frizz 0.13.8 → 0.13.10
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 +5 -5
- package/dist/frizz.js +536 -402
- 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,295 @@ 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 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
|
+
function xdg(env, name) {
|
|
871
|
+
const value = env[name];
|
|
872
|
+
return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
|
|
873
|
+
}
|
|
874
|
+
function insideHome(child, home) {
|
|
875
|
+
const norm = (value) => value.replace(/[\\/]+$/u, "").replaceAll("\\", "/").toLowerCase();
|
|
876
|
+
const parent = norm(home);
|
|
877
|
+
const inner = norm(child);
|
|
878
|
+
return inner === parent || inner.startsWith(`${parent}/`);
|
|
879
|
+
}
|
|
880
|
+
function windowsRoots(env, home) {
|
|
881
|
+
const local = env.LOCALAPPDATA && insideHome(env.LOCALAPPDATA, home) ? env.LOCALAPPDATA : join3(home, "AppData", "Local");
|
|
882
|
+
const base = join3(local, "Frizz");
|
|
883
|
+
return {
|
|
884
|
+
data: join3(base, "Data"),
|
|
885
|
+
state: join3(base, "State"),
|
|
886
|
+
cache: join3(base, "Cache")
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function darwinRoots(home) {
|
|
890
|
+
const support = join3(home, "Library", "Application Support", "Frizz");
|
|
891
|
+
return {
|
|
892
|
+
data: support,
|
|
893
|
+
// macOS has no state directory concept; Application Support is where this belongs, and the log
|
|
894
|
+
// files themselves already live under each project's own directory.
|
|
895
|
+
state: support,
|
|
896
|
+
cache: join3(home, "Library", "Caches", "Frizz")
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function xdgRoots(env, home) {
|
|
900
|
+
return {
|
|
901
|
+
data: join3(xdg(env, "XDG_DATA_HOME") ?? join3(home, ".local", "share"), "frizz"),
|
|
902
|
+
state: join3(xdg(env, "XDG_STATE_HOME") ?? join3(home, ".local", "state"), "frizz"),
|
|
903
|
+
cache: join3(xdg(env, "XDG_CACHE_HOME") ?? join3(home, ".cache"), "frizz")
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function frizzPaths(options2 = {}) {
|
|
907
|
+
const env = options2.env ?? process.env;
|
|
908
|
+
const platform = options2.platform ?? process.platform;
|
|
909
|
+
const home = options2.home ?? homedir2();
|
|
910
|
+
const exists = options2.exists ?? existsSync2;
|
|
911
|
+
const legacyRoot = legacyFrizzRoot(home);
|
|
912
|
+
if (isLegacyStateRoot(legacyRoot, exists)) {
|
|
913
|
+
return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
|
|
914
|
+
}
|
|
915
|
+
const explicit = {
|
|
916
|
+
data: xdg(env, "XDG_DATA_HOME"),
|
|
917
|
+
state: xdg(env, "XDG_STATE_HOME"),
|
|
918
|
+
cache: xdg(env, "XDG_CACHE_HOME")
|
|
919
|
+
};
|
|
920
|
+
const platformRoots = platform === "win32" ? windowsRoots(env, home) : platform === "darwin" ? darwinRoots(home) : xdgRoots(env, home);
|
|
921
|
+
return {
|
|
922
|
+
data: explicit.data ? join3(explicit.data, "frizz") : platformRoots.data,
|
|
923
|
+
state: explicit.state ? join3(explicit.state, "frizz") : platformRoots.state,
|
|
924
|
+
cache: explicit.cache ? join3(explicit.cache, "frizz") : platformRoots.cache,
|
|
925
|
+
legacy: false
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function frizzRoots() {
|
|
929
|
+
return cached ??= frizzPaths();
|
|
930
|
+
}
|
|
931
|
+
function projectStateDir(projectId, home) {
|
|
932
|
+
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
|
|
933
|
+
}
|
|
934
|
+
var LEGACY_DIR_NAME, cached;
|
|
935
|
+
var init_frizz_paths = __esm({
|
|
936
|
+
"packages/server/src/frizz-paths.ts"() {
|
|
937
|
+
"use strict";
|
|
938
|
+
LEGACY_DIR_NAME = ".frizz";
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// packages/server/src/logging.ts
|
|
943
|
+
import {
|
|
944
|
+
appendFileSync,
|
|
945
|
+
mkdirSync as mkdirSync2,
|
|
946
|
+
openSync,
|
|
947
|
+
readdirSync,
|
|
948
|
+
rmSync,
|
|
949
|
+
statSync as statSync2,
|
|
950
|
+
symlinkSync,
|
|
951
|
+
writeFileSync as writeFileSync2,
|
|
952
|
+
writeSync
|
|
953
|
+
} from "node:fs";
|
|
954
|
+
import { homedir as homedir3 } from "node:os";
|
|
955
|
+
import { join as join4 } from "node:path";
|
|
956
|
+
function defaultLogRoot(stateDir, home = homedir3()) {
|
|
957
|
+
return stateDir ? join4(stateDir, "logs") : join4(frizzPaths({ home }).state, "logs");
|
|
958
|
+
}
|
|
959
|
+
function runStamp(at) {
|
|
960
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
961
|
+
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
|
|
962
|
+
}
|
|
963
|
+
function runLogPath(stateDir, at = /* @__PURE__ */ new Date(), pid = process.pid, home = homedir3(), env = process.env) {
|
|
964
|
+
const name = `frizz-${runStamp(at)}-${pid}.log`;
|
|
965
|
+
const override = env[LOG_PATH_ENV]?.trim();
|
|
966
|
+
if (override) return override.endsWith(".log") ? override : join4(override, name);
|
|
967
|
+
return join4(defaultLogRoot(stateDir, home), name);
|
|
968
|
+
}
|
|
969
|
+
function latestLogPath(dir) {
|
|
970
|
+
return join4(dir, "latest.log");
|
|
971
|
+
}
|
|
972
|
+
function linkLatest(dir, target2) {
|
|
973
|
+
const link = latestLogPath(dir);
|
|
974
|
+
try {
|
|
975
|
+
rmSync(link, { force: true });
|
|
976
|
+
symlinkSync(target2, link);
|
|
977
|
+
} catch {
|
|
978
|
+
try {
|
|
979
|
+
writeFileSync2(link, `${target2}
|
|
980
|
+
`, { mode: 384 });
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Date.now()) {
|
|
986
|
+
let entries;
|
|
987
|
+
try {
|
|
988
|
+
entries = readdirSync(dir).filter((name) => name.endsWith(".log") && name !== "latest.log");
|
|
989
|
+
} catch {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const dated = entries.map((name) => {
|
|
993
|
+
try {
|
|
994
|
+
return { name, at: statSync2(join4(dir, name)).mtimeMs };
|
|
995
|
+
} catch {
|
|
996
|
+
return { name, at: 0 };
|
|
997
|
+
}
|
|
998
|
+
}).sort((a, b) => b.at - a.at);
|
|
999
|
+
const cutoff = now - days * 24 * 60 * 60 * 1e3;
|
|
1000
|
+
const stale = dated.filter((entry, index) => index >= keep || entry.at < cutoff);
|
|
1001
|
+
for (const entry of stale) {
|
|
1002
|
+
try {
|
|
1003
|
+
rmSync(join4(dir, entry.name), { force: true });
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function formatDiskLine(record2) {
|
|
1009
|
+
const at = new Date(record2.at);
|
|
1010
|
+
const pad = (value, width = 2) => String(value).padStart(width, "0");
|
|
1011
|
+
const clock = `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}.${pad(at.getMilliseconds(), 3)}`;
|
|
1012
|
+
return `${clock} ${record2.level.toUpperCase().padEnd(5)} ${record2.scope.padEnd(12)} ${record2.message}
|
|
1013
|
+
`;
|
|
1014
|
+
}
|
|
1015
|
+
function openLogFile(path) {
|
|
1016
|
+
try {
|
|
1017
|
+
mkdirSync2(join4(path, ".."), { recursive: true, mode: 448 });
|
|
1018
|
+
return openSync(path, "a", 384);
|
|
1019
|
+
} catch {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function createLogger(options2 = {}) {
|
|
1024
|
+
const path = options2.file === null ? null : options2.file ?? runLogPath(options2.stateDir);
|
|
1025
|
+
const level = options2.level ?? "debug";
|
|
1026
|
+
const now = options2.now ?? Date.now;
|
|
1027
|
+
const threshold = LEVEL_ORDER[level];
|
|
1028
|
+
const maxBytes = options2.maxBytes ?? MAX_LOG_BYTES;
|
|
1029
|
+
let fd = path === null ? null : openLogFile(path);
|
|
1030
|
+
if (fd !== null && path !== null && options2.owner !== false) {
|
|
1031
|
+
const dir = join4(path, "..");
|
|
1032
|
+
pruneRunLogs(dir);
|
|
1033
|
+
linkLatest(dir, path);
|
|
1034
|
+
}
|
|
1035
|
+
let written = 0;
|
|
1036
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1037
|
+
const emit = (recordLevel, scope, message) => {
|
|
1038
|
+
if (LEVEL_ORDER[recordLevel] < threshold) return;
|
|
1039
|
+
const record2 = { at: now(), level: recordLevel, scope, message };
|
|
1040
|
+
if (fd !== null) {
|
|
1041
|
+
try {
|
|
1042
|
+
const line = formatDiskLine(record2);
|
|
1043
|
+
if (written + line.length > maxBytes) {
|
|
1044
|
+
writeSync(fd, `--- log truncated at ${maxBytes} bytes ---
|
|
1045
|
+
`);
|
|
1046
|
+
fd = null;
|
|
1047
|
+
} else {
|
|
1048
|
+
writeSync(fd, line);
|
|
1049
|
+
written += line.length;
|
|
1050
|
+
}
|
|
1051
|
+
} catch {
|
|
1052
|
+
fd = null;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
for (const listener of listeners) {
|
|
1056
|
+
try {
|
|
1057
|
+
listener(record2);
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
debug: (scope, message) => emit("debug", scope, message),
|
|
1064
|
+
info: (scope, message) => emit("info", scope, message),
|
|
1065
|
+
warn: (scope, message) => emit("warn", scope, message),
|
|
1066
|
+
error: (scope, message) => emit("error", scope, message),
|
|
1067
|
+
get file() {
|
|
1068
|
+
return fd === null ? null : path;
|
|
1069
|
+
},
|
|
1070
|
+
onRecord(listener) {
|
|
1071
|
+
listeners.add(listener);
|
|
1072
|
+
return () => listeners.delete(listener);
|
|
1073
|
+
},
|
|
1074
|
+
close() {
|
|
1075
|
+
listeners.clear();
|
|
1076
|
+
fd = null;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
function setAmbientLogger(logger2) {
|
|
1081
|
+
ambient = logger2;
|
|
1082
|
+
return logger2;
|
|
1083
|
+
}
|
|
1084
|
+
function formatFeedLine(record2) {
|
|
1085
|
+
return formatDiskLine(record2).trimEnd();
|
|
1086
|
+
}
|
|
1087
|
+
function attachTerminalMirror(logger2, enabled = process.env.FRIZZ_DEBUG === "1") {
|
|
1088
|
+
if (!enabled) return () => {
|
|
1089
|
+
};
|
|
1090
|
+
return logger2.onRecord((record2) => process.stderr.write(`${formatFeedLine(record2)}
|
|
1091
|
+
`));
|
|
1092
|
+
}
|
|
1093
|
+
function ambientLogger() {
|
|
1094
|
+
if (ambient) return ambient;
|
|
1095
|
+
const inherited = process.env[LOG_FILE_ENV] ?? process.env[LOG_PATH_ENV];
|
|
1096
|
+
const level = process.env[LOG_LEVEL_ENV];
|
|
1097
|
+
const logger2 = createLogger({
|
|
1098
|
+
...inherited ? { file: inherited, owner: false } : { file: null },
|
|
1099
|
+
...level && level in LEVEL_ORDER ? { level } : {}
|
|
1100
|
+
});
|
|
1101
|
+
attachTerminalMirror(logger2);
|
|
1102
|
+
ambient = logger2;
|
|
1103
|
+
return ambient;
|
|
1104
|
+
}
|
|
1105
|
+
function logEnvironment(logger2, level = "debug") {
|
|
1106
|
+
return logger2.file ? { [LOG_FILE_ENV]: logger2.file, [LOG_LEVEL_ENV]: level } : {};
|
|
1107
|
+
}
|
|
1108
|
+
var LEVEL_ORDER, LOG_FILE_ENV, LOG_LEVEL_ENV, LOG_PATH_ENV, RETAINED_RUNS, RETAINED_DAYS, MAX_LOG_BYTES, ambient, log;
|
|
1109
|
+
var init_logging = __esm({
|
|
1110
|
+
"packages/server/src/logging.ts"() {
|
|
1111
|
+
"use strict";
|
|
1112
|
+
init_frizz_paths();
|
|
1113
|
+
LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
1114
|
+
LOG_FILE_ENV = "FRIZZ_LOG_FILE";
|
|
1115
|
+
LOG_LEVEL_ENV = "FRIZZ_LOG_LEVEL";
|
|
1116
|
+
LOG_PATH_ENV = "FRIZZ_LOG_PATH";
|
|
1117
|
+
RETAINED_RUNS = 20;
|
|
1118
|
+
RETAINED_DAYS = 14;
|
|
1119
|
+
MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
1120
|
+
ambient = null;
|
|
1121
|
+
log = {
|
|
1122
|
+
debug: (scope, message) => ambientLogger().debug(scope, message),
|
|
1123
|
+
info: (scope, message) => ambientLogger().info(scope, message),
|
|
1124
|
+
warn: (scope, message) => ambientLogger().warn(scope, message),
|
|
1125
|
+
error: (scope, message) => ambientLogger().error(scope, message)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// packages/server/src/audit.ts
|
|
1131
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1132
|
+
function encodeCallerStamp(stamp) {
|
|
1133
|
+
return Buffer.from(JSON.stringify(stamp)).toString("base64url");
|
|
1134
|
+
}
|
|
1135
|
+
var CALLER_HEADER, requestOrigin;
|
|
1136
|
+
var init_audit = __esm({
|
|
1137
|
+
"packages/server/src/audit.ts"() {
|
|
1138
|
+
"use strict";
|
|
1139
|
+
init_access_codes();
|
|
1140
|
+
init_frizz_paths();
|
|
1141
|
+
init_logging();
|
|
1142
|
+
CALLER_HEADER = "x-frizz-caller";
|
|
1143
|
+
requestOrigin = new AsyncLocalStorage();
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
|
|
849
1147
|
// node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
850
1148
|
var require_identity = __commonJS({
|
|
851
1149
|
"node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -12982,31 +13280,6 @@ var init_drainable_worker = __esm({
|
|
|
12982
13280
|
}
|
|
12983
13281
|
});
|
|
12984
13282
|
|
|
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
13283
|
// packages/shared/src/receipt-bus.ts
|
|
13011
13284
|
var init_receipt_bus = __esm({
|
|
13012
13285
|
"packages/shared/src/receipt-bus.ts"() {
|
|
@@ -13254,7 +13527,7 @@ function requireEffortOutsideAcp(profile, ctx) {
|
|
|
13254
13527
|
function fallbackPort(base) {
|
|
13255
13528
|
return base + 1e4;
|
|
13256
13529
|
}
|
|
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;
|
|
13530
|
+
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
13531
|
var init_src = __esm({
|
|
13259
13532
|
"packages/shared/src/index.ts"() {
|
|
13260
13533
|
"use strict";
|
|
@@ -13267,7 +13540,6 @@ var init_src = __esm({
|
|
|
13267
13540
|
init_code_fences();
|
|
13268
13541
|
init_delta();
|
|
13269
13542
|
init_drainable_worker();
|
|
13270
|
-
init_frizz_md();
|
|
13271
13543
|
init_interactions();
|
|
13272
13544
|
init_receipt_bus();
|
|
13273
13545
|
init_relay_protocol();
|
|
@@ -14566,6 +14838,19 @@ var init_src = __esm({
|
|
|
14566
14838
|
// ambiguous, and only the operator decides to risk a second copy.
|
|
14567
14839
|
supersedes: external_exports.string().min(1).max(200).optional()
|
|
14568
14840
|
});
|
|
14841
|
+
SteerThreadInput = external_exports.object({
|
|
14842
|
+
from: ThreadSlug,
|
|
14843
|
+
to: ThreadSlug,
|
|
14844
|
+
message: external_exports.string().min(1).max(1e5)
|
|
14845
|
+
}).strict();
|
|
14846
|
+
SteerThreadResult = external_exports.object({
|
|
14847
|
+
slug: ThreadSlug,
|
|
14848
|
+
title: external_exports.string(),
|
|
14849
|
+
// What the target was doing when the message arrived: `running` ⇒ it reads it at its next tool
|
|
14850
|
+
// boundary; `resting` ⇒ the message started its next turn; `reopened` ⇒ it was marked done and this
|
|
14851
|
+
// message put it back on the board.
|
|
14852
|
+
delivery: external_exports.enum(["running", "resting", "reopened"])
|
|
14853
|
+
}).strict();
|
|
14569
14854
|
DismissFailedFollowUpInput = external_exports.object({
|
|
14570
14855
|
slug: ThreadSlug,
|
|
14571
14856
|
deliveryId: external_exports.string().min(1).max(200)
|
|
@@ -15275,6 +15560,20 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15275
15560
|
// pairing, and `projectTranscriptPeerNames` uses this id to ask it. Never a drawer key on its own —
|
|
15276
15561
|
// that is `peerDispatchId`, which the same pass can also supply once this resolves.
|
|
15277
15562
|
peerSenderTaskId: external_exports.string().optional(),
|
|
15563
|
+
// ANOTHER TOP-LEVEL SESSION wrote this user turn — a thread messaging this one through Claude Code's
|
|
15564
|
+
// cross-session channel (see parseCrossSessionMessage), most often the coordinator thread that spawned
|
|
15565
|
+
// it. Not the human, and not a sub-agent either: the chat draws it as an expandable hairline naming
|
|
15566
|
+
// the sending thread, with the body one click away. `displayText` carries the unwrapped body.
|
|
15567
|
+
//
|
|
15568
|
+
// `name` is the sender's Claude Code session name, the fallback label. `thread` is the Frizz thread
|
|
15569
|
+
// behind it, filled in by the server from the sender's socket (session-peer.ts) — absent when the
|
|
15570
|
+
// sender is not a Frizz thread or is gone without having been resolved, and then the line names the
|
|
15571
|
+
// session without a link. `project` is the URL slug of the sender's project.
|
|
15572
|
+
sessionPeer: external_exports.object({
|
|
15573
|
+
name: external_exports.string().optional(),
|
|
15574
|
+
socket: external_exports.string().optional(),
|
|
15575
|
+
thread: external_exports.object({ project: external_exports.string().optional(), slug: external_exports.string(), title: external_exports.string() }).optional()
|
|
15576
|
+
}).optional(),
|
|
15278
15577
|
// FRIZZ REFUSED the ```awaiting fence this message ends in — it named something that is not running,
|
|
15279
15578
|
// or named nothing at all, or used a retired line kind (see `isParkCorrection`). The fence is not a
|
|
15280
15579
|
// park, so the chat draws nothing for it: an hourglass card with a park button asserts a wait that
|
|
@@ -15384,8 +15683,9 @@ function responseJson(res, status, value) {
|
|
|
15384
15683
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
15385
15684
|
res.end(JSON.stringify(value));
|
|
15386
15685
|
}
|
|
15387
|
-
function proxyHeaders(req, childPort, vouchSameOrigin = false) {
|
|
15686
|
+
function proxyHeaders(req, childPort, callerStamp, vouchSameOrigin = false) {
|
|
15388
15687
|
const headers = { ...req.headers };
|
|
15688
|
+
headers[CALLER_HEADER] = callerStamp;
|
|
15389
15689
|
headers.host = `127.0.0.1:${childPort}`;
|
|
15390
15690
|
if (typeof headers.origin === "string") headers.origin = `http://127.0.0.1:${childPort}`;
|
|
15391
15691
|
for (const name of FORWARDED_HEADERS) delete headers[name];
|
|
@@ -15402,6 +15702,15 @@ function readCookie(header, name) {
|
|
|
15402
15702
|
}
|
|
15403
15703
|
return void 0;
|
|
15404
15704
|
}
|
|
15705
|
+
function firstHeader(value) {
|
|
15706
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
15707
|
+
return first?.trim() || void 0;
|
|
15708
|
+
}
|
|
15709
|
+
function isLoopbackAddress(address) {
|
|
15710
|
+
if (!address) return false;
|
|
15711
|
+
const v4 = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
|
|
15712
|
+
return v4.startsWith("127.") || address === "::1";
|
|
15713
|
+
}
|
|
15405
15714
|
function isControlRequest(req) {
|
|
15406
15715
|
const url = new URL(req.url ?? "/", "http://frizz.invalid");
|
|
15407
15716
|
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 +15744,7 @@ var init_restart_supervisor = __esm({
|
|
|
15435
15744
|
init_local_origin();
|
|
15436
15745
|
init_local_image();
|
|
15437
15746
|
init_supervisor_pages();
|
|
15747
|
+
init_audit();
|
|
15438
15748
|
init_src();
|
|
15439
15749
|
SUPERVISOR_CONTROL_PREFIX = "/_frizz/control";
|
|
15440
15750
|
SUPERVISOR_RESTART_PATH = `${SUPERVISOR_CONTROL_PREFIX}/restart`;
|
|
@@ -15799,6 +16109,28 @@ var init_restart_supervisor = __esm({
|
|
|
15799
16109
|
if (!this.publicOrigin) return true;
|
|
15800
16110
|
return this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
15801
16111
|
}
|
|
16112
|
+
/**
|
|
16113
|
+
* What this proxy knows about who sent a request, for the application server's audit trail
|
|
16114
|
+
* (audit.ts). The session is named by its record id, never its value, and only once it verifies.
|
|
16115
|
+
*
|
|
16116
|
+
* `clientIp` is only ever taken from a request that arrived as the public origin, and it is that
|
|
16117
|
+
* origin's CLAIM: a relay or tunnel reaches this port from loopback, so the real visitor address
|
|
16118
|
+
* exists only in what the relay chose to forward (`cf-connecting-ip` from Cloudflare).
|
|
16119
|
+
*/
|
|
16120
|
+
callerStamp(req) {
|
|
16121
|
+
const remote2 = req.socket.remoteAddress ?? null;
|
|
16122
|
+
const via = this.arrivedPublicly(req) ? "public" : isLoopbackAddress(remote2) ? "loopback" : "network";
|
|
16123
|
+
const host = via === "loopback" ? void 0 : firstHeader(req.headers.host);
|
|
16124
|
+
const forwarded = firstHeader(req.headers["x-forwarded-for"])?.split(",")[0]?.trim();
|
|
16125
|
+
const clientIp = via === "public" ? firstHeader(req.headers["cf-connecting-ip"]) ?? firstHeader(req.headers["x-real-ip"]) ?? forwarded : void 0;
|
|
16126
|
+
return encodeCallerStamp({
|
|
16127
|
+
via,
|
|
16128
|
+
remote: remote2,
|
|
16129
|
+
...host ? { host: host.slice(0, 200) } : {},
|
|
16130
|
+
...clientIp ? { clientIp: clientIp.slice(0, 200) } : {},
|
|
16131
|
+
session: this.access.sessionIdOf(readCookie(req.headers.cookie, SESSION_COOKIE))
|
|
16132
|
+
});
|
|
16133
|
+
}
|
|
15802
16134
|
/** Did this request arrive through the public origin carrying a live session? Loopback never does. */
|
|
15803
16135
|
carriesRemoteSession(req) {
|
|
15804
16136
|
return this.arrivedPublicly(req) && this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
@@ -15898,134 +16230,55 @@ var init_restart_supervisor = __esm({
|
|
|
15898
16230
|
port: childPort,
|
|
15899
16231
|
method: req.method,
|
|
15900
16232
|
path: req.url,
|
|
15901
|
-
headers: proxyHeaders(req, childPort, this.vouchesSameOrigin(req))
|
|
16233
|
+
headers: proxyHeaders(req, childPort, this.callerStamp(req), this.vouchesSameOrigin(req))
|
|
15902
16234
|
}, (upstreamResponse) => {
|
|
15903
16235
|
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";
|
|
16236
|
+
upstreamResponse.pipe(res);
|
|
16237
|
+
});
|
|
16238
|
+
upstream.once("error", () => {
|
|
16239
|
+
if (res.headersSent) return res.destroy();
|
|
16240
|
+
res.writeHead(503, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
16241
|
+
res.end(recoveryPage(req.url ?? "/", "unreachable"));
|
|
16242
|
+
});
|
|
16243
|
+
req.pipe(upstream);
|
|
16244
|
+
}
|
|
16245
|
+
handleUpgrade(req, socket, head) {
|
|
16246
|
+
if (this.arrivedPublicly(req) && !this.sessionAccepted(req)) {
|
|
16247
|
+
rejectWebSocketUpgrade(socket, 401, "Unauthorized");
|
|
16248
|
+
return;
|
|
16249
|
+
}
|
|
16250
|
+
if (!this.authorityAccepted(req, false)) {
|
|
16251
|
+
socket.destroy();
|
|
16252
|
+
return;
|
|
16253
|
+
}
|
|
16254
|
+
const childPort = this.options.childPort();
|
|
16255
|
+
if (!childPort) {
|
|
16256
|
+
socket.destroy();
|
|
16257
|
+
return;
|
|
16258
|
+
}
|
|
16259
|
+
const upstream = connect(childPort, "127.0.0.1");
|
|
16260
|
+
for (const half of [socket, upstream]) {
|
|
16261
|
+
this.upgradedSockets.add(half);
|
|
16262
|
+
half.once("close", () => this.upgradedSockets.delete(half));
|
|
16263
|
+
}
|
|
16264
|
+
upstream.once("connect", () => {
|
|
16265
|
+
const headers = proxyHeaders(req, childPort, this.callerStamp(req));
|
|
16266
|
+
headers.connection = req.headers.connection ?? "Upgrade";
|
|
16267
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
|
|
16268
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
16269
|
+
if (value === void 0) continue;
|
|
16270
|
+
for (const entry of Array.isArray(value) ? value : [value]) lines.push(`${name}: ${entry}`);
|
|
16271
|
+
}
|
|
16272
|
+
upstream.write(`${lines.join("\r\n")}\r
|
|
16273
|
+
\r
|
|
16274
|
+
`);
|
|
16275
|
+
if (head.length > 0) upstream.write(head);
|
|
16276
|
+
socket.pipe(upstream).pipe(socket);
|
|
16277
|
+
});
|
|
16278
|
+
upstream.once("error", () => socket.destroy());
|
|
16279
|
+
socket.once("error", () => upstream.destroy());
|
|
16280
|
+
}
|
|
16281
|
+
};
|
|
16029
16282
|
}
|
|
16030
16283
|
});
|
|
16031
16284
|
|
|
@@ -19723,194 +19976,6 @@ var init_wrapper = __esm({
|
|
|
19723
19976
|
}
|
|
19724
19977
|
});
|
|
19725
19978
|
|
|
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
19979
|
// packages/server/src/process-generation.ts
|
|
19915
19980
|
import { execFileSync } from "node:child_process";
|
|
19916
19981
|
import { randomUUID } from "node:crypto";
|
|
@@ -20946,7 +21011,7 @@ var init_boot_progress = __esm({
|
|
|
20946
21011
|
|
|
20947
21012
|
// packages/server/src/project-root.ts
|
|
20948
21013
|
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";
|
|
21014
|
+
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
21015
|
import { homedir as homedir8 } from "node:os";
|
|
20951
21016
|
import { dirname as dirname8, join as join11, parse, resolve as resolve3 } from "node:path";
|
|
20952
21017
|
function projectIdPath(root) {
|
|
@@ -21040,6 +21105,24 @@ function isHomeDirectory(dir, home = homedir8()) {
|
|
|
21040
21105
|
return false;
|
|
21041
21106
|
}
|
|
21042
21107
|
}
|
|
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
|
+
function isWorldWritable(dir, platform = process.platform) {
|
|
21116
|
+
if (platform === "win32") return false;
|
|
21117
|
+
try {
|
|
21118
|
+
return (statSync5(dir).mode & 2) !== 0;
|
|
21119
|
+
} catch {
|
|
21120
|
+
return false;
|
|
21121
|
+
}
|
|
21122
|
+
}
|
|
21123
|
+
function refuseWorldWritableProject(dir) {
|
|
21124
|
+
if (isWorldWritable(dir)) throw new Error(`A folder every account can write to cannot be a project: ${dir}`);
|
|
21125
|
+
}
|
|
21043
21126
|
function existingProjectId(dir) {
|
|
21044
21127
|
const fromFile = readProjectIdFile(dir);
|
|
21045
21128
|
if (fromFile) return fromFile;
|
|
@@ -21065,7 +21148,7 @@ function discoverProjectRoot(cwd = process.cwd(), home = homedir8()) {
|
|
|
21065
21148
|
const stop = resolve3(home);
|
|
21066
21149
|
const filesystemRoot = parse(dir).root;
|
|
21067
21150
|
for (let at = dir; ; at = dirname8(at)) {
|
|
21068
|
-
if (at === stop || at === filesystemRoot) break;
|
|
21151
|
+
if (at === stop || at === filesystemRoot || isHomeDirectory(at, home)) break;
|
|
21069
21152
|
if (existsSync4(projectIdPath(at))) return at;
|
|
21070
21153
|
if (hasAny(at, REPO_MARKERS)) return at;
|
|
21071
21154
|
if (hasAny(at, PROJECT_MARKERS)) return at;
|
|
@@ -21077,6 +21160,7 @@ var FRIZZ_DIR, ID_FILE, SELF_IGNORE, REPO_MARKERS, PROJECT_MARKERS;
|
|
|
21077
21160
|
var init_project_root = __esm({
|
|
21078
21161
|
"packages/server/src/project-root.ts"() {
|
|
21079
21162
|
"use strict";
|
|
21163
|
+
init_frizz_paths();
|
|
21080
21164
|
init_project_identity();
|
|
21081
21165
|
FRIZZ_DIR = ".frizz";
|
|
21082
21166
|
ID_FILE = ".id";
|
|
@@ -21115,11 +21199,13 @@ function readRegistry(home = homedir9()) {
|
|
|
21115
21199
|
try {
|
|
21116
21200
|
const parsed = JSON.parse(raw);
|
|
21117
21201
|
if (parsed?.version !== 1 || !Array.isArray(parsed.projects)) return { ...EMPTY, projects: [] };
|
|
21202
|
+
const forgotten = Array.isArray(parsed.forgotten) ? parsed.forgotten.filter((id) => typeof id === "string") : [];
|
|
21118
21203
|
return {
|
|
21119
21204
|
version: 1,
|
|
21120
21205
|
projects: parsed.projects.filter(
|
|
21121
21206
|
(p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string"
|
|
21122
|
-
)
|
|
21207
|
+
),
|
|
21208
|
+
...forgotten.length > 0 ? { forgotten } : {}
|
|
21123
21209
|
};
|
|
21124
21210
|
} catch {
|
|
21125
21211
|
return { ...EMPTY, projects: [] };
|
|
@@ -21184,6 +21270,10 @@ function canonicalPath(dir) {
|
|
|
21184
21270
|
function registerProject(input, home = homedir9()) {
|
|
21185
21271
|
input = { ...input, dir: canonicalPath(input.dir) };
|
|
21186
21272
|
const registry = readRegistry(home);
|
|
21273
|
+
if (registry.forgotten) {
|
|
21274
|
+
registry.forgotten = registry.forgotten.filter((id) => id !== input.id);
|
|
21275
|
+
if (registry.forgotten.length === 0) delete registry.forgotten;
|
|
21276
|
+
}
|
|
21187
21277
|
const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
21188
21278
|
const byId = registry.projects.find((p) => p.id === input.id);
|
|
21189
21279
|
if (byId) {
|
|
@@ -23699,27 +23789,27 @@ function createAccessPane(options2) {
|
|
|
23699
23789
|
}
|
|
23700
23790
|
|
|
23701
23791
|
// src/remote-controller.ts
|
|
23702
|
-
import { homedir as
|
|
23792
|
+
import { homedir as homedir6 } from "node:os";
|
|
23703
23793
|
|
|
23704
23794
|
// src/cloud.ts
|
|
23705
23795
|
init_frizz_paths();
|
|
23706
23796
|
init_src();
|
|
23707
23797
|
import { spawn as spawn2 } from "node:child_process";
|
|
23708
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
23798
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
23709
23799
|
import { createInterface } from "node:readline/promises";
|
|
23710
|
-
import { homedir as
|
|
23711
|
-
import { dirname as dirname4, join as
|
|
23800
|
+
import { homedir as homedir5 } from "node:os";
|
|
23801
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
23712
23802
|
|
|
23713
23803
|
// src/identity.ts
|
|
23714
23804
|
init_frizz_paths();
|
|
23715
23805
|
init_src();
|
|
23716
|
-
import { mkdirSync as
|
|
23717
|
-
import { homedir as
|
|
23718
|
-
import { dirname as dirname3, join as
|
|
23719
|
-
function claimIdentityPath(home =
|
|
23720
|
-
return
|
|
23806
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23807
|
+
import { homedir as homedir4 } from "node:os";
|
|
23808
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
23809
|
+
function claimIdentityPath(home = homedir4(), env) {
|
|
23810
|
+
return join5(frizzPaths({ home, env }).state, "identity.key");
|
|
23721
23811
|
}
|
|
23722
|
-
async function loadOrCreateClaimIdentity(home =
|
|
23812
|
+
async function loadOrCreateClaimIdentity(home = homedir4()) {
|
|
23723
23813
|
const path = claimIdentityPath(home);
|
|
23724
23814
|
let stored;
|
|
23725
23815
|
try {
|
|
@@ -23735,8 +23825,8 @@ async function loadOrCreateClaimIdentity(home = homedir3()) {
|
|
|
23735
23825
|
);
|
|
23736
23826
|
}
|
|
23737
23827
|
const identity = await generateClaimIdentity();
|
|
23738
|
-
|
|
23739
|
-
|
|
23828
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
23829
|
+
writeFileSync3(path, await exportClaimPrivateKey(identity.privateKey), { mode: 384 });
|
|
23740
23830
|
return identity;
|
|
23741
23831
|
}
|
|
23742
23832
|
|
|
@@ -24332,6 +24422,11 @@ function describeCloudConfig(config) {
|
|
|
24332
24422
|
return "Cloudflare Tunnel";
|
|
24333
24423
|
}
|
|
24334
24424
|
var REGISTRAR_IS_LIVE = true;
|
|
24425
|
+
var CUSTOM_NAMES_OFFERED = false;
|
|
24426
|
+
function customNamesOffered(env = process.env) {
|
|
24427
|
+
return CUSTOM_NAMES_OFFERED || env.FRIZZ_CUSTOM_NAMES === "1";
|
|
24428
|
+
}
|
|
24429
|
+
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
24430
|
function isClaimedConfig(config) {
|
|
24336
24431
|
return typeof config.claim === "string" && config.claim.length > 0;
|
|
24337
24432
|
}
|
|
@@ -24345,19 +24440,24 @@ function zoneClaimLabel(hostname) {
|
|
|
24345
24440
|
const label = hostname.slice(0, -suffix.length);
|
|
24346
24441
|
return label.length > 0 && !label.includes(".") ? label : null;
|
|
24347
24442
|
}
|
|
24348
|
-
async function reconcileCloudConfig(config, port, home =
|
|
24443
|
+
async function reconcileCloudConfig(config, port, home = homedir5(), onNotice, origin, github = githubDeviceFlow(onNotice ? { onPrompt: (prompt) => onNotice(deviceCodeMessage(prompt)) } : {})) {
|
|
24349
24444
|
if (isRelayConfig(config)) return config;
|
|
24350
24445
|
const label = config.claim ?? zoneClaimLabel(config.hostname);
|
|
24351
24446
|
if (!label) return config;
|
|
24447
|
+
if (!isAnonymousClaimName(label) && !customNamesOffered()) {
|
|
24448
|
+
throw new Error(
|
|
24449
|
+
`${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`
|
|
24450
|
+
);
|
|
24451
|
+
}
|
|
24352
24452
|
onNotice?.(
|
|
24353
24453
|
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
24454
|
);
|
|
24355
24455
|
return establishCloudConfig(label, port, home, origin, github);
|
|
24356
24456
|
}
|
|
24357
|
-
function cloudConfigPath(home =
|
|
24358
|
-
return
|
|
24457
|
+
function cloudConfigPath(home = homedir5()) {
|
|
24458
|
+
return join6(frizzPaths({ home }).data, "cloud.json");
|
|
24359
24459
|
}
|
|
24360
|
-
function readCloudConfig(home =
|
|
24460
|
+
function readCloudConfig(home = homedir5()) {
|
|
24361
24461
|
try {
|
|
24362
24462
|
const parsed = JSON.parse(readFileSync3(cloudConfigPath(home), "utf8"));
|
|
24363
24463
|
if (typeof parsed.hostname !== "string" || !parsed.hostname) return null;
|
|
@@ -24377,10 +24477,10 @@ function readCloudConfig(home = homedir4()) {
|
|
|
24377
24477
|
return null;
|
|
24378
24478
|
}
|
|
24379
24479
|
}
|
|
24380
|
-
function tunnelTokenPath(home =
|
|
24381
|
-
return
|
|
24480
|
+
function tunnelTokenPath(home = homedir5()) {
|
|
24481
|
+
return join6(frizzPaths({ home }).state, "tunnel-token");
|
|
24382
24482
|
}
|
|
24383
|
-
function readTunnelToken(home =
|
|
24483
|
+
function readTunnelToken(home = homedir5()) {
|
|
24384
24484
|
try {
|
|
24385
24485
|
const token = readFileSync3(tunnelTokenPath(home), "utf8").trim();
|
|
24386
24486
|
return token.length > 0 ? token : null;
|
|
@@ -24388,19 +24488,19 @@ function readTunnelToken(home = homedir4()) {
|
|
|
24388
24488
|
return null;
|
|
24389
24489
|
}
|
|
24390
24490
|
}
|
|
24391
|
-
function writeTunnelToken(token, home =
|
|
24491
|
+
function writeTunnelToken(token, home = homedir5()) {
|
|
24392
24492
|
const path = tunnelTokenPath(home);
|
|
24393
|
-
|
|
24394
|
-
|
|
24493
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
24494
|
+
writeFileSync4(path, `${token}
|
|
24395
24495
|
`, { mode: 384 });
|
|
24396
24496
|
}
|
|
24397
|
-
function deleteCloudConfig(home =
|
|
24398
|
-
|
|
24497
|
+
function deleteCloudConfig(home = homedir5()) {
|
|
24498
|
+
rmSync2(cloudConfigPath(home), { force: true });
|
|
24399
24499
|
}
|
|
24400
|
-
function writeCloudConfig(config, home =
|
|
24500
|
+
function writeCloudConfig(config, home = homedir5()) {
|
|
24401
24501
|
const path = cloudConfigPath(home);
|
|
24402
|
-
|
|
24403
|
-
|
|
24502
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
24503
|
+
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
24404
24504
|
`);
|
|
24405
24505
|
}
|
|
24406
24506
|
function normalizeHostname(raw) {
|
|
@@ -24420,7 +24520,7 @@ async function promptForTunnelName(hostname, fallback) {
|
|
|
24420
24520
|
rl.close();
|
|
24421
24521
|
}
|
|
24422
24522
|
}
|
|
24423
|
-
async function establishCloudConfig(answer, port, home =
|
|
24523
|
+
async function establishCloudConfig(answer, port, home = homedir5(), origin, github = githubDeviceFlow()) {
|
|
24424
24524
|
if (answer.includes(".")) {
|
|
24425
24525
|
const hostname = normalizeHostname(answer);
|
|
24426
24526
|
const tunnel = await promptForTunnelName(hostname, hostname.split(".")[0]);
|
|
@@ -24432,6 +24532,7 @@ async function establishCloudConfig(answer, port, home = homedir4(), origin, git
|
|
|
24432
24532
|
);
|
|
24433
24533
|
}
|
|
24434
24534
|
const anonymous = answer === "" || isAnonymousClaimName(answer);
|
|
24535
|
+
if (!anonymous && !customNamesOffered()) throw new Error(CUSTOM_NAMES_PAUSED);
|
|
24435
24536
|
if (!anonymous && !claimNameIsValid(answer)) {
|
|
24436
24537
|
normalizeClaimName(answer);
|
|
24437
24538
|
}
|
|
@@ -24464,7 +24565,7 @@ async function establishCloudConfig(answer, port, home = homedir4(), origin, git
|
|
|
24464
24565
|
}
|
|
24465
24566
|
return { hostname: result.hostname, claim: name, serve: "relay" };
|
|
24466
24567
|
}
|
|
24467
|
-
async function resolveRunToken(config, port, home =
|
|
24568
|
+
async function resolveRunToken(config, port, home = homedir5(), onWarning, origin) {
|
|
24468
24569
|
if (!isClaimedConfig(config)) return null;
|
|
24469
24570
|
const cached2 = readTunnelToken(home);
|
|
24470
24571
|
try {
|
|
@@ -24481,12 +24582,12 @@ async function resolveRunToken(config, port, home = homedir4(), onWarning, origi
|
|
|
24481
24582
|
throw error;
|
|
24482
24583
|
}
|
|
24483
24584
|
}
|
|
24484
|
-
function resolveTunnelConfigPath(config, home =
|
|
24585
|
+
function resolveTunnelConfigPath(config, home = homedir5()) {
|
|
24485
24586
|
if (config.config) return config.config;
|
|
24486
|
-
const candidate =
|
|
24587
|
+
const candidate = join6(home, ".cloudflared", "frizz.yml");
|
|
24487
24588
|
return existsSync3(candidate) ? candidate : null;
|
|
24488
24589
|
}
|
|
24489
|
-
async function startRelay(config, port, home =
|
|
24590
|
+
async function startRelay(config, port, home = homedir5(), onStatus) {
|
|
24490
24591
|
const identity = await loadOrCreateClaimIdentity(home);
|
|
24491
24592
|
const origin = `https://${config.hostname}`;
|
|
24492
24593
|
return connectRelay({
|
|
@@ -24504,7 +24605,7 @@ async function startRelay(config, port, home = homedir4(), onStatus) {
|
|
|
24504
24605
|
} : {}
|
|
24505
24606
|
});
|
|
24506
24607
|
}
|
|
24507
|
-
function startTunnel(config, onExit, onError, home =
|
|
24608
|
+
function startTunnel(config, onExit, onError, home = homedir5(), runToken) {
|
|
24508
24609
|
const configPath = runToken ? null : resolveTunnelConfigPath(config, home);
|
|
24509
24610
|
const args2 = runToken ? ["tunnel", "--no-autoupdate", "run", "--token", runToken] : [
|
|
24510
24611
|
"tunnel",
|
|
@@ -24535,7 +24636,7 @@ function startTunnel(config, onExit, onError, home = homedir4(), runToken) {
|
|
|
24535
24636
|
|
|
24536
24637
|
// src/remote-controller.ts
|
|
24537
24638
|
function createRemoteController(options2) {
|
|
24538
|
-
const home = options2.home ??
|
|
24639
|
+
const home = options2.home ?? homedir6();
|
|
24539
24640
|
let config = null;
|
|
24540
24641
|
let transport = null;
|
|
24541
24642
|
const stopTransport = () => {
|
|
@@ -24650,6 +24751,15 @@ var CHOICES = [
|
|
|
24650
24751
|
{ kind: "other", title: "Something else", blurb: "any proxy or tunnel you run \u2014 tell Frizz its address" },
|
|
24651
24752
|
{ kind: "off", title: "Off", blurb: "loopback only" }
|
|
24652
24753
|
];
|
|
24754
|
+
function choicesFor(current) {
|
|
24755
|
+
if (customNamesOffered()) return CHOICES;
|
|
24756
|
+
if (kindOf(current) === "frizz") {
|
|
24757
|
+
return CHOICES.map(
|
|
24758
|
+
(choice) => choice.kind === "frizz" ? { ...choice, blurb: `${current.hostname} \u2014 kept and renewed; new custom names are paused` } : choice
|
|
24759
|
+
);
|
|
24760
|
+
}
|
|
24761
|
+
return CHOICES.filter((choice) => choice.kind !== "frizz");
|
|
24762
|
+
}
|
|
24653
24763
|
var ENTER = /* @__PURE__ */ new Set(["\r", "\n"]);
|
|
24654
24764
|
var BACKSPACE = /* @__PURE__ */ new Set(["\x7F", "\b"]);
|
|
24655
24765
|
var ESC = "\x1B";
|
|
@@ -24690,6 +24800,8 @@ function createRemotePane(options2) {
|
|
|
24690
24800
|
output.write(fitScreen([{ text: "", drop: 0 }, ...indented], output.rows, output.columns));
|
|
24691
24801
|
};
|
|
24692
24802
|
const check = (ok, text) => ok ? `${text} \u2713` : text;
|
|
24803
|
+
const choices = () => choicesFor(options2.current());
|
|
24804
|
+
const rowOf = (kind) => Math.max(0, choices().findIndex((choice) => choice.kind === kind));
|
|
24693
24805
|
const paint = () => {
|
|
24694
24806
|
if (!open) return;
|
|
24695
24807
|
const s = screen;
|
|
@@ -24702,7 +24814,7 @@ function createRemotePane(options2) {
|
|
|
24702
24814
|
"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
24815
|
),
|
|
24704
24816
|
"",
|
|
24705
|
-
...
|
|
24817
|
+
...choices().map((choice, index) => {
|
|
24706
24818
|
const marker = index === s.index ? "\u276F" : " ";
|
|
24707
24819
|
const title = choice.title.padEnd(18);
|
|
24708
24820
|
const now = choice.kind === current ? ` ${DIM}(current)${RESET2}` : "";
|
|
@@ -24911,6 +25023,21 @@ function createRemotePane(options2) {
|
|
|
24911
25023
|
}
|
|
24912
25024
|
paint();
|
|
24913
25025
|
};
|
|
25026
|
+
const serveCurrent = async () => {
|
|
25027
|
+
const current = options2.current();
|
|
25028
|
+
const back = { name: "menu", index: rowOf("frizz") };
|
|
25029
|
+
if (!current) return;
|
|
25030
|
+
screen = { name: "busy", message: `serving ${current.hostname}\u2026` };
|
|
25031
|
+
paint();
|
|
25032
|
+
try {
|
|
25033
|
+
await options2.apply(current);
|
|
25034
|
+
options2.onChanged?.(current);
|
|
25035
|
+
screen = { name: "done", message: `Serving https://${current.hostname} (${describeCloudConfig(current)}).`, link: options2.issueLink(), config: current };
|
|
25036
|
+
} catch (error) {
|
|
25037
|
+
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back };
|
|
25038
|
+
}
|
|
25039
|
+
paint();
|
|
25040
|
+
};
|
|
24914
25041
|
const turnOff = async () => {
|
|
24915
25042
|
screen = { name: "busy", message: "back to loopback only\u2026" };
|
|
24916
25043
|
paint();
|
|
@@ -24919,15 +25046,14 @@ function createRemotePane(options2) {
|
|
|
24919
25046
|
options2.onChanged?.(null);
|
|
24920
25047
|
screen = { name: "done", message: "Loopback only. This board is reachable from this machine alone.", link: null, config: null };
|
|
24921
25048
|
} catch (error) {
|
|
24922
|
-
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back: { name: "menu", index:
|
|
25049
|
+
screen = { name: "error", message: error instanceof Error ? error.message : String(error), back: { name: "menu", index: rowOf("off") } };
|
|
24923
25050
|
}
|
|
24924
25051
|
paint();
|
|
24925
25052
|
};
|
|
24926
25053
|
return {
|
|
24927
25054
|
open() {
|
|
24928
25055
|
open = true;
|
|
24929
|
-
|
|
24930
|
-
screen = { name: "menu", index: Math.max(0, CHOICES.findIndex((choice) => choice.kind === current)) };
|
|
25056
|
+
screen = { name: "menu", index: rowOf(kindOf(options2.current())) };
|
|
24931
25057
|
output.write(ALT_SCREEN_ON);
|
|
24932
25058
|
output.write(HIDE_CURSOR);
|
|
24933
25059
|
paint();
|
|
@@ -24950,13 +25076,15 @@ function createRemotePane(options2) {
|
|
|
24950
25076
|
}
|
|
24951
25077
|
if (s.name === "menu") {
|
|
24952
25078
|
if (key === ESC || key === "q") return "close";
|
|
24953
|
-
|
|
24954
|
-
|
|
24955
|
-
else if (
|
|
25079
|
+
const rows = choices();
|
|
25080
|
+
if (key === UP || key === "k") s.index = (s.index + rows.length - 1) % rows.length;
|
|
25081
|
+
else if (key === DOWN || key === "j") s.index = (s.index + 1) % rows.length;
|
|
25082
|
+
else if (/^[1-9]$/.test(key) && Number(key) <= rows.length) s.index = Number(key) - 1;
|
|
24956
25083
|
else if (ENTER.has(key)) {
|
|
24957
|
-
const choice =
|
|
25084
|
+
const choice = rows[Math.min(s.index, rows.length - 1)];
|
|
24958
25085
|
if (choice.kind === "off") void turnOff();
|
|
24959
25086
|
else if (choice.kind === "private") void claimPrivate();
|
|
25087
|
+
else if (choice.kind === "frizz" && !customNamesOffered()) void serveCurrent();
|
|
24960
25088
|
else openForm(choice.kind);
|
|
24961
25089
|
return "keep";
|
|
24962
25090
|
}
|
|
@@ -24964,7 +25092,7 @@ function createRemotePane(options2) {
|
|
|
24964
25092
|
return "keep";
|
|
24965
25093
|
}
|
|
24966
25094
|
if (key === ESC) {
|
|
24967
|
-
screen = { name: "menu", index:
|
|
25095
|
+
screen = { name: "menu", index: rowOf(s.kind) };
|
|
24968
25096
|
paint();
|
|
24969
25097
|
return "keep";
|
|
24970
25098
|
}
|
|
@@ -25352,7 +25480,7 @@ var LAUNCH_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
|
|
|
25352
25480
|
var LAUNCH_HARD_TIMEOUT_MS = BOOT_HARD_TIMEOUT_MS;
|
|
25353
25481
|
var RETIRED_NETWORK_FLAGS = ["--host", "--allowed-host", "--public-origin", "--cloud"];
|
|
25354
25482
|
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
|
|
25483
|
+
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
25484
|
}
|
|
25357
25485
|
function parseCliArgs(argv) {
|
|
25358
25486
|
const args2 = new Set(argv);
|
|
@@ -25518,19 +25646,26 @@ function cleanupSandbox(home) {
|
|
|
25518
25646
|
} catch {
|
|
25519
25647
|
}
|
|
25520
25648
|
}
|
|
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
|
+
}
|
|
25521
25652
|
function resolveLaunchIntent(cwd = process.cwd(), home = homedir10(), env = process.env) {
|
|
25522
25653
|
const candidate = realpathSync5(discoverProjectRoot(cwd, home));
|
|
25523
25654
|
const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
|
|
25524
|
-
|
|
25655
|
+
const isHome = isHomeDirectory(candidate, home);
|
|
25656
|
+
const refused = isWorldWritable(candidate) ? "world-writable" : isStateRootHome(candidate, home) ? "home" : void 0;
|
|
25657
|
+
if (!refused && (known || hasProjectMarker(candidate) && !isHome))
|
|
25525
25658
|
return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
|
|
25526
25659
|
const host = mostRecentProject(home, env);
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
25660
|
+
if (!host) {
|
|
25661
|
+
if (refused) return { kind: "empty", directory: candidate, reason: refused };
|
|
25662
|
+
return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
|
|
25663
|
+
}
|
|
25664
|
+
return refused || isHome ? { kind: "grid", workspace: host } : { kind: "offer", workspace: host, directory: candidate };
|
|
25530
25665
|
}
|
|
25531
25666
|
function mostRecentProject(home, env) {
|
|
25532
25667
|
for (const entry of listProjects(home)) {
|
|
25533
|
-
if (entry.stale ||
|
|
25668
|
+
if (entry.stale || isStateRootHome(entry.path, home) || isWorldWritable(entry.path)) continue;
|
|
25534
25669
|
try {
|
|
25535
25670
|
return resolveWorkspace(entry.path, home, env);
|
|
25536
25671
|
} catch {
|
|
@@ -25553,6 +25688,8 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir10(), env = process
|
|
|
25553
25688
|
gitRoot = void 0;
|
|
25554
25689
|
}
|
|
25555
25690
|
const root0 = realpathSync5(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
25691
|
+
refuseWorldWritableProject(root0);
|
|
25692
|
+
refuseStateRootHome(root0, home);
|
|
25556
25693
|
const identity = gitRoot ? resolveGitProjectIdentity(root0, home) : void 0;
|
|
25557
25694
|
const root = identity?.root ?? root0;
|
|
25558
25695
|
const id = ensureProjectIdFile(root, home, identity?.id);
|
|
@@ -27094,7 +27231,7 @@ import {
|
|
|
27094
27231
|
realpathSync as realpathSync6,
|
|
27095
27232
|
renameSync as renameSync6,
|
|
27096
27233
|
rmSync as rmSync9,
|
|
27097
|
-
statSync as
|
|
27234
|
+
statSync as statSync6,
|
|
27098
27235
|
writeFileSync as writeFileSync11
|
|
27099
27236
|
} from "node:fs";
|
|
27100
27237
|
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 +27275,7 @@ function containedFile(root, name, directory = false) {
|
|
|
27138
27275
|
const rel = relative3(realpathSync6(root), path);
|
|
27139
27276
|
if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel))
|
|
27140
27277
|
throw new Error(`Frizz server artifact escapes its package: ${name}`);
|
|
27141
|
-
const stat =
|
|
27278
|
+
const stat = statSync6(path);
|
|
27142
27279
|
if (directory ? !stat.isDirectory() : !stat.isFile()) throw new Error(`invalid Frizz server artifact: ${name}`);
|
|
27143
27280
|
return path;
|
|
27144
27281
|
}
|
|
@@ -27181,7 +27318,7 @@ function resolveNpmCli(env = process.env, executable = process.execPath) {
|
|
|
27181
27318
|
} catch {
|
|
27182
27319
|
}
|
|
27183
27320
|
}
|
|
27184
|
-
const found = candidates.find((path) => existsSync9(path) &&
|
|
27321
|
+
const found = candidates.find((path) => existsSync9(path) && statSync6(path).isFile());
|
|
27185
27322
|
if (!found) throw new Error("npm was not found beside Node or on PATH; install Node with npm to install Frizz server releases");
|
|
27186
27323
|
return resolve6(found);
|
|
27187
27324
|
}
|
|
@@ -27563,7 +27700,7 @@ Options:
|
|
|
27563
27700
|
|
|
27564
27701
|
|
|
27565
27702
|
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
|
|
27703
|
+
walkthrough sets up a private frizz.sh name (no account needed), a Cloudflare
|
|
27567
27704
|
Tunnel, Tailscale, or a proxy of your own, and
|
|
27568
27705
|
remembers the choice, so a plain launch serves it from then on. The board stays on loopback and
|
|
27569
27706
|
shows a single-use sign-in link as a QR; press L for a fresh one, or run --link from another shell.`
|
|
@@ -27596,10 +27733,7 @@ var workspace = (() => {
|
|
|
27596
27733
|
return workspaceFromLaunchTarget(pinned);
|
|
27597
27734
|
}
|
|
27598
27735
|
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
|
-
);
|
|
27736
|
+
if (intent.kind === "empty") throw new Error(emptyLaunchMessage(intent));
|
|
27603
27737
|
launchIntent = intent;
|
|
27604
27738
|
return intent.workspace;
|
|
27605
27739
|
} catch (error) {
|