Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion LifeOS/Tools/DeployCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
112 changes: 112 additions & 0 deletions LifeOS/install/LifeOS/PULSE/Assistant/module.ts
Original file line number Diff line number Diff line change
@@ -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<T>(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<Response | null> {
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
}
15 changes: 14 additions & 1 deletion LifeOS/install/LifeOS/PULSE/checks/life-morning-brief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")

Expand Down
27 changes: 11 additions & 16 deletions LifeOS/install/USER/TELOS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions LifeOS/install/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "lifeos-runtime",
"private": true,
"type": "module",
"description": "Shared runtime dependencies for LifeOS hooks and LIFEOS/TOOLS scripts. Deployed to <configRoot>/package.json by DeployCore.ts so `bun install` resolves node_modules for both <configRoot>/hooks/ and <configRoot>/LIFEOS/.",
"dependencies": {
"yaml": "^2.8.2"
}
}
18 changes: 15 additions & 3 deletions LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -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
Expand Down
36 changes: 35 additions & 1 deletion LifeOS/install/skills/LifeOS/Tools/DeployCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down