frizz 0.13.8 → 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.
- package/README.md +14 -15
- package/dist/frizz.js +547 -419
- package/package.json +2 -2
package/dist/frizz.js
CHANGED
|
@@ -450,6 +450,15 @@ var init_access_codes = __esm({
|
|
|
450
450
|
const id = _AccessStore.idOf(payload);
|
|
451
451
|
return !(id && this.sessions.isRevoked(id));
|
|
452
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Which device this session belongs to, for the audit trail: its record id (what `frizz --sessions`
|
|
455
|
+
* lists and `--sign-out` takes), `"legacy"` for a session minted before ids existed, or null when it
|
|
456
|
+
* does not verify. Never the session itself — the id is the one part of it that is not a credential.
|
|
457
|
+
*/
|
|
458
|
+
sessionIdOf(session) {
|
|
459
|
+
if (!session || !this.verifySession(session)) return null;
|
|
460
|
+
return _AccessStore.idOf(session.slice(0, session.lastIndexOf("."))) ?? "legacy";
|
|
461
|
+
}
|
|
453
462
|
/**
|
|
454
463
|
* Sign out the device HOLDING this session — the "Sign out this device" row, not `--sign-out`.
|
|
455
464
|
*
|
|
@@ -846,6 +855,293 @@ code{font-family:var(--mono);font-size:.92em;padding:.1em .35em;border-radius:4p
|
|
|
846
855
|
}
|
|
847
856
|
});
|
|
848
857
|
|
|
858
|
+
// packages/server/src/frizz-paths.ts
|
|
859
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
860
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
861
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative, resolve, sep as sep2 } from "node:path";
|
|
862
|
+
function legacyFrizzRoot(home = homedir2()) {
|
|
863
|
+
return join3(home, LEGACY_DIR_NAME);
|
|
864
|
+
}
|
|
865
|
+
function xdg(env, name) {
|
|
866
|
+
const value = env[name];
|
|
867
|
+
return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
|
|
868
|
+
}
|
|
869
|
+
function insideHome(child, home) {
|
|
870
|
+
const norm = (value) => value.replace(/[\\/]+$/u, "").replaceAll("\\", "/").toLowerCase();
|
|
871
|
+
const parent = norm(home);
|
|
872
|
+
const inner = norm(child);
|
|
873
|
+
return inner === parent || inner.startsWith(`${parent}/`);
|
|
874
|
+
}
|
|
875
|
+
function windowsRoots(env, home) {
|
|
876
|
+
const local = env.LOCALAPPDATA && insideHome(env.LOCALAPPDATA, home) ? env.LOCALAPPDATA : join3(home, "AppData", "Local");
|
|
877
|
+
const base = join3(local, "Frizz");
|
|
878
|
+
return {
|
|
879
|
+
data: join3(base, "Data"),
|
|
880
|
+
state: join3(base, "State"),
|
|
881
|
+
cache: join3(base, "Cache")
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function darwinRoots(home) {
|
|
885
|
+
const support = join3(home, "Library", "Application Support", "Frizz");
|
|
886
|
+
return {
|
|
887
|
+
data: support,
|
|
888
|
+
// macOS has no state directory concept; Application Support is where this belongs, and the log
|
|
889
|
+
// files themselves already live under each project's own directory.
|
|
890
|
+
state: support,
|
|
891
|
+
cache: join3(home, "Library", "Caches", "Frizz")
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function xdgRoots(env, home) {
|
|
895
|
+
return {
|
|
896
|
+
data: join3(xdg(env, "XDG_DATA_HOME") ?? join3(home, ".local", "share"), "frizz"),
|
|
897
|
+
state: join3(xdg(env, "XDG_STATE_HOME") ?? join3(home, ".local", "state"), "frizz"),
|
|
898
|
+
cache: join3(xdg(env, "XDG_CACHE_HOME") ?? join3(home, ".cache"), "frizz")
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function frizzPaths(options2 = {}) {
|
|
902
|
+
const env = options2.env ?? process.env;
|
|
903
|
+
const platform = options2.platform ?? process.platform;
|
|
904
|
+
const home = options2.home ?? homedir2();
|
|
905
|
+
const exists = options2.exists ?? existsSync2;
|
|
906
|
+
const legacyRoot = legacyFrizzRoot(home);
|
|
907
|
+
if (exists(legacyRoot)) {
|
|
908
|
+
return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
|
|
909
|
+
}
|
|
910
|
+
const explicit = {
|
|
911
|
+
data: xdg(env, "XDG_DATA_HOME"),
|
|
912
|
+
state: xdg(env, "XDG_STATE_HOME"),
|
|
913
|
+
cache: xdg(env, "XDG_CACHE_HOME")
|
|
914
|
+
};
|
|
915
|
+
const platformRoots = platform === "win32" ? windowsRoots(env, home) : platform === "darwin" ? darwinRoots(home) : xdgRoots(env, home);
|
|
916
|
+
return {
|
|
917
|
+
data: explicit.data ? join3(explicit.data, "frizz") : platformRoots.data,
|
|
918
|
+
state: explicit.state ? join3(explicit.state, "frizz") : platformRoots.state,
|
|
919
|
+
cache: explicit.cache ? join3(explicit.cache, "frizz") : platformRoots.cache,
|
|
920
|
+
legacy: false
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function frizzRoots() {
|
|
924
|
+
return cached ??= frizzPaths();
|
|
925
|
+
}
|
|
926
|
+
function projectStateDir(projectId, home) {
|
|
927
|
+
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
|
|
928
|
+
}
|
|
929
|
+
function launchHostDir(home) {
|
|
930
|
+
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "host");
|
|
931
|
+
}
|
|
932
|
+
var LEGACY_DIR_NAME, cached;
|
|
933
|
+
var init_frizz_paths = __esm({
|
|
934
|
+
"packages/server/src/frizz-paths.ts"() {
|
|
935
|
+
"use strict";
|
|
936
|
+
LEGACY_DIR_NAME = ".frizz";
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// packages/server/src/logging.ts
|
|
941
|
+
import {
|
|
942
|
+
appendFileSync,
|
|
943
|
+
mkdirSync as mkdirSync2,
|
|
944
|
+
openSync,
|
|
945
|
+
readdirSync,
|
|
946
|
+
rmSync,
|
|
947
|
+
statSync as statSync2,
|
|
948
|
+
symlinkSync,
|
|
949
|
+
writeFileSync as writeFileSync2,
|
|
950
|
+
writeSync
|
|
951
|
+
} from "node:fs";
|
|
952
|
+
import { homedir as homedir3 } from "node:os";
|
|
953
|
+
import { join as join4 } from "node:path";
|
|
954
|
+
function defaultLogRoot(stateDir, home = homedir3()) {
|
|
955
|
+
return stateDir ? join4(stateDir, "logs") : join4(frizzPaths({ home }).state, "logs");
|
|
956
|
+
}
|
|
957
|
+
function runStamp(at) {
|
|
958
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
959
|
+
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
|
|
960
|
+
}
|
|
961
|
+
function runLogPath(stateDir, at = /* @__PURE__ */ new Date(), pid = process.pid, home = homedir3(), env = process.env) {
|
|
962
|
+
const name = `frizz-${runStamp(at)}-${pid}.log`;
|
|
963
|
+
const override = env[LOG_PATH_ENV]?.trim();
|
|
964
|
+
if (override) return override.endsWith(".log") ? override : join4(override, name);
|
|
965
|
+
return join4(defaultLogRoot(stateDir, home), name);
|
|
966
|
+
}
|
|
967
|
+
function latestLogPath(dir) {
|
|
968
|
+
return join4(dir, "latest.log");
|
|
969
|
+
}
|
|
970
|
+
function linkLatest(dir, target2) {
|
|
971
|
+
const link = latestLogPath(dir);
|
|
972
|
+
try {
|
|
973
|
+
rmSync(link, { force: true });
|
|
974
|
+
symlinkSync(target2, link);
|
|
975
|
+
} catch {
|
|
976
|
+
try {
|
|
977
|
+
writeFileSync2(link, `${target2}
|
|
978
|
+
`, { mode: 384 });
|
|
979
|
+
} catch {
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Date.now()) {
|
|
984
|
+
let entries;
|
|
985
|
+
try {
|
|
986
|
+
entries = readdirSync(dir).filter((name) => name.endsWith(".log") && name !== "latest.log");
|
|
987
|
+
} catch {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const dated = entries.map((name) => {
|
|
991
|
+
try {
|
|
992
|
+
return { name, at: statSync2(join4(dir, name)).mtimeMs };
|
|
993
|
+
} catch {
|
|
994
|
+
return { name, at: 0 };
|
|
995
|
+
}
|
|
996
|
+
}).sort((a, b) => b.at - a.at);
|
|
997
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
998
|
+
const stale = dated.filter((entry, index) => index >= keep || entry.at < cutoff);
|
|
999
|
+
for (const entry of stale) {
|
|
1000
|
+
try {
|
|
1001
|
+
rmSync(join4(dir, entry.name), { force: true });
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function formatDiskLine(record2) {
|
|
1007
|
+
const at = new Date(record2.at);
|
|
1008
|
+
const pad = (value, width = 2) => String(value).padStart(width, "0");
|
|
1009
|
+
const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}.${pad(at.getMilliseconds(), 3)}`;
|
|
1010
|
+
return `${clock} ${record2.level.toUpperCase().padEnd(5)} ${record2.scope.padEnd(12)} ${record2.message}
|
|
1011
|
+
`;
|
|
1012
|
+
}
|
|
1013
|
+
function openLogFile(path) {
|
|
1014
|
+
try {
|
|
1015
|
+
mkdirSync2(join4(path, ".."), { recursive: true, mode: 448 });
|
|
1016
|
+
return openSync(path, "a", 384);
|
|
1017
|
+
} catch {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function createLogger(options2 = {}) {
|
|
1022
|
+
const path = options2.file === null ? null : options2.file ?? runLogPath(options2.stateDir);
|
|
1023
|
+
const level = options2.level ?? "debug";
|
|
1024
|
+
const now = options2.now ?? Date.now;
|
|
1025
|
+
const threshold = LEVEL_ORDER[level];
|
|
1026
|
+
const maxBytes = options2.maxBytes ?? MAX_LOG_BYTES;
|
|
1027
|
+
let fd = path === null ? null : openLogFile(path);
|
|
1028
|
+
if (fd !== null && path !== null && options2.owner !== false) {
|
|
1029
|
+
const dir = join4(path, "..");
|
|
1030
|
+
pruneRunLogs(dir);
|
|
1031
|
+
linkLatest(dir, path);
|
|
1032
|
+
}
|
|
1033
|
+
let written = 0;
|
|
1034
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1035
|
+
const emit = (recordLevel, scope, message) => {
|
|
1036
|
+
if (LEVEL_ORDER[recordLevel] < threshold) return;
|
|
1037
|
+
const record2 = { at: now(), level: recordLevel, scope, message };
|
|
1038
|
+
if (fd !== null) {
|
|
1039
|
+
try {
|
|
1040
|
+
const line = formatDiskLine(record2);
|
|
1041
|
+
if (written + line.length > maxBytes) {
|
|
1042
|
+
writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
|
|
1043
|
+
`);
|
|
1044
|
+
fd = null;
|
|
1045
|
+
} else {
|
|
1046
|
+
writeSync(fd, line);
|
|
1047
|
+
written += line.length;
|
|
1048
|
+
}
|
|
1049
|
+
} catch {
|
|
1050
|
+
fd = null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
for (const listener of listeners) {
|
|
1054
|
+
try {
|
|
1055
|
+
listener(record2);
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
return {
|
|
1061
|
+
debug: (scope, message) => emit("debug", scope, message),
|
|
1062
|
+
info: (scope, message) => emit("info", scope, message),
|
|
1063
|
+
warn: (scope, message) => emit("warn", scope, message),
|
|
1064
|
+
error: (scope, message) => emit("error", scope, message),
|
|
1065
|
+
get file() {
|
|
1066
|
+
return fd === null ? null : path;
|
|
1067
|
+
},
|
|
1068
|
+
onRecord(listener) {
|
|
1069
|
+
listeners.add(listener);
|
|
1070
|
+
return () => listeners.delete(listener);
|
|
1071
|
+
},
|
|
1072
|
+
close() {
|
|
1073
|
+
listeners.clear();
|
|
1074
|
+
fd = null;
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function setAmbientLogger(logger2) {
|
|
1079
|
+
ambient = logger2;
|
|
1080
|
+
return logger2;
|
|
1081
|
+
}
|
|
1082
|
+
function formatFeedLine(record2) {
|
|
1083
|
+
return formatDiskLine(record2).trimEnd();
|
|
1084
|
+
}
|
|
1085
|
+
function attachTerminalMirror(logger2, enabled = process.env.FRIZZ_DEBUG === "1") {
|
|
1086
|
+
if (!enabled) return () => {
|
|
1087
|
+
};
|
|
1088
|
+
return logger2.onRecord((record2) => process.stderr.write(`${formatFeedLine(record2)}
|
|
1089
|
+
`));
|
|
1090
|
+
}
|
|
1091
|
+
function ambientLogger() {
|
|
1092
|
+
if (ambient) return ambient;
|
|
1093
|
+
const inherited = process.env[LOG_FILE_ENV] ?? process.env[LOG_PATH_ENV];
|
|
1094
|
+
const level = process.env[LOG_LEVEL_ENV];
|
|
1095
|
+
const logger2 = createLogger({
|
|
1096
|
+
...inherited ? { file: inherited, owner: false } : { file: null },
|
|
1097
|
+
...level && level in LEVEL_ORDER ? { level } : {}
|
|
1098
|
+
});
|
|
1099
|
+
attachTerminalMirror(logger2);
|
|
1100
|
+
ambient = logger2;
|
|
1101
|
+
return ambient;
|
|
1102
|
+
}
|
|
1103
|
+
function logEnvironment(logger2, level = "debug") {
|
|
1104
|
+
return logger2.file ? { [LOG_FILE_ENV]: logger2.file, [LOG_LEVEL_ENV]: level } : {};
|
|
1105
|
+
}
|
|
1106
|
+
var LEVEL_ORDER, LOG_FILE_ENV, LOG_LEVEL_ENV, LOG_PATH_ENV, RETAINED_RUNS, RETAINED_DAYS, MAX_LOG_BYTES, ambient, log;
|
|
1107
|
+
var init_logging = __esm({
|
|
1108
|
+
"packages/server/src/logging.ts"() {
|
|
1109
|
+
"use strict";
|
|
1110
|
+
init_frizz_paths();
|
|
1111
|
+
LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
1112
|
+
LOG_FILE_ENV = "FRIZZ_LOG_FILE";
|
|
1113
|
+
LOG_LEVEL_ENV = "FRIZZ_LOG_LEVEL";
|
|
1114
|
+
LOG_PATH_ENV = "FRIZZ_LOG_PATH";
|
|
1115
|
+
RETAINED_RUNS = 20;
|
|
1116
|
+
RETAINED_DAYS = 14;
|
|
1117
|
+
MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
1118
|
+
ambient = null;
|
|
1119
|
+
log = {
|
|
1120
|
+
debug: (scope, message) => ambientLogger().debug(scope, message),
|
|
1121
|
+
info: (scope, message) => ambientLogger().info(scope, message),
|
|
1122
|
+
warn: (scope, message) => ambientLogger().warn(scope, message),
|
|
1123
|
+
error: (scope, message) => ambientLogger().error(scope, message)
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
// packages/server/src/audit.ts
|
|
1129
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1130
|
+
function encodeCallerStamp(stamp) {
|
|
1131
|
+
return Buffer.from(JSON.stringify(stamp)).toString("base64url");
|
|
1132
|
+
}
|
|
1133
|
+
var CALLER_HEADER, requestOrigin;
|
|
1134
|
+
var init_audit = __esm({
|
|
1135
|
+
"packages/server/src/audit.ts"() {
|
|
1136
|
+
"use strict";
|
|
1137
|
+
init_access_codes();
|
|
1138
|
+
init_frizz_paths();
|
|
1139
|
+
init_logging();
|
|
1140
|
+
CALLER_HEADER = "x-frizz-caller";
|
|
1141
|
+
requestOrigin = new AsyncLocalStorage();
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
|
|
849
1145
|
// node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
850
1146
|
var require_identity = __commonJS({
|
|
851
1147
|
"node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -12982,31 +13278,6 @@ var init_drainable_worker = __esm({
|
|
|
12982
13278
|
}
|
|
12983
13279
|
});
|
|
12984
13280
|
|
|
12985
|
-
// packages/shared/src/frizz-md.ts
|
|
12986
|
-
var FrizzMdLanding, FrizzMdAutonomy, FRIZZ_MD_NOTES_MAX, FrizzMdAnswers, FrizzMdStatus;
|
|
12987
|
-
var init_frizz_md = __esm({
|
|
12988
|
-
"packages/shared/src/frizz-md.ts"() {
|
|
12989
|
-
"use strict";
|
|
12990
|
-
init_zod();
|
|
12991
|
-
FrizzMdLanding = external_exports.enum(["pull-request", "main", "main-push", "branch"]);
|
|
12992
|
-
FrizzMdAutonomy = external_exports.enum(["decide", "balanced", "check-in"]);
|
|
12993
|
-
FRIZZ_MD_NOTES_MAX = 8e3;
|
|
12994
|
-
FrizzMdAnswers = external_exports.object({
|
|
12995
|
-
landing: FrizzMdLanding,
|
|
12996
|
-
autonomy: FrizzMdAutonomy,
|
|
12997
|
-
notes: external_exports.string().max(FRIZZ_MD_NOTES_MAX)
|
|
12998
|
-
}).strict();
|
|
12999
|
-
FrizzMdStatus = external_exports.object({
|
|
13000
|
-
/** A FRIZZ.md is already at the project root: the project has its rules, so nothing is asked. */
|
|
13001
|
-
exists: external_exports.boolean(),
|
|
13002
|
-
/** The operator skipped the questionnaire for this project: it is not asked again. */
|
|
13003
|
-
skipped: external_exports.boolean(),
|
|
13004
|
-
/** The branch the landing rules name — the remote's default, else the checked-out one, else `main`. */
|
|
13005
|
-
defaultBranch: external_exports.string()
|
|
13006
|
-
});
|
|
13007
|
-
}
|
|
13008
|
-
});
|
|
13009
|
-
|
|
13010
13281
|
// packages/shared/src/receipt-bus.ts
|
|
13011
13282
|
var init_receipt_bus = __esm({
|
|
13012
13283
|
"packages/shared/src/receipt-bus.ts"() {
|
|
@@ -13254,7 +13525,7 @@ function requireEffortOutsideAcp(profile, ctx) {
|
|
|
13254
13525
|
function fallbackPort(base) {
|
|
13255
13526
|
return base + 1e4;
|
|
13256
13527
|
}
|
|
13257
|
-
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;
|
|
13258
13529
|
var init_src = __esm({
|
|
13259
13530
|
"packages/shared/src/index.ts"() {
|
|
13260
13531
|
"use strict";
|
|
@@ -13267,7 +13538,6 @@ var init_src = __esm({
|
|
|
13267
13538
|
init_code_fences();
|
|
13268
13539
|
init_delta();
|
|
13269
13540
|
init_drainable_worker();
|
|
13270
|
-
init_frizz_md();
|
|
13271
13541
|
init_interactions();
|
|
13272
13542
|
init_receipt_bus();
|
|
13273
13543
|
init_relay_protocol();
|
|
@@ -13369,7 +13639,7 @@ var init_src = __esm({
|
|
|
13369
13639
|
"running",
|
|
13370
13640
|
// process alive, turn in flight
|
|
13371
13641
|
"perm-prompt",
|
|
13372
|
-
// process alive,
|
|
13642
|
+
// process alive, parked on a permission request a person has not answered yet
|
|
13373
13643
|
"turn-idle",
|
|
13374
13644
|
// process alive, waiting at the prompt
|
|
13375
13645
|
"exited"
|
|
@@ -14566,6 +14836,19 @@ var init_src = __esm({
|
|
|
14566
14836
|
// ambiguous, and only the operator decides to risk a second copy.
|
|
14567
14837
|
supersedes: external_exports.string().min(1).max(200).optional()
|
|
14568
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();
|
|
14569
14852
|
DismissFailedFollowUpInput = external_exports.object({
|
|
14570
14853
|
slug: ThreadSlug,
|
|
14571
14854
|
deliveryId: external_exports.string().min(1).max(200)
|
|
@@ -15275,6 +15558,20 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15275
15558
|
// pairing, and `projectTranscriptPeerNames` uses this id to ask it. Never a drawer key on its own —
|
|
15276
15559
|
// that is `peerDispatchId`, which the same pass can also supply once this resolves.
|
|
15277
15560
|
peerSenderTaskId: external_exports.string().optional(),
|
|
15561
|
+
// ANOTHER TOP-LEVEL SESSION wrote this user turn — a thread messaging this one through Claude Code's
|
|
15562
|
+
// cross-session channel (see parseCrossSessionMessage), most often the coordinator thread that spawned
|
|
15563
|
+
// it. Not the human, and not a sub-agent either: the chat draws it as an expandable hairline naming
|
|
15564
|
+
// the sending thread, with the body one click away. `displayText` carries the unwrapped body.
|
|
15565
|
+
//
|
|
15566
|
+
// `name` is the sender's Claude Code session name, the fallback label. `thread` is the Frizz thread
|
|
15567
|
+
// behind it, filled in by the server from the sender's socket (session-peer.ts) — absent when the
|
|
15568
|
+
// sender is not a Frizz thread or is gone without having been resolved, and then the line names the
|
|
15569
|
+
// session without a link. `project` is the URL slug of the sender's project.
|
|
15570
|
+
sessionPeer: external_exports.object({
|
|
15571
|
+
name: external_exports.string().optional(),
|
|
15572
|
+
socket: external_exports.string().optional(),
|
|
15573
|
+
thread: external_exports.object({ project: external_exports.string().optional(), slug: external_exports.string(), title: external_exports.string() }).optional()
|
|
15574
|
+
}).optional(),
|
|
15278
15575
|
// FRIZZ REFUSED the ```awaiting fence this message ends in — it named something that is not running,
|
|
15279
15576
|
// or named nothing at all, or used a retired line kind (see `isParkCorrection`). The fence is not a
|
|
15280
15577
|
// park, so the chat draws nothing for it: an hourglass card with a park button asserts a wait that
|
|
@@ -15384,8 +15681,9 @@ function responseJson(res, status, value) {
|
|
|
15384
15681
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
15385
15682
|
res.end(JSON.stringify(value));
|
|
15386
15683
|
}
|
|
15387
|
-
function proxyHeaders(req, childPort, vouchSameOrigin = false) {
|
|
15684
|
+
function proxyHeaders(req, childPort, callerStamp, vouchSameOrigin = false) {
|
|
15388
15685
|
const headers = { ...req.headers };
|
|
15686
|
+
headers[CALLER_HEADER] = callerStamp;
|
|
15389
15687
|
headers.host = `127.0.0.1:${childPort}`;
|
|
15390
15688
|
if (typeof headers.origin === "string") headers.origin = `http://127.0.0.1:${childPort}`;
|
|
15391
15689
|
for (const name of FORWARDED_HEADERS) delete headers[name];
|
|
@@ -15402,6 +15700,15 @@ function readCookie(header, name) {
|
|
|
15402
15700
|
}
|
|
15403
15701
|
return void 0;
|
|
15404
15702
|
}
|
|
15703
|
+
function firstHeader(value) {
|
|
15704
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
15705
|
+
return first?.trim() || void 0;
|
|
15706
|
+
}
|
|
15707
|
+
function isLoopbackAddress(address) {
|
|
15708
|
+
if (!address) return false;
|
|
15709
|
+
const v4 = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
|
|
15710
|
+
return v4.startsWith("127.") || address === "::1";
|
|
15711
|
+
}
|
|
15405
15712
|
function isControlRequest(req) {
|
|
15406
15713
|
const url = new URL(req.url ?? "/", "http://frizz.invalid");
|
|
15407
15714
|
return url.pathname === SUPERVISOR_RESTART_PATH || url.pathname === SUPERVISOR_UPDATE_RESTART_PATH || url.pathname === SUPERVISOR_STATUS_PATH || url.pathname === SUPERVISOR_ACCESS_CODE_PATH || url.pathname === SUPERVISOR_SESSIONS_PATH || url.pathname === SUPERVISOR_SIGN_OUT_PATH;
|
|
@@ -15435,6 +15742,7 @@ var init_restart_supervisor = __esm({
|
|
|
15435
15742
|
init_local_origin();
|
|
15436
15743
|
init_local_image();
|
|
15437
15744
|
init_supervisor_pages();
|
|
15745
|
+
init_audit();
|
|
15438
15746
|
init_src();
|
|
15439
15747
|
SUPERVISOR_CONTROL_PREFIX = "/_frizz/control";
|
|
15440
15748
|
SUPERVISOR_RESTART_PATH = `${SUPERVISOR_CONTROL_PREFIX}/restart`;
|
|
@@ -15799,6 +16107,28 @@ var init_restart_supervisor = __esm({
|
|
|
15799
16107
|
if (!this.publicOrigin) return true;
|
|
15800
16108
|
return this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
15801
16109
|
}
|
|
16110
|
+
/**
|
|
16111
|
+
* What this proxy knows about who sent a request, for the application server's audit trail
|
|
16112
|
+
* (audit.ts). The session is named by its record id, never its value, and only once it verifies.
|
|
16113
|
+
*
|
|
16114
|
+
* `clientIp` is only ever taken from a request that arrived as the public origin, and it is that
|
|
16115
|
+
* origin's CLAIM: a relay or tunnel reaches this port from loopback, so the real visitor address
|
|
16116
|
+
* exists only in what the relay chose to forward (`cf-connecting-ip` from Cloudflare).
|
|
16117
|
+
*/
|
|
16118
|
+
callerStamp(req) {
|
|
16119
|
+
const remote2 = req.socket.remoteAddress ?? null;
|
|
16120
|
+
const via = this.arrivedPublicly(req) ? "public" : isLoopbackAddress(remote2) ? "loopback" : "network";
|
|
16121
|
+
const host = via === "loopback" ? void 0 : firstHeader(req.headers.host);
|
|
16122
|
+
const forwarded = firstHeader(req.headers["x-forwarded-for"])?.split(",")[0]?.trim();
|
|
16123
|
+
const clientIp = via === "public" ? firstHeader(req.headers["cf-connecting-ip"]) ?? firstHeader(req.headers["x-real-ip"]) ?? forwarded : void 0;
|
|
16124
|
+
return encodeCallerStamp({
|
|
16125
|
+
via,
|
|
16126
|
+
remote: remote2,
|
|
16127
|
+
...host ? { host: host.slice(0, 200) } : {},
|
|
16128
|
+
...clientIp ? { clientIp: clientIp.slice(0, 200) } : {},
|
|
16129
|
+
session: this.access.sessionIdOf(readCookie(req.headers.cookie, SESSION_COOKIE))
|
|
16130
|
+
});
|
|
16131
|
+
}
|
|
15802
16132
|
/** Did this request arrive through the public origin carrying a live session? Loopback never does. */
|
|
15803
16133
|
carriesRemoteSession(req) {
|
|
15804
16134
|
return this.arrivedPublicly(req) && this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
@@ -15898,134 +16228,55 @@ var init_restart_supervisor = __esm({
|
|
|
15898
16228
|
port: childPort,
|
|
15899
16229
|
method: req.method,
|
|
15900
16230
|
path: req.url,
|
|
15901
|
-
headers: proxyHeaders(req, childPort, this.vouchesSameOrigin(req))
|
|
15902
|
-
}, (upstreamResponse) => {
|
|
15903
|
-
res.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers);
|
|
15904
|
-
upstreamResponse.pipe(res);
|
|
15905
|
-
});
|
|
15906
|
-
upstream.once("error", () => {
|
|
15907
|
-
if (res.headersSent) return res.destroy();
|
|
15908
|
-
res.writeHead(503, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
15909
|
-
res.end(recoveryPage(req.url ?? "/", "unreachable"));
|
|
15910
|
-
});
|
|
15911
|
-
req.pipe(upstream);
|
|
15912
|
-
}
|
|
15913
|
-
handleUpgrade(req, socket, head) {
|
|
15914
|
-
if (this.arrivedPublicly(req) && !this.sessionAccepted(req)) {
|
|
15915
|
-
rejectWebSocketUpgrade(socket, 401, "Unauthorized");
|
|
15916
|
-
return;
|
|
15917
|
-
}
|
|
15918
|
-
if (!this.authorityAccepted(req, false)) {
|
|
15919
|
-
socket.destroy();
|
|
15920
|
-
return;
|
|
15921
|
-
}
|
|
15922
|
-
const childPort = this.options.childPort();
|
|
15923
|
-
if (!childPort) {
|
|
15924
|
-
socket.destroy();
|
|
15925
|
-
return;
|
|
15926
|
-
}
|
|
15927
|
-
const upstream = connect(childPort, "127.0.0.1");
|
|
15928
|
-
for (const half of [socket, upstream]) {
|
|
15929
|
-
this.upgradedSockets.add(half);
|
|
15930
|
-
half.once("close", () => this.upgradedSockets.delete(half));
|
|
15931
|
-
}
|
|
15932
|
-
upstream.once("connect", () => {
|
|
15933
|
-
const headers = proxyHeaders(req, childPort);
|
|
15934
|
-
headers.connection = req.headers.connection ?? "Upgrade";
|
|
15935
|
-
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
|
|
15936
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
15937
|
-
if (value === void 0) continue;
|
|
15938
|
-
for (const entry of Array.isArray(value) ? value : [value]) lines.push(`${name}: ${entry}`);
|
|
15939
|
-
}
|
|
15940
|
-
upstream.write(`${lines.join("\r\n")}\r
|
|
15941
|
-
\r
|
|
15942
|
-
`);
|
|
15943
|
-
if (head.length > 0) upstream.write(head);
|
|
15944
|
-
socket.pipe(upstream).pipe(socket);
|
|
15945
|
-
});
|
|
15946
|
-
upstream.once("error", () => socket.destroy());
|
|
15947
|
-
socket.once("error", () => upstream.destroy());
|
|
15948
|
-
}
|
|
15949
|
-
};
|
|
15950
|
-
}
|
|
15951
|
-
});
|
|
15952
|
-
|
|
15953
|
-
// packages/server/src/frizz-paths.ts
|
|
15954
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
15955
|
-
import { homedir as homedir2, tmpdir } from "node:os";
|
|
15956
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative, resolve, sep as sep2 } from "node:path";
|
|
15957
|
-
function legacyFrizzRoot(home = homedir2()) {
|
|
15958
|
-
return join3(home, LEGACY_DIR_NAME);
|
|
15959
|
-
}
|
|
15960
|
-
function xdg(env, name) {
|
|
15961
|
-
const value = env[name];
|
|
15962
|
-
return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
|
|
15963
|
-
}
|
|
15964
|
-
function insideHome(child, home) {
|
|
15965
|
-
const norm = (value) => value.replace(/[\\/]+$/u, "").replaceAll("\\", "/").toLowerCase();
|
|
15966
|
-
const parent = norm(home);
|
|
15967
|
-
const inner = norm(child);
|
|
15968
|
-
return inner === parent || inner.startsWith(`${parent}/`);
|
|
15969
|
-
}
|
|
15970
|
-
function windowsRoots(env, home) {
|
|
15971
|
-
const local = env.LOCALAPPDATA && insideHome(env.LOCALAPPDATA, home) ? env.LOCALAPPDATA : join3(home, "AppData", "Local");
|
|
15972
|
-
const base = join3(local, "Frizz");
|
|
15973
|
-
return {
|
|
15974
|
-
data: join3(base, "Data"),
|
|
15975
|
-
state: join3(base, "State"),
|
|
15976
|
-
cache: join3(base, "Cache")
|
|
15977
|
-
};
|
|
15978
|
-
}
|
|
15979
|
-
function darwinRoots(home) {
|
|
15980
|
-
const support = join3(home, "Library", "Application Support", "Frizz");
|
|
15981
|
-
return {
|
|
15982
|
-
data: support,
|
|
15983
|
-
// macOS has no state directory concept; Application Support is where this belongs, and the log
|
|
15984
|
-
// files themselves already live under each project's own directory.
|
|
15985
|
-
state: support,
|
|
15986
|
-
cache: join3(home, "Library", "Caches", "Frizz")
|
|
15987
|
-
};
|
|
15988
|
-
}
|
|
15989
|
-
function xdgRoots(env, home) {
|
|
15990
|
-
return {
|
|
15991
|
-
data: join3(xdg(env, "XDG_DATA_HOME") ?? join3(home, ".local", "share"), "frizz"),
|
|
15992
|
-
state: join3(xdg(env, "XDG_STATE_HOME") ?? join3(home, ".local", "state"), "frizz"),
|
|
15993
|
-
cache: join3(xdg(env, "XDG_CACHE_HOME") ?? join3(home, ".cache"), "frizz")
|
|
15994
|
-
};
|
|
15995
|
-
}
|
|
15996
|
-
function frizzPaths(options2 = {}) {
|
|
15997
|
-
const env = options2.env ?? process.env;
|
|
15998
|
-
const platform = options2.platform ?? process.platform;
|
|
15999
|
-
const home = options2.home ?? homedir2();
|
|
16000
|
-
const exists = options2.exists ?? existsSync2;
|
|
16001
|
-
const legacyRoot = legacyFrizzRoot(home);
|
|
16002
|
-
if (exists(legacyRoot)) {
|
|
16003
|
-
return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
|
|
16004
|
-
}
|
|
16005
|
-
const explicit = {
|
|
16006
|
-
data: xdg(env, "XDG_DATA_HOME"),
|
|
16007
|
-
state: xdg(env, "XDG_STATE_HOME"),
|
|
16008
|
-
cache: xdg(env, "XDG_CACHE_HOME")
|
|
16009
|
-
};
|
|
16010
|
-
const platformRoots = platform === "win32" ? windowsRoots(env, home) : platform === "darwin" ? darwinRoots(home) : xdgRoots(env, home);
|
|
16011
|
-
return {
|
|
16012
|
-
data: explicit.data ? join3(explicit.data, "frizz") : platformRoots.data,
|
|
16013
|
-
state: explicit.state ? join3(explicit.state, "frizz") : platformRoots.state,
|
|
16014
|
-
cache: explicit.cache ? join3(explicit.cache, "frizz") : platformRoots.cache,
|
|
16015
|
-
legacy: false
|
|
16016
|
-
};
|
|
16017
|
-
}
|
|
16018
|
-
function frizzRoots() {
|
|
16019
|
-
return cached ??= frizzPaths();
|
|
16020
|
-
}
|
|
16021
|
-
function projectStateDir(projectId, home) {
|
|
16022
|
-
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
|
|
16023
|
-
}
|
|
16024
|
-
var LEGACY_DIR_NAME, cached;
|
|
16025
|
-
var init_frizz_paths = __esm({
|
|
16026
|
-
"packages/server/src/frizz-paths.ts"() {
|
|
16027
|
-
"use strict";
|
|
16028
|
-
LEGACY_DIR_NAME = ".frizz";
|
|
16231
|
+
headers: proxyHeaders(req, childPort, this.callerStamp(req), this.vouchesSameOrigin(req))
|
|
16232
|
+
}, (upstreamResponse) => {
|
|
16233
|
+
res.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers);
|
|
16234
|
+
upstreamResponse.pipe(res);
|
|
16235
|
+
});
|
|
16236
|
+
upstream.once("error", () => {
|
|
16237
|
+
if (res.headersSent) return res.destroy();
|
|
16238
|
+
res.writeHead(503, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
16239
|
+
res.end(recoveryPage(req.url ?? "/", "unreachable"));
|
|
16240
|
+
});
|
|
16241
|
+
req.pipe(upstream);
|
|
16242
|
+
}
|
|
16243
|
+
handleUpgrade(req, socket, head) {
|
|
16244
|
+
if (this.arrivedPublicly(req) && !this.sessionAccepted(req)) {
|
|
16245
|
+
rejectWebSocketUpgrade(socket, 401, "Unauthorized");
|
|
16246
|
+
return;
|
|
16247
|
+
}
|
|
16248
|
+
if (!this.authorityAccepted(req, false)) {
|
|
16249
|
+
socket.destroy();
|
|
16250
|
+
return;
|
|
16251
|
+
}
|
|
16252
|
+
const childPort = this.options.childPort();
|
|
16253
|
+
if (!childPort) {
|
|
16254
|
+
socket.destroy();
|
|
16255
|
+
return;
|
|
16256
|
+
}
|
|
16257
|
+
const upstream = connect(childPort, "127.0.0.1");
|
|
16258
|
+
for (const half of [socket, upstream]) {
|
|
16259
|
+
this.upgradedSockets.add(half);
|
|
16260
|
+
half.once("close", () => this.upgradedSockets.delete(half));
|
|
16261
|
+
}
|
|
16262
|
+
upstream.once("connect", () => {
|
|
16263
|
+
const headers = proxyHeaders(req, childPort, this.callerStamp(req));
|
|
16264
|
+
headers.connection = req.headers.connection ?? "Upgrade";
|
|
16265
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
|
|
16266
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
16267
|
+
if (value === void 0) continue;
|
|
16268
|
+
for (const entry of Array.isArray(value) ? value : [value]) lines.push(`${name}: ${entry}`);
|
|
16269
|
+
}
|
|
16270
|
+
upstream.write(`${lines.join("\r\n")}\r
|
|
16271
|
+
\r
|
|
16272
|
+
`);
|
|
16273
|
+
if (head.length > 0) upstream.write(head);
|
|
16274
|
+
socket.pipe(upstream).pipe(socket);
|
|
16275
|
+
});
|
|
16276
|
+
upstream.once("error", () => socket.destroy());
|
|
16277
|
+
socket.once("error", () => upstream.destroy());
|
|
16278
|
+
}
|
|
16279
|
+
};
|
|
16029
16280
|
}
|
|
16030
16281
|
});
|
|
16031
16282
|
|
|
@@ -19723,194 +19974,6 @@ var init_wrapper = __esm({
|
|
|
19723
19974
|
}
|
|
19724
19975
|
});
|
|
19725
19976
|
|
|
19726
|
-
// packages/server/src/logging.ts
|
|
19727
|
-
import {
|
|
19728
|
-
appendFileSync,
|
|
19729
|
-
mkdirSync as mkdirSync4,
|
|
19730
|
-
openSync,
|
|
19731
|
-
readdirSync,
|
|
19732
|
-
rmSync as rmSync2,
|
|
19733
|
-
statSync as statSync2,
|
|
19734
|
-
symlinkSync,
|
|
19735
|
-
writeFileSync as writeFileSync4,
|
|
19736
|
-
writeSync
|
|
19737
|
-
} from "node:fs";
|
|
19738
|
-
import { homedir as homedir6 } from "node:os";
|
|
19739
|
-
import { join as join6 } from "node:path";
|
|
19740
|
-
function defaultLogRoot(stateDir, home = homedir6()) {
|
|
19741
|
-
return stateDir ? join6(stateDir, "logs") : join6(frizzPaths({ home }).state, "logs");
|
|
19742
|
-
}
|
|
19743
|
-
function runStamp(at) {
|
|
19744
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
19745
|
-
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
|
|
19746
|
-
}
|
|
19747
|
-
function runLogPath(stateDir, at = /* @__PURE__ */ new Date(), pid = process.pid, home = homedir6(), env = process.env) {
|
|
19748
|
-
const name = `frizz-${runStamp(at)}-${pid}.log`;
|
|
19749
|
-
const override = env[LOG_PATH_ENV]?.trim();
|
|
19750
|
-
if (override) return override.endsWith(".log") ? override : join6(override, name);
|
|
19751
|
-
return join6(defaultLogRoot(stateDir, home), name);
|
|
19752
|
-
}
|
|
19753
|
-
function latestLogPath(dir) {
|
|
19754
|
-
return join6(dir, "latest.log");
|
|
19755
|
-
}
|
|
19756
|
-
function linkLatest(dir, target2) {
|
|
19757
|
-
const link = latestLogPath(dir);
|
|
19758
|
-
try {
|
|
19759
|
-
rmSync2(link, { force: true });
|
|
19760
|
-
symlinkSync(target2, link);
|
|
19761
|
-
} catch {
|
|
19762
|
-
try {
|
|
19763
|
-
writeFileSync4(link, `${target2}
|
|
19764
|
-
`, { mode: 384 });
|
|
19765
|
-
} catch {
|
|
19766
|
-
}
|
|
19767
|
-
}
|
|
19768
|
-
}
|
|
19769
|
-
function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Date.now()) {
|
|
19770
|
-
let entries;
|
|
19771
|
-
try {
|
|
19772
|
-
entries = readdirSync(dir).filter((name) => name.endsWith(".log") && name !== "latest.log");
|
|
19773
|
-
} catch {
|
|
19774
|
-
return;
|
|
19775
|
-
}
|
|
19776
|
-
const dated = entries.map((name) => {
|
|
19777
|
-
try {
|
|
19778
|
-
return { name, at: statSync2(join6(dir, name)).mtimeMs };
|
|
19779
|
-
} catch {
|
|
19780
|
-
return { name, at: 0 };
|
|
19781
|
-
}
|
|
19782
|
-
}).sort((a, b) => b.at - a.at);
|
|
19783
|
-
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
19784
|
-
const stale = dated.filter((entry, index) => index >= keep || entry.at < cutoff);
|
|
19785
|
-
for (const entry of stale) {
|
|
19786
|
-
try {
|
|
19787
|
-
rmSync2(join6(dir, entry.name), { force: true });
|
|
19788
|
-
} catch {
|
|
19789
|
-
}
|
|
19790
|
-
}
|
|
19791
|
-
}
|
|
19792
|
-
function formatDiskLine(record2) {
|
|
19793
|
-
const at = new Date(record2.at);
|
|
19794
|
-
const pad = (value, width = 2) => String(value).padStart(width, "0");
|
|
19795
|
-
const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}.${pad(at.getMilliseconds(), 3)}`;
|
|
19796
|
-
return `${clock} ${record2.level.toUpperCase().padEnd(5)} ${record2.scope.padEnd(12)} ${record2.message}
|
|
19797
|
-
`;
|
|
19798
|
-
}
|
|
19799
|
-
function openLogFile(path) {
|
|
19800
|
-
try {
|
|
19801
|
-
mkdirSync4(join6(path, ".."), { recursive: true, mode: 448 });
|
|
19802
|
-
return openSync(path, "a", 384);
|
|
19803
|
-
} catch {
|
|
19804
|
-
return null;
|
|
19805
|
-
}
|
|
19806
|
-
}
|
|
19807
|
-
function createLogger(options2 = {}) {
|
|
19808
|
-
const path = options2.file === null ? null : options2.file ?? runLogPath(options2.stateDir);
|
|
19809
|
-
const level = options2.level ?? "debug";
|
|
19810
|
-
const now = options2.now ?? Date.now;
|
|
19811
|
-
const threshold = LEVEL_ORDER[level];
|
|
19812
|
-
const maxBytes = options2.maxBytes ?? MAX_LOG_BYTES;
|
|
19813
|
-
let fd = path === null ? null : openLogFile(path);
|
|
19814
|
-
if (fd !== null && path !== null && options2.owner !== false) {
|
|
19815
|
-
const dir = join6(path, "..");
|
|
19816
|
-
pruneRunLogs(dir);
|
|
19817
|
-
linkLatest(dir, path);
|
|
19818
|
-
}
|
|
19819
|
-
let written = 0;
|
|
19820
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
19821
|
-
const emit = (recordLevel, scope, message) => {
|
|
19822
|
-
if (LEVEL_ORDER[recordLevel] < threshold) return;
|
|
19823
|
-
const record2 = { at: now(), level: recordLevel, scope, message };
|
|
19824
|
-
if (fd !== null) {
|
|
19825
|
-
try {
|
|
19826
|
-
const line = formatDiskLine(record2);
|
|
19827
|
-
if (written + line.length > maxBytes) {
|
|
19828
|
-
writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
|
|
19829
|
-
`);
|
|
19830
|
-
fd = null;
|
|
19831
|
-
} else {
|
|
19832
|
-
writeSync(fd, line);
|
|
19833
|
-
written += line.length;
|
|
19834
|
-
}
|
|
19835
|
-
} catch {
|
|
19836
|
-
fd = null;
|
|
19837
|
-
}
|
|
19838
|
-
}
|
|
19839
|
-
for (const listener of listeners) {
|
|
19840
|
-
try {
|
|
19841
|
-
listener(record2);
|
|
19842
|
-
} catch {
|
|
19843
|
-
}
|
|
19844
|
-
}
|
|
19845
|
-
};
|
|
19846
|
-
return {
|
|
19847
|
-
debug: (scope, message) => emit("debug", scope, message),
|
|
19848
|
-
info: (scope, message) => emit("info", scope, message),
|
|
19849
|
-
warn: (scope, message) => emit("warn", scope, message),
|
|
19850
|
-
error: (scope, message) => emit("error", scope, message),
|
|
19851
|
-
get file() {
|
|
19852
|
-
return fd === null ? null : path;
|
|
19853
|
-
},
|
|
19854
|
-
onRecord(listener) {
|
|
19855
|
-
listeners.add(listener);
|
|
19856
|
-
return () => listeners.delete(listener);
|
|
19857
|
-
},
|
|
19858
|
-
close() {
|
|
19859
|
-
listeners.clear();
|
|
19860
|
-
fd = null;
|
|
19861
|
-
}
|
|
19862
|
-
};
|
|
19863
|
-
}
|
|
19864
|
-
function setAmbientLogger(logger2) {
|
|
19865
|
-
ambient = logger2;
|
|
19866
|
-
return logger2;
|
|
19867
|
-
}
|
|
19868
|
-
function formatFeedLine(record2) {
|
|
19869
|
-
return formatDiskLine(record2).trimEnd();
|
|
19870
|
-
}
|
|
19871
|
-
function attachTerminalMirror(logger2, enabled = process.env.FRIZZ_DEBUG === "1") {
|
|
19872
|
-
if (!enabled) return () => {
|
|
19873
|
-
};
|
|
19874
|
-
return logger2.onRecord((record2) => process.stderr.write(`${formatFeedLine(record2)}
|
|
19875
|
-
`));
|
|
19876
|
-
}
|
|
19877
|
-
function ambientLogger() {
|
|
19878
|
-
if (ambient) return ambient;
|
|
19879
|
-
const inherited = process.env[LOG_FILE_ENV] ?? process.env[LOG_PATH_ENV];
|
|
19880
|
-
const level = process.env[LOG_LEVEL_ENV];
|
|
19881
|
-
const logger2 = createLogger({
|
|
19882
|
-
...inherited ? { file: inherited, owner: false } : { file: null },
|
|
19883
|
-
...level && level in LEVEL_ORDER ? { level } : {}
|
|
19884
|
-
});
|
|
19885
|
-
attachTerminalMirror(logger2);
|
|
19886
|
-
ambient = logger2;
|
|
19887
|
-
return ambient;
|
|
19888
|
-
}
|
|
19889
|
-
function logEnvironment(logger2, level = "debug") {
|
|
19890
|
-
return logger2.file ? { [LOG_FILE_ENV]: logger2.file, [LOG_LEVEL_ENV]: level } : {};
|
|
19891
|
-
}
|
|
19892
|
-
var LEVEL_ORDER, LOG_FILE_ENV, LOG_LEVEL_ENV, LOG_PATH_ENV, RETAINED_RUNS, RETAINED_DAYS, MAX_LOG_BYTES, ambient, log;
|
|
19893
|
-
var init_logging = __esm({
|
|
19894
|
-
"packages/server/src/logging.ts"() {
|
|
19895
|
-
"use strict";
|
|
19896
|
-
init_frizz_paths();
|
|
19897
|
-
LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
19898
|
-
LOG_FILE_ENV = "FRIZZ_LOG_FILE";
|
|
19899
|
-
LOG_LEVEL_ENV = "FRIZZ_LOG_LEVEL";
|
|
19900
|
-
LOG_PATH_ENV = "FRIZZ_LOG_PATH";
|
|
19901
|
-
RETAINED_RUNS = 20;
|
|
19902
|
-
RETAINED_DAYS = 14;
|
|
19903
|
-
MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
19904
|
-
ambient = null;
|
|
19905
|
-
log = {
|
|
19906
|
-
debug: (scope, message) => ambientLogger().debug(scope, message),
|
|
19907
|
-
info: (scope, message) => ambientLogger().info(scope, message),
|
|
19908
|
-
warn: (scope, message) => ambientLogger().warn(scope, message),
|
|
19909
|
-
error: (scope, message) => ambientLogger().error(scope, message)
|
|
19910
|
-
};
|
|
19911
|
-
}
|
|
19912
|
-
});
|
|
19913
|
-
|
|
19914
19977
|
// packages/server/src/process-generation.ts
|
|
19915
19978
|
import { execFileSync } from "node:child_process";
|
|
19916
19979
|
import { randomUUID } from "node:crypto";
|
|
@@ -20946,7 +21009,7 @@ var init_boot_progress = __esm({
|
|
|
20946
21009
|
|
|
20947
21010
|
// packages/server/src/project-root.ts
|
|
20948
21011
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
20949
|
-
import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync3, mkdirSync as mkdirSync8, openSync as openSync4, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
21012
|
+
import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync3, mkdirSync as mkdirSync8, openSync as openSync4, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
20950
21013
|
import { homedir as homedir8 } from "node:os";
|
|
20951
21014
|
import { dirname as dirname8, join as join11, parse, resolve as resolve3 } from "node:path";
|
|
20952
21015
|
function projectIdPath(root) {
|
|
@@ -21040,6 +21103,17 @@ function isHomeDirectory(dir, home = homedir8()) {
|
|
|
21040
21103
|
return false;
|
|
21041
21104
|
}
|
|
21042
21105
|
}
|
|
21106
|
+
function isWorldWritable(dir, platform = process.platform) {
|
|
21107
|
+
if (platform === "win32") return false;
|
|
21108
|
+
try {
|
|
21109
|
+
return (statSync5(dir).mode & 2) !== 0;
|
|
21110
|
+
} catch {
|
|
21111
|
+
return false;
|
|
21112
|
+
}
|
|
21113
|
+
}
|
|
21114
|
+
function refuseWorldWritableProject(dir) {
|
|
21115
|
+
if (isWorldWritable(dir)) throw new Error(`A folder every account can write to cannot be a project: ${dir}`);
|
|
21116
|
+
}
|
|
21043
21117
|
function existingProjectId(dir) {
|
|
21044
21118
|
const fromFile = readProjectIdFile(dir);
|
|
21045
21119
|
if (fromFile) return fromFile;
|
|
@@ -21115,11 +21189,13 @@ function readRegistry(home = homedir9()) {
|
|
|
21115
21189
|
try {
|
|
21116
21190
|
const parsed = JSON.parse(raw);
|
|
21117
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") : [];
|
|
21118
21193
|
return {
|
|
21119
21194
|
version: 1,
|
|
21120
21195
|
projects: parsed.projects.filter(
|
|
21121
21196
|
(p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string"
|
|
21122
|
-
)
|
|
21197
|
+
),
|
|
21198
|
+
...forgotten.length > 0 ? { forgotten } : {}
|
|
21123
21199
|
};
|
|
21124
21200
|
} catch {
|
|
21125
21201
|
return { ...EMPTY, projects: [] };
|
|
@@ -21183,7 +21259,12 @@ function canonicalPath(dir) {
|
|
|
21183
21259
|
}
|
|
21184
21260
|
function registerProject(input, home = homedir9()) {
|
|
21185
21261
|
input = { ...input, dir: canonicalPath(input.dir) };
|
|
21262
|
+
if (input.dir === canonicalPath(launchHostDir(home))) return { action: "unlisted" };
|
|
21186
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
|
+
}
|
|
21187
21268
|
const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
21188
21269
|
const byId = registry.projects.find((p) => p.id === input.id);
|
|
21189
21270
|
if (byId) {
|
|
@@ -23699,27 +23780,27 @@ function createAccessPane(options2) {
|
|
|
23699
23780
|
}
|
|
23700
23781
|
|
|
23701
23782
|
// src/remote-controller.ts
|
|
23702
|
-
import { homedir as
|
|
23783
|
+
import { homedir as homedir6 } from "node:os";
|
|
23703
23784
|
|
|
23704
23785
|
// src/cloud.ts
|
|
23705
23786
|
init_frizz_paths();
|
|
23706
23787
|
init_src();
|
|
23707
23788
|
import { spawn as spawn2 } from "node:child_process";
|
|
23708
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
23789
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
23709
23790
|
import { createInterface } from "node:readline/promises";
|
|
23710
|
-
import { homedir as
|
|
23711
|
-
import { dirname as dirname4, join as
|
|
23791
|
+
import { homedir as homedir5 } from "node:os";
|
|
23792
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
23712
23793
|
|
|
23713
23794
|
// src/identity.ts
|
|
23714
23795
|
init_frizz_paths();
|
|
23715
23796
|
init_src();
|
|
23716
|
-
import { mkdirSync as
|
|
23717
|
-
import { homedir as
|
|
23718
|
-
import { dirname as dirname3, join as
|
|
23719
|
-
function claimIdentityPath(home =
|
|
23720
|
-
return
|
|
23797
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23798
|
+
import { homedir as homedir4 } from "node:os";
|
|
23799
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
23800
|
+
function claimIdentityPath(home = homedir4(), env) {
|
|
23801
|
+
return join5(frizzPaths({ home, env }).state, "identity.key");
|
|
23721
23802
|
}
|
|
23722
|
-
async function loadOrCreateClaimIdentity(home =
|
|
23803
|
+
async function loadOrCreateClaimIdentity(home = homedir4()) {
|
|
23723
23804
|
const path = claimIdentityPath(home);
|
|
23724
23805
|
let stored;
|
|
23725
23806
|
try {
|
|
@@ -23735,8 +23816,8 @@ async function loadOrCreateClaimIdentity(home = homedir3()) {
|
|
|
23735
23816
|
);
|
|
23736
23817
|
}
|
|
23737
23818
|
const identity = await generateClaimIdentity();
|
|
23738
|
-
|
|
23739
|
-
|
|
23819
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
23820
|
+
writeFileSync3(path, await exportClaimPrivateKey(identity.privateKey), { mode: 384 });
|
|
23740
23821
|
return identity;
|
|
23741
23822
|
}
|
|
23742
23823
|
|
|
@@ -24332,6 +24413,11 @@ function describeCloudConfig(config) {
|
|
|
24332
24413
|
return "Cloudflare Tunnel";
|
|
24333
24414
|
}
|
|
24334
24415
|
var REGISTRAR_IS_LIVE = true;
|
|
24416
|
+
var CUSTOM_NAMES_OFFERED = false;
|
|
24417
|
+
function customNamesOffered(env = process.env) {
|
|
24418
|
+
return CUSTOM_NAMES_OFFERED || env.FRIZZ_CUSTOM_NAMES === "1";
|
|
24419
|
+
}
|
|
24420
|
+
var CUSTOM_NAMES_PAUSED = "custom frizz.sh names are paused for now \u2014 press enter for a private name (no account needed), or answer with the hostname of a tunnel you run";
|
|
24335
24421
|
function isClaimedConfig(config) {
|
|
24336
24422
|
return typeof config.claim === "string" && config.claim.length > 0;
|
|
24337
24423
|
}
|
|
@@ -24345,19 +24431,24 @@ function zoneClaimLabel(hostname) {
|
|
|
24345
24431
|
const label = hostname.slice(0, -suffix.length);
|
|
24346
24432
|
return label.length > 0 && !label.includes(".") ? label : null;
|
|
24347
24433
|
}
|
|
24348
|
-
async function reconcileCloudConfig(config, port, home =
|
|
24434
|
+
async function reconcileCloudConfig(config, port, home = homedir5(), onNotice, origin, github = githubDeviceFlow(onNotice ? { onPrompt: (prompt) => onNotice(deviceCodeMessage(prompt)) } : {})) {
|
|
24349
24435
|
if (isRelayConfig(config)) return config;
|
|
24350
24436
|
const label = config.claim ?? zoneClaimLabel(config.hostname);
|
|
24351
24437
|
if (!label) return config;
|
|
24438
|
+
if (!isAnonymousClaimName(label) && !customNamesOffered()) {
|
|
24439
|
+
throw new Error(
|
|
24440
|
+
`${config.hostname} needs moving onto the Frizz relay, which means claiming it again, and custom frizz.sh names are paused for now \u2014 press R to pick a private name or another setup`
|
|
24441
|
+
);
|
|
24442
|
+
}
|
|
24352
24443
|
onNotice?.(
|
|
24353
24444
|
isClaimedConfig(config) ? `${config.hostname} is served by the Frizz relay now; moving this board off its tunnel` : `${config.hostname} is inside ${CLAIM_ZONE}, where names are claimed, not tunnelled; claiming ${label}`
|
|
24354
24445
|
);
|
|
24355
24446
|
return establishCloudConfig(label, port, home, origin, github);
|
|
24356
24447
|
}
|
|
24357
|
-
function cloudConfigPath(home =
|
|
24358
|
-
return
|
|
24448
|
+
function cloudConfigPath(home = homedir5()) {
|
|
24449
|
+
return join6(frizzPaths({ home }).data, "cloud.json");
|
|
24359
24450
|
}
|
|
24360
|
-
function readCloudConfig(home =
|
|
24451
|
+
function readCloudConfig(home = homedir5()) {
|
|
24361
24452
|
try {
|
|
24362
24453
|
const parsed = JSON.parse(readFileSync3(cloudConfigPath(home), "utf8"));
|
|
24363
24454
|
if (typeof parsed.hostname !== "string" || !parsed.hostname) return null;
|
|
@@ -24377,10 +24468,10 @@ function readCloudConfig(home = homedir4()) {
|
|
|
24377
24468
|
return null;
|
|
24378
24469
|
}
|
|
24379
24470
|
}
|
|
24380
|
-
function tunnelTokenPath(home =
|
|
24381
|
-
return
|
|
24471
|
+
function tunnelTokenPath(home = homedir5()) {
|
|
24472
|
+
return join6(frizzPaths({ home }).state, "tunnel-token");
|
|
24382
24473
|
}
|
|
24383
|
-
function readTunnelToken(home =
|
|
24474
|
+
function readTunnelToken(home = homedir5()) {
|
|
24384
24475
|
try {
|
|
24385
24476
|
const token = readFileSync3(tunnelTokenPath(home), "utf8").trim();
|
|
24386
24477
|
return token.length > 0 ? token : null;
|
|
@@ -24388,19 +24479,19 @@ function readTunnelToken(home = homedir4()) {
|
|
|
24388
24479
|
return null;
|
|
24389
24480
|
}
|
|
24390
24481
|
}
|
|
24391
|
-
function writeTunnelToken(token, home =
|
|
24482
|
+
function writeTunnelToken(token, home = homedir5()) {
|
|
24392
24483
|
const path = tunnelTokenPath(home);
|
|
24393
|
-
|
|
24394
|
-
|
|
24484
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
24485
|
+
writeFileSync4(path, `${token}
|
|
24395
24486
|
`, { mode: 384 });
|
|
24396
24487
|
}
|
|
24397
|
-
function deleteCloudConfig(home =
|
|
24398
|
-
|
|
24488
|
+
function deleteCloudConfig(home = homedir5()) {
|
|
24489
|
+
rmSync2(cloudConfigPath(home), { force: true });
|
|
24399
24490
|
}
|
|
24400
|
-
function writeCloudConfig(config, home =
|
|
24491
|
+
function writeCloudConfig(config, home = homedir5()) {
|
|
24401
24492
|
const path = cloudConfigPath(home);
|
|
24402
|
-
|
|
24403
|
-
|
|
24493
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
24494
|
+
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
24404
24495
|
`);
|
|
24405
24496
|
}
|
|
24406
24497
|
function normalizeHostname(raw) {
|
|
@@ -24420,7 +24511,7 @@ async function promptForTunnelName(hostname, fallback) {
|
|
|
24420
24511
|
rl.close();
|
|
24421
24512
|
}
|
|
24422
24513
|
}
|
|
24423
|
-
async function establishCloudConfig(answer, port, home =
|
|
24514
|
+
async function establishCloudConfig(answer, port, home = homedir5(), origin, github = githubDeviceFlow()) {
|
|
24424
24515
|
if (answer.includes(".")) {
|
|
24425
24516
|
const hostname = normalizeHostname(answer);
|
|
24426
24517
|
const tunnel = await promptForTunnelName(hostname, hostname.split(".")[0]);
|
|
@@ -24432,6 +24523,7 @@ async function establishCloudConfig(answer, port, home = homedir4(), origin, git
|
|
|
24432
24523
|
);
|
|
24433
24524
|
}
|
|
24434
24525
|
const anonymous = answer === "" || isAnonymousClaimName(answer);
|
|
24526
|
+
if (!anonymous && !customNamesOffered()) throw new Error(CUSTOM_NAMES_PAUSED);
|
|
24435
24527
|
if (!anonymous && !claimNameIsValid(answer)) {
|
|
24436
24528
|
normalizeClaimName(answer);
|
|
24437
24529
|
}
|
|
@@ -24464,7 +24556,7 @@ async function establishCloudConfig(answer, port, home = homedir4(), origin, git
|
|
|
24464
24556
|
}
|
|
24465
24557
|
return { hostname: result.hostname, claim: name, serve: "relay" };
|
|
24466
24558
|
}
|
|
24467
|
-
async function resolveRunToken(config, port, home =
|
|
24559
|
+
async function resolveRunToken(config, port, home = homedir5(), onWarning, origin) {
|
|
24468
24560
|
if (!isClaimedConfig(config)) return null;
|
|
24469
24561
|
const cached2 = readTunnelToken(home);
|
|
24470
24562
|
try {
|
|
@@ -24481,12 +24573,12 @@ async function resolveRunToken(config, port, home = homedir4(), onWarning, origi
|
|
|
24481
24573
|
throw error;
|
|
24482
24574
|
}
|
|
24483
24575
|
}
|
|
24484
|
-
function resolveTunnelConfigPath(config, home =
|
|
24576
|
+
function resolveTunnelConfigPath(config, home = homedir5()) {
|
|
24485
24577
|
if (config.config) return config.config;
|
|
24486
|
-
const candidate =
|
|
24578
|
+
const candidate = join6(home, ".cloudflared", "frizz.yml");
|
|
24487
24579
|
return existsSync3(candidate) ? candidate : null;
|
|
24488
24580
|
}
|
|
24489
|
-
async function startRelay(config, port, home =
|
|
24581
|
+
async function startRelay(config, port, home = homedir5(), onStatus) {
|
|
24490
24582
|
const identity = await loadOrCreateClaimIdentity(home);
|
|
24491
24583
|
const origin = `https://${config.hostname}`;
|
|
24492
24584
|
return connectRelay({
|
|
@@ -24504,7 +24596,7 @@ async function startRelay(config, port, home = homedir4(), onStatus) {
|
|
|
24504
24596
|
} : {}
|
|
24505
24597
|
});
|
|
24506
24598
|
}
|
|
24507
|
-
function startTunnel(config, onExit, onError, home =
|
|
24599
|
+
function startTunnel(config, onExit, onError, home = homedir5(), runToken) {
|
|
24508
24600
|
const configPath = runToken ? null : resolveTunnelConfigPath(config, home);
|
|
24509
24601
|
const args2 = runToken ? ["tunnel", "--no-autoupdate", "run", "--token", runToken] : [
|
|
24510
24602
|
"tunnel",
|
|
@@ -24535,7 +24627,7 @@ function startTunnel(config, onExit, onError, home = homedir4(), runToken) {
|
|
|
24535
24627
|
|
|
24536
24628
|
// src/remote-controller.ts
|
|
24537
24629
|
function createRemoteController(options2) {
|
|
24538
|
-
const home = options2.home ??
|
|
24630
|
+
const home = options2.home ?? homedir6();
|
|
24539
24631
|
let config = null;
|
|
24540
24632
|
let transport = null;
|
|
24541
24633
|
const stopTransport = () => {
|
|
@@ -24650,6 +24742,15 @@ var CHOICES = [
|
|
|
24650
24742
|
{ kind: "other", title: "Something else", blurb: "any proxy or tunnel you run \u2014 tell Frizz its address" },
|
|
24651
24743
|
{ kind: "off", title: "Off", blurb: "loopback only" }
|
|
24652
24744
|
];
|
|
24745
|
+
function choicesFor(current) {
|
|
24746
|
+
if (customNamesOffered()) return CHOICES;
|
|
24747
|
+
if (kindOf(current) === "frizz") {
|
|
24748
|
+
return CHOICES.map(
|
|
24749
|
+
(choice) => choice.kind === "frizz" ? { ...choice, blurb: `${current.hostname} \u2014 kept and renewed; new custom names are paused` } : choice
|
|
24750
|
+
);
|
|
24751
|
+
}
|
|
24752
|
+
return CHOICES.filter((choice) => choice.kind !== "frizz");
|
|
24753
|
+
}
|
|
24653
24754
|
var ENTER = /* @__PURE__ */ new Set(["\r", "\n"]);
|
|
24654
24755
|
var BACKSPACE = /* @__PURE__ */ new Set(["\x7F", "\b"]);
|
|
24655
24756
|
var ESC = "\x1B";
|
|
@@ -24690,6 +24791,8 @@ function createRemotePane(options2) {
|
|
|
24690
24791
|
output.write(fitScreen([{ text: "", drop: 0 }, ...indented], output.rows, output.columns));
|
|
24691
24792
|
};
|
|
24692
24793
|
const check = (ok, text) => ok ? `${text} \u2713` : text;
|
|
24794
|
+
const choices = () => choicesFor(options2.current());
|
|
24795
|
+
const rowOf = (kind) => Math.max(0, choices().findIndex((choice) => choice.kind === kind));
|
|
24693
24796
|
const paint = () => {
|
|
24694
24797
|
if (!open) return;
|
|
24695
24798
|
const s = screen;
|
|
@@ -24702,7 +24805,7 @@ function createRemotePane(options2) {
|
|
|
24702
24805
|
"Frizz binds 127.0.0.1 and has no login. Reaching it from another device means something in front of it does the authenticating. Whatever you pick here is remembered; from then on a plain `npx frizz` serves it."
|
|
24703
24806
|
),
|
|
24704
24807
|
"",
|
|
24705
|
-
...
|
|
24808
|
+
...choices().map((choice, index) => {
|
|
24706
24809
|
const marker = index === s.index ? "\u276F" : " ";
|
|
24707
24810
|
const title = choice.title.padEnd(18);
|
|
24708
24811
|
const now = choice.kind === current ? ` ${DIM}(current)${RESET2}` : "";
|
|
@@ -24911,6 +25014,21 @@ function createRemotePane(options2) {
|
|
|
24911
25014
|
}
|
|
24912
25015
|
paint();
|
|
24913
25016
|
};
|
|
25017
|
+
const serveCurrent = async () => {
|
|
25018
|
+
const current = options2.current();
|
|
25019
|
+
const back = { name: "menu", index: rowOf("frizz") };
|
|
25020
|
+
if (!current) return;
|
|
25021
|
+
screen = { name: "busy", message: `serving ${current.hostname}\u2026` };
|
|
25022
|
+
paint();
|
|
25023
|
+
try {
|
|
25024
|
+
await options2.apply(current);
|
|
25025
|
+
options2.onChanged?.(current);
|
|
25026
|
+
screen = { name: "done", message: `Serving https://${current.hostname} (${describeCloudConfig(current)}).`, link: options2.issueLink(), config: current };
|
|
25027
|
+
} catch (error) {
|
|
25028
|
+
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back };
|
|
25029
|
+
}
|
|
25030
|
+
paint();
|
|
25031
|
+
};
|
|
24914
25032
|
const turnOff = async () => {
|
|
24915
25033
|
screen = { name: "busy", message: "back to loopback only\u2026" };
|
|
24916
25034
|
paint();
|
|
@@ -24919,15 +25037,14 @@ function createRemotePane(options2) {
|
|
|
24919
25037
|
options2.onChanged?.(null);
|
|
24920
25038
|
screen = { name: "done", message: "Loopback only. This board is reachable from this machine alone.", link: null, config: null };
|
|
24921
25039
|
} catch (error) {
|
|
24922
|
-
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back: { name: "menu", index:
|
|
25040
|
+
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back: { name: "menu", index: rowOf("off") } };
|
|
24923
25041
|
}
|
|
24924
25042
|
paint();
|
|
24925
25043
|
};
|
|
24926
25044
|
return {
|
|
24927
25045
|
open() {
|
|
24928
25046
|
open = true;
|
|
24929
|
-
|
|
24930
|
-
screen = { name: "menu", index: Math.max(0, CHOICES.findIndex((choice) => choice.kind === current)) };
|
|
25047
|
+
screen = { name: "menu", index: rowOf(kindOf(options2.current())) };
|
|
24931
25048
|
output.write(ALT_SCREEN_ON);
|
|
24932
25049
|
output.write(HIDE_CURSOR);
|
|
24933
25050
|
paint();
|
|
@@ -24950,13 +25067,15 @@ function createRemotePane(options2) {
|
|
|
24950
25067
|
}
|
|
24951
25068
|
if (s.name === "menu") {
|
|
24952
25069
|
if (key === ESC || key === "q") return "close";
|
|
24953
|
-
|
|
24954
|
-
|
|
24955
|
-
else if (
|
|
25070
|
+
const rows = choices();
|
|
25071
|
+
if (key === UP || key === "k") s.index = (s.index + rows.length - 1) % rows.length;
|
|
25072
|
+
else if (key === DOWN || key === "j") s.index = (s.index + 1) % rows.length;
|
|
25073
|
+
else if (/^[1-9]$/.test(key) && Number(key) <= rows.length) s.index = Number(key) - 1;
|
|
24956
25074
|
else if (ENTER.has(key)) {
|
|
24957
|
-
const choice =
|
|
25075
|
+
const choice = rows[Math.min(s.index, rows.length - 1)];
|
|
24958
25076
|
if (choice.kind === "off") void turnOff();
|
|
24959
25077
|
else if (choice.kind === "private") void claimPrivate();
|
|
25078
|
+
else if (choice.kind === "frizz" && !customNamesOffered()) void serveCurrent();
|
|
24960
25079
|
else openForm(choice.kind);
|
|
24961
25080
|
return "keep";
|
|
24962
25081
|
}
|
|
@@ -24964,7 +25083,7 @@ function createRemotePane(options2) {
|
|
|
24964
25083
|
return "keep";
|
|
24965
25084
|
}
|
|
24966
25085
|
if (key === ESC) {
|
|
24967
|
-
screen = { name: "menu", index:
|
|
25086
|
+
screen = { name: "menu", index: rowOf(s.kind) };
|
|
24968
25087
|
paint();
|
|
24969
25088
|
return "keep";
|
|
24970
25089
|
}
|
|
@@ -25339,6 +25458,7 @@ init_boot_progress();
|
|
|
25339
25458
|
init_frizz_paths();
|
|
25340
25459
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25341
25460
|
import { createServer as createServer2 } from "node:net";
|
|
25461
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
25342
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";
|
|
25343
25463
|
import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
|
|
25344
25464
|
import { basename as basename5, dirname as dirname10, join as join13, resolve as resolve4 } from "node:path";
|
|
@@ -25352,7 +25472,7 @@ var LAUNCH_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
|
|
|
25352
25472
|
var LAUNCH_HARD_TIMEOUT_MS = BOOT_HARD_TIMEOUT_MS;
|
|
25353
25473
|
var RETIRED_NETWORK_FLAGS = ["--host", "--allowed-host", "--public-origin", "--cloud"];
|
|
25354
25474
|
function retiredFlagMessage(flag) {
|
|
25355
|
-
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
|
|
25475
|
+
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 name, a Cloudflare Tunnel, Tailscale or a proxy of your own. The choice is remembered, so a plain launch serves it.`;
|
|
25356
25476
|
}
|
|
25357
25477
|
function parseCliArgs(argv) {
|
|
25358
25478
|
const args2 = new Set(argv);
|
|
@@ -25478,6 +25598,7 @@ function prepareSandbox(env = process.env, realHome = homedir10()) {
|
|
|
25478
25598
|
} catch {
|
|
25479
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");
|
|
25480
25600
|
}
|
|
25601
|
+
writeProjectIdFile(project, randomUUID5());
|
|
25481
25602
|
process.chdir(project);
|
|
25482
25603
|
return { home, project };
|
|
25483
25604
|
}
|
|
@@ -25521,16 +25642,23 @@ function cleanupSandbox(home) {
|
|
|
25521
25642
|
function resolveLaunchIntent(cwd = process.cwd(), home = homedir10(), env = process.env) {
|
|
25522
25643
|
const candidate = realpathSync5(discoverProjectRoot(cwd, home));
|
|
25523
25644
|
const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
const host = mostRecentProject(home, env);
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
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" };
|
|
25530
25658
|
}
|
|
25531
25659
|
function mostRecentProject(home, env) {
|
|
25532
25660
|
for (const entry of listProjects(home)) {
|
|
25533
|
-
if (entry.stale || isHomeDirectory(entry.path, home)) continue;
|
|
25661
|
+
if (entry.stale || isHomeDirectory(entry.path, home) || isWorldWritable(entry.path)) continue;
|
|
25534
25662
|
try {
|
|
25535
25663
|
return resolveWorkspace(entry.path, home, env);
|
|
25536
25664
|
} catch {
|
|
@@ -25553,6 +25681,7 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir10(), env = process
|
|
|
25553
25681
|
gitRoot = void 0;
|
|
25554
25682
|
}
|
|
25555
25683
|
const root0 = realpathSync5(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
25684
|
+
refuseWorldWritableProject(root0);
|
|
25556
25685
|
const identity = gitRoot ? resolveGitProjectIdentity(root0, home) : void 0;
|
|
25557
25686
|
const root = identity?.root ?? root0;
|
|
25558
25687
|
const id = ensureProjectIdFile(root, home, identity?.id);
|
|
@@ -25882,7 +26011,7 @@ init_restart_supervisor();
|
|
|
25882
26011
|
init_logging();
|
|
25883
26012
|
init_boot_progress();
|
|
25884
26013
|
import { fork } from "node:child_process";
|
|
25885
|
-
import { randomUUID as
|
|
26014
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
25886
26015
|
import { existsSync as existsSync7, mkdirSync as mkdirSync11, readFileSync as readFileSync11 } from "node:fs";
|
|
25887
26016
|
import { createServer as createNetServer } from "node:net";
|
|
25888
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";
|
|
@@ -26127,7 +26256,7 @@ var Supervisor = class {
|
|
|
26127
26256
|
watchSubscribe;
|
|
26128
26257
|
reexec;
|
|
26129
26258
|
supervisorLock;
|
|
26130
|
-
statusPublisherToken =
|
|
26259
|
+
statusPublisherToken = randomUUID6();
|
|
26131
26260
|
processGeneration = currentProcessGeneration();
|
|
26132
26261
|
ownerToken;
|
|
26133
26262
|
launchTarget;
|
|
@@ -27083,7 +27212,7 @@ var npmRegistryReleaseAdapter = createNpmRegistryReleaseAdapter();
|
|
|
27083
27212
|
// src/server-release.ts
|
|
27084
27213
|
init_frizz_paths();
|
|
27085
27214
|
import { execFile as execFile4 } from "node:child_process";
|
|
27086
|
-
import { createHash as createHash4, randomUUID as
|
|
27215
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
|
|
27087
27216
|
import {
|
|
27088
27217
|
closeSync as closeSync5,
|
|
27089
27218
|
existsSync as existsSync9,
|
|
@@ -27094,7 +27223,7 @@ import {
|
|
|
27094
27223
|
realpathSync as realpathSync6,
|
|
27095
27224
|
renameSync as renameSync6,
|
|
27096
27225
|
rmSync as rmSync9,
|
|
27097
|
-
statSync as
|
|
27226
|
+
statSync as statSync6,
|
|
27098
27227
|
writeFileSync as writeFileSync11
|
|
27099
27228
|
} from "node:fs";
|
|
27100
27229
|
import { delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute6, join as join15, relative as relative3, resolve as resolve6, sep as sep4 } from "node:path";
|
|
@@ -27138,7 +27267,7 @@ function containedFile(root, name, directory = false) {
|
|
|
27138
27267
|
const rel = relative3(realpathSync6(root), path);
|
|
27139
27268
|
if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel))
|
|
27140
27269
|
throw new Error(`Frizz server artifact escapes its package: ${name}`);
|
|
27141
|
-
const stat =
|
|
27270
|
+
const stat = statSync6(path);
|
|
27142
27271
|
if (directory ? !stat.isDirectory() : !stat.isFile()) throw new Error(`invalid Frizz server artifact: ${name}`);
|
|
27143
27272
|
return path;
|
|
27144
27273
|
}
|
|
@@ -27181,7 +27310,7 @@ function resolveNpmCli(env = process.env, executable = process.execPath) {
|
|
|
27181
27310
|
} catch {
|
|
27182
27311
|
}
|
|
27183
27312
|
}
|
|
27184
|
-
const found = candidates.find((path) => existsSync9(path) &&
|
|
27313
|
+
const found = candidates.find((path) => existsSync9(path) && statSync6(path).isFile());
|
|
27185
27314
|
if (!found) throw new Error("npm was not found beside Node or on PATH; install Node with npm to install Frizz server releases");
|
|
27186
27315
|
return resolve6(found);
|
|
27187
27316
|
}
|
|
@@ -27239,7 +27368,7 @@ function npmServerPackageInstaller(env = process.env) {
|
|
|
27239
27368
|
}
|
|
27240
27369
|
function atomicJson2(path, value) {
|
|
27241
27370
|
mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
|
|
27242
|
-
const temp = `${path}.${
|
|
27371
|
+
const temp = `${path}.${randomUUID7()}.tmp`;
|
|
27243
27372
|
try {
|
|
27244
27373
|
const fd = openSync6(temp, "wx", 384);
|
|
27245
27374
|
try {
|
|
@@ -27340,7 +27469,7 @@ var ServerReleaseStore = class {
|
|
|
27340
27469
|
const spec = { ...this.spec, version };
|
|
27341
27470
|
assertSpec(spec, this.compatibility);
|
|
27342
27471
|
mkdirSync12(this.generations, { recursive: true, mode: 448 });
|
|
27343
|
-
const id =
|
|
27472
|
+
const id = randomUUID7();
|
|
27344
27473
|
const staging = join15(this.generations, `${id}.staging`);
|
|
27345
27474
|
const destination = join15(this.generations, id);
|
|
27346
27475
|
mkdirSync12(staging, { mode: 448 });
|
|
@@ -27366,7 +27495,7 @@ var ServerReleaseStore = class {
|
|
|
27366
27495
|
// src/server-owner.ts
|
|
27367
27496
|
init_frizz_paths();
|
|
27368
27497
|
init_project_launch();
|
|
27369
|
-
import { randomUUID as
|
|
27498
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
27370
27499
|
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, realpathSync as realpathSync7 } from "node:fs";
|
|
27371
27500
|
import { join as join16 } from "node:path";
|
|
27372
27501
|
var STABLE_SERVER_OWNER_NAME = "frizz-server";
|
|
@@ -27411,7 +27540,7 @@ function wrapLease(lease) {
|
|
|
27411
27540
|
token: lease.token,
|
|
27412
27541
|
pid: lease.pid,
|
|
27413
27542
|
processStart: lease.processStart,
|
|
27414
|
-
publisherToken:
|
|
27543
|
+
publisherToken: randomUUID8()
|
|
27415
27544
|
};
|
|
27416
27545
|
}
|
|
27417
27546
|
function acquireStableServerOwner(roots) {
|
|
@@ -27542,10 +27671,11 @@ if (options.help) {
|
|
|
27542
27671
|
|
|
27543
27672
|
Usage: npx ${PACKAGE_NAME} [options]
|
|
27544
27673
|
|
|
27545
|
-
Run it
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
|
|
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.
|
|
27549
27679
|
|
|
27550
27680
|
Options:
|
|
27551
27681
|
--no-app print the URL without opening a browser
|
|
@@ -27563,7 +27693,7 @@ Options:
|
|
|
27563
27693
|
|
|
27564
27694
|
|
|
27565
27695
|
To reach the board from a phone or another machine, press R in the terminal running it: a short
|
|
27566
|
-
walkthrough sets up a private frizz.sh name (no account needed), a
|
|
27696
|
+
walkthrough sets up a private frizz.sh name (no account needed), a Cloudflare
|
|
27567
27697
|
Tunnel, Tailscale, or a proxy of your own, and
|
|
27568
27698
|
remembers the choice, so a plain launch serves it from then on. The board stays on loopback and
|
|
27569
27699
|
shows a single-use sign-in link as a QR; press L for a fresh one, or run --link from another shell.`
|
|
@@ -27596,10 +27726,6 @@ var workspace = (() => {
|
|
|
27596
27726
|
return workspaceFromLaunchTarget(pinned);
|
|
27597
27727
|
}
|
|
27598
27728
|
const intent = resolveLaunchIntent();
|
|
27599
|
-
if (intent.kind === "empty")
|
|
27600
|
-
throw new Error(
|
|
27601
|
-
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.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.`
|
|
27602
|
-
);
|
|
27603
27729
|
launchIntent = intent;
|
|
27604
27730
|
return intent.workspace;
|
|
27605
27731
|
} catch (error) {
|
|
@@ -27745,7 +27871,7 @@ async function openOrPrint(port, reused, path = "") {
|
|
|
27745
27871
|
if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
|
|
27746
27872
|
if (sessionNotice && !reused) warnings.push(sessionNotice);
|
|
27747
27873
|
if (!readout) {
|
|
27748
|
-
console.log(`${reused ? "reusing" : "started"} Frizz for ${workspace.root}`);
|
|
27874
|
+
console.log(`${reused ? "reusing" : "started"} Frizz${launchIntent && launchIntent.kind !== "open" ? "" : ` for ${workspace.root}`}`);
|
|
27749
27875
|
console.log(url);
|
|
27750
27876
|
if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
|
|
27751
27877
|
for (const warning of warnings) console.log(warning);
|
|
@@ -27756,7 +27882,9 @@ async function openOrPrint(port, reused, path = "") {
|
|
|
27756
27882
|
[
|
|
27757
27883
|
{ label: "Local", value: boardAddress(url), accent: true },
|
|
27758
27884
|
...publicOrigin ? [{ label: "Public", value: activeAccessLink?.url ?? `${publicOrigin}/`, accent: true }] : [],
|
|
27759
|
-
|
|
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)}` }],
|
|
27760
27888
|
...logger.file ? [{ label: "Logs", value: tildePath(logger.file, home) }] : []
|
|
27761
27889
|
],
|
|
27762
27890
|
reused ? (
|