| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- /**
- * Pure-JS port of the path helpers from `src/pilot/paths.ts`.
- *
- * Lets `ui/server/` resolve `~/.pilotdeck` and encode project IDs the
- * same way the gateway server does, WITHOUT pulling `dist/src/pilot/`
- * into the express bridge. Keeping the math here means the UI server
- * can run from source without needing the TypeScript output to exist
- * on disk first.
- *
- * Keep this in sync with `src/pilot/paths.ts` — both must round-trip
- * identically or `~/.pilotdeck/projects/<id>/.cwd` markers written by
- * the bridge will not be found by `gateway.listProjects()` and vice
- * versa.
- */
- import { homedir } from 'node:os';
- import { resolve } from 'node:path';
- import { createHash } from 'node:crypto';
- export const DEFAULT_PILOT_HOME = '~/.pilotdeck';
- function normalizeHomePath(p) {
- if (p === '~') return homedir();
- if (p.startsWith('~/')) return resolve(homedir(), p.slice(2));
- return resolve(p);
- }
- /**
- * Resolve the active PilotDeck home directory. Honors `PILOT_HOME` so
- * tests / multi-instance setups can isolate state. Defaults to
- * `~/.pilotdeck`.
- *
- * @param {Record<string, string | undefined>} [env] Environment to read.
- * @returns {string} Absolute path.
- */
- export function resolvePilotHome(env = process.env) {
- return normalizeHomePath(env.PILOT_HOME ?? DEFAULT_PILOT_HOME);
- }
- /**
- * Encode an absolute project path into the on-disk project ID used under
- * `~/.pilotdeck/projects/<id>/`.
- *
- * This is the legacy lossy encoding. New UI-created projects use
- * `createCollisionResistantProjectId()` only when this id is already claimed
- * by a different `.cwd` marker.
- *
- * @param {string} projectRoot Absolute filesystem path.
- * @returns {string} Encoded project ID.
- */
- export function createProjectId(projectRoot) {
- const normalizedRoot = resolve(projectRoot);
- return createLegacyProjectId(normalizedRoot);
- }
- export function createCollisionResistantProjectId(projectRoot) {
- const normalizedRoot = resolve(projectRoot);
- const legacyId = createLegacyProjectId(normalizedRoot);
- const digest = createHash('sha1').update(normalizedRoot).digest('hex').slice(0, 10);
- return `${legacyId}--${digest}`;
- }
- /**
- * Sanitize a sessionId for safe use as a filename component.
- *
- * TUI/CLI sessionKeys embed the absolute project path (e.g.
- * `tui:project=/Users/foo/work/repo:default`). Without sanitization
- * the raw `/` characters make `path.resolve()` treat it as multiple
- * path segments, burying the transcript in nested dirs that
- * `listProjectSessions` can't find.
- *
- * Keep in sync with `src/session/storage/ProjectSessionStorage.ts`.
- *
- * @param {string} sessionId Raw session key.
- * @returns {string} Filename-safe session identifier.
- */
- export function sanitizeSessionIdForPath(sessionId) {
- const illegal = process.platform === 'win32' ? /[\\/:<>"|?*]+/g : /[\\/]+/g;
- return sessionId.replace(illegal, '-').replace(/^-+|-+$/g, '') || 'session';
- }
- function createLegacyProjectId(projectRoot) {
- // Normalize to forward slashes so the same physical path produces the same
- // project ID on Windows (\) and Unix (/). Also strip a Windows drive-letter
- // prefix (e.g. "C:") so "C:\Users\foo" slugifies identically to "/Users/foo".
- const normalized = projectRoot.replace(/\\/g, '/').replace(/^[A-Za-z]:/, '');
- return normalized.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'project';
- }
|