Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153// profile.mjs — decide how a session behaves from where it was opened.//// Easel began as a piece studio: open it anywhere, get a blank piece, a live// channel, a QR on the rock and a public URL under your handle. "Pro" is the// same terminal harness pointed at ordinary work — a client repo, a server —// where none of that should happen: nothing publishes, no audience, and the// engine runs with the user's own settings and MCP servers instead of Easel's// isolation. "Private" is orthogonal: the Slab marker still says the session// exists and whether it is working, but carries no subject, so the menubar// writes no memoir of a client's brief.//// Both are decided once, at open, from the flags, the environment, and a small// config of globs at ~/.config/aesel/profiles.json — so a client directory can// be private for good rather than remembered per session. The matcher is a// few lines on purpose: `~`, `*` and `**` are all a path glob needs here, and a// dependency for it would be the only one Easel has.import { readFileSync, realpathSync } from "node:fs";import { homedir } from "node:os";import { join, resolve } from "node:path";import { configDir } from "./paths.mjs";
const EXAMPLE = { private: ["~/fuser/**", "~/ac-worktrees/fuser*/**"], pro: ["~/fuser/**"],};
export function exampleConfig() { return JSON.stringify(EXAMPLE, null, 2);}
// `~/a/**` matches ~/a itself and everything under it; `*` stays inside one// path segment; `**` crosses them.export function globToRegExp(pattern, home = homedir()) { let glob = String(pattern).trim(); if (glob === "~") glob = home; else if (glob.startsWith("~/")) glob = home + glob.slice(1); glob = glob.replace(/\/+$/, ""); let source = ""; for (let i = 0; i < glob.length; i += 1) { const c = glob[i]; if (c === "*" && glob[i + 1] === "*") { const before = glob[i - 1]; const after = glob[i + 2]; if ((before === "/" || before === undefined) && after === undefined) { // trailing `/**` — the directory and anything below it. source = source.replace(/\/$/, ""); source += "(/.*)?"; } else if (before === "/" && after === "/") { // `/**/` in the middle — zero or more segments. source += "(.*/)?"; i += 1; // skip the following slash, already consumed } else { source += ".*"; } i += 1; continue; } if (c === "*") { source += "[^/]*"; continue; } if (c === "?") { source += "[^/]"; continue; } source += /[.+^${}()|[\]\\]/.test(c) ? `\\${c}` : c; } return new RegExp(`^${source}$`);}
export function globMatch(path, pattern, home = homedir()) { return globToRegExp(pattern, home).test(String(path).replace(/\/+$/, "") || "/");}
function readConfig(configPath) { const clean = { private: [], pro: [] }; let parsed; try { parsed = JSON.parse(readFileSync(configPath, "utf8")); } catch { return clean; } if (!parsed || typeof parsed !== "object") return clean; for (const key of ["private", "pro"]) { if (Array.isArray(parsed[key])) { clean[key] = parsed[key].filter((g) => typeof g === "string" && g.trim()); } } return clean;}
// A cwd is usually reached through a symlink or two (worktrees, ~/Desktop// aliases); match the path as typed and as it really is.function forms(cwd) { const typed = resolve(cwd); try { const real = realpathSync(typed); return real === typed ? [typed] : [typed, real]; } catch { return [typed]; }}
function firstMatch(paths, globs, home) { for (const glob of globs) { for (const path of paths) if (globMatch(path, glob, home)) return glob; } return null;}
export function resolveProfile({ cwd = process.cwd(), flags = {}, configPath, env = process.env,} = {}) { const home = env.HOME || homedir(); const config = readConfig(configPath || join(configDir({ home, env }), "profiles.json")); const paths = forms(cwd); const reasons = [];
let name = "piece"; if (flags.pro === true) { name = "pro"; reasons.push("pro: --pro"); } else if (flags.pro !== false) { const hit = firstMatch(paths, config.pro, home); if (hit) { name = "pro"; reasons.push(`pro: cwd matches ${hit}`); } }
let isPrivate = false; if (flags.private === true) { isPrivate = true; reasons.push("private: --private"); } else if (env.AESEL_PRIVATE === "1") { isPrivate = true; reasons.push("private: AESEL_PRIVATE=1"); } else { const hit = firstMatch(paths, config.private, home); if (hit) { isPrivate = true; reasons.push(`private: cwd matches ${hit}`); } }
return { name, private: isPrivate, // Pro publishes nothing; private publishes nothing and advertises state only. publish: name === "piece" && !isPrivate, advertise: isPrivate ? "status" : "full", passthrough: name === "pro", reason: reasons.length ? reasons.join("; ") : "piece: default", };}