diff --git a/LifeOS/Tools/DeployCore.ts b/LifeOS/Tools/DeployCore.ts index 43881c398..1d109735c 100755 --- a/LifeOS/Tools/DeployCore.ts +++ b/LifeOS/Tools/DeployCore.ts @@ -44,7 +44,7 @@ function arg(a: string[], flag: string): string | undefined { } interface DeployResult { - what: "skills" | "runtime" | "memory"; + what: "skills" | "runtime" | "memory" | "dependencies"; src: string; dst: string; present: boolean; @@ -130,6 +130,39 @@ function scaffoldMemory(configRoot: string, apply: boolean): DeployResult { return r; } +/** + * (d) shared runtime deps: install/package.json → configRoot/package.json, then + * `bun install` in configRoot. Several deployed hooks/TOOLS scripts (e.g. + * hooks/lib/identity.ts, LIFEOS/TOOLS/Banner.ts) import npm packages (yaml) + * that resolve via node_modules walked up from configRoot — without this step + * those scripts throw "Cannot find package" on first run after a fresh install. + */ +function deployDependencies(payloadInstall: string, configRoot: string, apply: boolean): DeployResult { + const src = join(payloadInstall, "package.json"); + const dst = join(configRoot, "package.json"); + const r: DeployResult = { what: "dependencies", src, dst, present: existsSync(src), copied: 0, actions: [], blockers: [], failures: [] }; + if (!r.present) { + r.blockers.push(`dependency manifest missing: ${src} — point --skill-root at a staged release`); + return r; + } + if (!apply) { + r.actions.push(`copyMissing ${src} → ${dst}`, `bun install --cwd ${configRoot}`); + return r; + } + const { copied, failures } = copyMissing(src, dst); + r.copied = copied; + r.failures = failures; + if (failures.length === 0) { + const proc = Bun.spawnSync(["bun", "install"], { cwd: configRoot, stdout: "pipe", stderr: "pipe" }); + if (proc.exitCode !== 0) { + r.failures.push(`bun install --cwd ${configRoot} exited ${proc.exitCode}: ${proc.stderr.toString().trim()}`); + } else { + r.actions.push(`bun install --cwd ${configRoot}`); + } + } + return r; +} + function main(): void { const a = process.argv.slice(2); const home = process.env.HOME || homedir(); @@ -152,6 +185,7 @@ function main(): void { deploySkills(payloadInstall, configRoot, apply), deployRuntime(payloadInstall, configRoot, apply), scaffoldMemory(configRoot, apply), + deployDependencies(payloadInstall, configRoot, apply), ]; // A missing required payload source (blocker) or a copy failure is a hard diff --git a/LifeOS/install/LifeOS/PULSE/Assistant/module.ts b/LifeOS/install/LifeOS/PULSE/Assistant/module.ts new file mode 100644 index 000000000..786cb1a5a --- /dev/null +++ b/LifeOS/install/LifeOS/PULSE/Assistant/module.ts @@ -0,0 +1,112 @@ +/** + * Assistant module — STUB. + * + * pulse.ts imports "./Assistant/module" whenever `config.da.enabled` is true + * (see pulse.ts's da subsystem wiring) and wires its exports to the + * `/assistant/*` routes that back the Pulse Assistant tab + * (Observability/src/app/assistant/page.tsx). No implementation of this + * module has shipped yet, so every route below returns an honest empty/501 + * response instead of the import silently failing and leaving the tab's + * fetches unhandled. The frontend already renders a clean empty state for + * these shapes (see EmptyStateGuide + the `isFreshInstall` check in + * page.tsx) — this module exists so that behavior is deliberate, not a + * side-effect of a caught import error. + * + * Does NOT create its own HTTP server — pulse.ts calls + * handleAssistantRequest(). Real identity/personality/diary/cron-CRUD + * persistence is a separate, larger feature; this stub intentionally does + * not attempt it. + */ + +interface DaConfig { + enabled: boolean + primary?: string + [key: string]: unknown +} + +const NOT_IMPLEMENTED = + "Assistant backend not implemented in this LifeOS release — DA configuration is read-only until this ships." + +let primaryDa = "" + +export function assistantHealth() { + return { + status: "ok", + primary_da: primaryDa, + identity_loaded: false, + scheduled_tasks: 0, + last_heartbeat: null as string | null, + diary_entries_today: 0, + opinions_count: 0, + } +} + +export function startAssistant(daConfig: DaConfig, _jobs: unknown): void { + primaryDa = daConfig.primary ?? "" + console.log("[assistant] stub module loaded — DA identity/personality/diary/cron are not yet implemented, serving empty state") +} + +export function stopAssistant(): void { + primaryDa = "" +} + +function jsonReadOnly(body: T): Response { + return Response.json(body) +} + +function notImplemented(): Response { + return Response.json({ error: NOT_IMPLEMENTED }, { status: 501 }) +} + +export async function handleAssistantRequest(req: Request, pathname: string): Promise { + const method = req.method + + if (pathname === "/assistant/health") { + return method === "GET" ? jsonReadOnly(assistantHealth()) : null + } + + if (pathname === "/assistant/identity") { + return method === "GET" ? jsonReadOnly(null) : null + } + + if (pathname === "/assistant/personality") { + return method === "GET" ? jsonReadOnly(null) : null + } + + if (pathname === "/assistant/personality/traits") { + return method === "PATCH" ? notImplemented() : null + } + + if (pathname === "/assistant/tasks") { + return method === "GET" + ? jsonReadOnly({ tasks: [], count: 0, by_source: { da: 0, pulse: 0, "claude-code": 0 } }) + : null + } + + if (pathname === "/assistant/diary") { + return method === "GET" ? jsonReadOnly({ entries: [] }) : null + } + + if (pathname === "/assistant/opinions") { + return method === "GET" ? jsonReadOnly({ raw: "" }) : null + } + + if (pathname === "/assistant/cron") { + if (method === "GET") { + return jsonReadOnly({ jobs: [], user_file_path: "", counts: { total: 0, enabled: 0, system: 0, user: 0 } }) + } + if (method === "POST") return notImplemented() + return null + } + + if (pathname.startsWith("/assistant/cron/")) { + if (method === "PATCH" || method === "DELETE") return notImplemented() + return null + } + + if (pathname === "/assistant/avatar") { + return new Response(null, { status: 404 }) + } + + return null +} diff --git a/LifeOS/install/LifeOS/PULSE/checks/life-morning-brief.ts b/LifeOS/install/LifeOS/PULSE/checks/life-morning-brief.ts index 280a62551..381e3b247 100644 --- a/LifeOS/install/LifeOS/PULSE/checks/life-morning-brief.ts +++ b/LifeOS/install/LifeOS/PULSE/checks/life-morning-brief.ts @@ -21,6 +21,19 @@ function readFile(name: string): string { return readFileSync(p, "utf-8") } +// GOALS.md is a legacy per-topic file, superseded by the unified TELOS.md +// (H2 sections) on 2026-05-01. Fall back to TELOS.md's "## GOALS" section +// when the legacy file has been archived, so goals written only to TELOS.md +// still surface here. +function readGoals(): string { + const legacy = readFile("GOALS.md") + if (legacy) return legacy + const telos = readFile("TELOS.md") + if (!telos) return "" + const match = telos.match(/(?:^|\n)##\s*GOALS\b[^\n]*\n([\s\S]*?)(?=\n##\s|\n---|$)/i) + return match ? match[1].trim() : "" +} + // Extract top 3 goals from GOALS.md function getTopGoals(content: string): string[] { const lines = content.split("\n") @@ -55,7 +68,7 @@ function getNextMove(content: string): string | null { return first.trim().replace(/^\d+\.\s*/, "") } -const goals = readFile("GOALS.md") +const goals = readGoals() const sparks = readFile("SPARKS.md") const current = readFile("CURRENT.md") diff --git a/LifeOS/install/USER/TELOS/BELIEFS.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/BELIEFS.md similarity index 100% rename from LifeOS/install/USER/TELOS/BELIEFS.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/BELIEFS.md diff --git a/LifeOS/install/USER/TELOS/CHALLENGES.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/CHALLENGES.md similarity index 100% rename from LifeOS/install/USER/TELOS/CHALLENGES.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/CHALLENGES.md diff --git a/LifeOS/install/USER/TELOS/GOALS.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/GOALS.md similarity index 100% rename from LifeOS/install/USER/TELOS/GOALS.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/GOALS.md diff --git a/LifeOS/install/USER/TELOS/MISSION.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/MISSION.md similarity index 100% rename from LifeOS/install/USER/TELOS/MISSION.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/MISSION.md diff --git a/LifeOS/install/USER/TELOS/NARRATIVES.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/NARRATIVES.md similarity index 100% rename from LifeOS/install/USER/TELOS/NARRATIVES.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/NARRATIVES.md diff --git a/LifeOS/install/USER/TELOS/PROBLEMS.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/PROBLEMS.md similarity index 100% rename from LifeOS/install/USER/TELOS/PROBLEMS.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/PROBLEMS.md diff --git a/LifeOS/install/USER/TELOS/STRATEGIES.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/STRATEGIES.md similarity index 100% rename from LifeOS/install/USER/TELOS/STRATEGIES.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/STRATEGIES.md diff --git a/LifeOS/install/USER/TELOS/WISDOM.md b/LifeOS/install/USER/TELOS/Archive/2026-05-01/WISDOM.md similarity index 100% rename from LifeOS/install/USER/TELOS/WISDOM.md rename to LifeOS/install/USER/TELOS/Archive/2026-05-01/WISDOM.md diff --git a/LifeOS/install/USER/TELOS/README.md b/LifeOS/install/USER/TELOS/README.md index 762e9c3ba..2ba2b4929 100644 --- a/LifeOS/install/USER/TELOS/README.md +++ b/LifeOS/install/USER/TELOS/README.md @@ -11,16 +11,9 @@ behind any work you ask for. | File | What goes in it | |------|------------------| -| `MISSION.md` | The 1–3 things you're putting your life behind. Big, durable, often unfinishable. | -| `GOALS.md` | Concrete year-scale goals tied to each mission. SMART-ish; revisited quarterly. | -| `PROBLEMS.md` | The world-level problems your work is trying to solve (vs. internal challenges). | -| `STRATEGIES.md` | The plays you've chosen to make progress on the problems. | -| `NARRATIVES.md` | The story you tell yourself and others about what you're doing and why. | -| `CHALLENGES.md` | Personal blockers — habits, fears, patterns that get in your way. | -| `BELIEFS.md` | The opinions and frames you operate from. The DA uses these to read drafts in your voice. | -| `WISDOM.md` | Lessons you've extracted from experience and want to keep applying. | -| `BOOKS.md` | Books that shaped you. Useful when the DA picks recommendations or framings. | -| `PRINCIPAL_TELOS.md` | **Auto-generated summary** of all the above. Loaded into every session via CLAUDE.md. | +| `TELOS.md` | **The single source of truth.** Unified H2 sections — Current State, Ideal State, Mission, Problems, Goals, Challenges, Strategies, Projects, Narratives, Wisdom. Edit this file directly (or run `/interview`); `GenerateTelosSummary.ts` reads it. | +| `BOOKS.md` | Books that shaped you. Standalone — not part of the unified `TELOS.md` schema. Useful when the DA picks recommendations or framings. | +| `PRINCIPAL_TELOS.md` | **Auto-generated summary** of `TELOS.md`. Loaded into every session via CLAUDE.md. Do not edit manually — regenerate with `GenerateTelosSummary.ts` after editing `TELOS.md`. | ## Subdirectories @@ -29,16 +22,18 @@ behind any work you ask for. | `CURRENT_STATE/` | Where you are right now across the dimensions of your life — health, finances, relationships, work, learning. The DA uses this as the starting point for any "how do I get from here to there" question. Sample scaffolds inside. | | `IDEAL_STATE/` | Where you want to be — the vision you're aiming at across the same dimensions. Sample scaffolds inside. | | `Backups/` | Versioned snapshots of your TELOS files. Tools that bulk-edit TELOS write a backup here before changing anything. Empty until something is backed up. | +| `Archive/2026-05-01/` | Pre-unification per-topic files (`MISSION.md`, `GOALS.md`, `PROBLEMS.md`, `STRATEGIES.md`, `CHALLENGES.md`, `NARRATIVES.md`, `BELIEFS.md`, `WISDOM.md`). `TELOS.md` superseded these on 2026-05-01 — kept only for reference. `GenerateTelosSummary.ts` will read a file here as a legacy override **only if you restore it to the top level**; leave this directory alone otherwise. | ## How to fill these in -**Easiest:** run `/interview` after install. It walks you through each file -in order, asks the right questions, and writes your answers to disk — -replacing every `(sample)` entry with content that's actually yours. +**Easiest:** run `/interview` after install. It walks you through `TELOS.md` +section by section, asks the right questions, and writes your answers to +disk — replacing every `(sample)` entry with content that's actually yours. -**By hand:** open each `.md` file. The bootstrap content shows the shape — -delete the placeholders and write your real answers. Keep entries short and -high-signal; the DA reads everything in this directory at session start. +**By hand:** open `TELOS.md`. The bootstrap content shows the shape — delete +the placeholders and write your real answers under each H2 section. Keep +entries short and high-signal; the DA reads this file at session start. +Regenerate the summary afterward (see below). **From existing data:** if you already have goals/missions in Obsidian, Notion, journal entries, or a Telos repo, run the **Migrate** skill before diff --git a/LifeOS/install/package.json b/LifeOS/install/package.json new file mode 100644 index 000000000..68fa7eb7a --- /dev/null +++ b/LifeOS/install/package.json @@ -0,0 +1,9 @@ +{ + "name": "lifeos-runtime", + "private": true, + "type": "module", + "description": "Shared runtime dependencies for LifeOS hooks and LIFEOS/TOOLS scripts. Deployed to /package.json by DeployCore.ts so `bun install` resolves node_modules for both /hooks/ and /LIFEOS/.", + "dependencies": { + "yaml": "^2.8.2" + } +} diff --git a/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts b/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts index a07d6f11d..9ec4a0efb 100644 --- a/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts +++ b/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts @@ -79,8 +79,20 @@ function readFileIfExists(path: string): string | null { return readFileSync(path, "utf-8"); } +// MISSION.md/GOALS.md/WISDOM.md are legacy per-topic files, superseded by the +// unified TELOS.md (H2 sections) on 2026-05-01. Fall back to the matching +// TELOS.md section when the legacy file has been archived, so content written +// only to TELOS.md still surfaces in the daemon profile. +function readTelosSection(heading: string): string | null { + const telos = readFileIfExists(join(TELOS_DIR, "TELOS.md")); + if (!telos) return null; + const re = new RegExp(`(?:^|\\n)##\\s*${heading}\\b[^\\n]*\\n([\\s\\S]*?)(?=\\n##\\s|\\n---|$)`, "i"); + const match = telos.match(re); + return match ? match[1].trim() : null; +} + function readMissions(): string { - const content = readFileIfExists(join(TELOS_DIR, "MISSION.md")); + const content = readFileIfExists(join(TELOS_DIR, "MISSION.md")) ?? readTelosSection("Mission"); if (!content) return ""; const lines = content.split("\n"); @@ -103,7 +115,7 @@ function readMissions(): string { } function readGoals(): string { - const content = readFileIfExists(join(TELOS_DIR, "GOALS.md")); + const content = readFileIfExists(join(TELOS_DIR, "GOALS.md")) ?? readTelosSection("GOALS"); if (!content) return ""; const lines = content.split("\n"); @@ -149,7 +161,7 @@ function readMovies(): string[] { } function readWisdom(): string[] { - const content = readFileIfExists(join(TELOS_DIR, "WISDOM.md")); + const content = readFileIfExists(join(TELOS_DIR, "WISDOM.md")) ?? readTelosSection("Wisdom"); if (!content) return []; // Split by double newlines to get individual quotes diff --git a/LifeOS/install/skills/LifeOS/Tools/DeployCore.ts b/LifeOS/install/skills/LifeOS/Tools/DeployCore.ts index 43881c398..1d109735c 100755 --- a/LifeOS/install/skills/LifeOS/Tools/DeployCore.ts +++ b/LifeOS/install/skills/LifeOS/Tools/DeployCore.ts @@ -44,7 +44,7 @@ function arg(a: string[], flag: string): string | undefined { } interface DeployResult { - what: "skills" | "runtime" | "memory"; + what: "skills" | "runtime" | "memory" | "dependencies"; src: string; dst: string; present: boolean; @@ -130,6 +130,39 @@ function scaffoldMemory(configRoot: string, apply: boolean): DeployResult { return r; } +/** + * (d) shared runtime deps: install/package.json → configRoot/package.json, then + * `bun install` in configRoot. Several deployed hooks/TOOLS scripts (e.g. + * hooks/lib/identity.ts, LIFEOS/TOOLS/Banner.ts) import npm packages (yaml) + * that resolve via node_modules walked up from configRoot — without this step + * those scripts throw "Cannot find package" on first run after a fresh install. + */ +function deployDependencies(payloadInstall: string, configRoot: string, apply: boolean): DeployResult { + const src = join(payloadInstall, "package.json"); + const dst = join(configRoot, "package.json"); + const r: DeployResult = { what: "dependencies", src, dst, present: existsSync(src), copied: 0, actions: [], blockers: [], failures: [] }; + if (!r.present) { + r.blockers.push(`dependency manifest missing: ${src} — point --skill-root at a staged release`); + return r; + } + if (!apply) { + r.actions.push(`copyMissing ${src} → ${dst}`, `bun install --cwd ${configRoot}`); + return r; + } + const { copied, failures } = copyMissing(src, dst); + r.copied = copied; + r.failures = failures; + if (failures.length === 0) { + const proc = Bun.spawnSync(["bun", "install"], { cwd: configRoot, stdout: "pipe", stderr: "pipe" }); + if (proc.exitCode !== 0) { + r.failures.push(`bun install --cwd ${configRoot} exited ${proc.exitCode}: ${proc.stderr.toString().trim()}`); + } else { + r.actions.push(`bun install --cwd ${configRoot}`); + } + } + return r; +} + function main(): void { const a = process.argv.slice(2); const home = process.env.HOME || homedir(); @@ -152,6 +185,7 @@ function main(): void { deploySkills(payloadInstall, configRoot, apply), deployRuntime(payloadInstall, configRoot, apply), scaffoldMemory(configRoot, apply), + deployDependencies(payloadInstall, configRoot, apply), ]; // A missing required payload source (blocker) or a copy failure is a hard