From ee9f87ff3395a53211594fa4d2053015c9e61507 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 15:16:29 +0530 Subject: [PATCH 01/12] Support project-scoped installs (CLAUDE_CONFIG_DIR/LIFEOS_DIR honored everywhere) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LifeOS's install Tools already accepted --config-root/--config-dir, but ~60 files across the install payload hardcoded $HOME/.claude (or homedir()) directly, bypassing those flags. A project-scoped install (e.g. --config-root /.claude, so LifeOS doesn't leak into every Claude Code session) would silently read/write the real global ~/.claude instead once past the install step โ€” hooks pointing at files that were never placed there, memory/knowledge tools writing to the wrong tree, etc. - InstallHooks.ts: hooks.json ships commands hardcoded to "$HOME/.claude/..."; rewrite that prefix to the actual --config-root before merging into settings.json. - InstallSettings.ts: env template values under $HOME/.claude now expand to --config-root instead of unconditionally expanding to the real home dir. - install/hooks/lib/paths.ts: getClaudeDir() now honors CLAUDE_CONFIG_DIR (it already honored CLAUDE_PLUGIN_ROOT for packed-plugin installs) โ€” this is the central resolver several core hooks (PromptProcessing, EventLogger, LoadContext, AgentInvocation) depend on. - ~55 further files across LIFEOS/TOOLS, PULSE, hooks/, and skills/ Tools: added the same CLAUDE_CONFIG_DIR / LIFEOS_DIR fallback ahead of each hardcoded homedir()+".claude" path, matching the convention already used correctly in Conveyor/Ledger.ts and PULSE/modules/content.ts. Every change is additive (env var checked first, falls through to the exact original default) โ€” zero behavior change for a normal global install with these vars unset. - ContextAudit.ts: its cross-reference validator only recognized ~/.claude/LIFEOS/... as a valid absolute prefix, so it would flag every correct project-scoped doc reference as broken; added the missing branches. - Docs: INSTALL.md and Workflows/Setup.md now document the existing CLAUDE_CONFIG_DIR/LIFEOS_DIR mechanism as the supported way to scope an install to one project, since previously nothing surfaced that this was possible. Left untouched (confirmed cosmetic, no functional path): usage/doc strings, console.log/error message text, and a couple of files already correctly guarded by an existing env-var check. ๐Ÿค– Generated with Claude Code Co-Authored-By: Claude Sonnet 5 --- LifeOS/INSTALL.md | 2 ++ LifeOS/Tools/InstallHooks.ts | 8 +++++++- LifeOS/Tools/InstallSettings.ts | 14 +++++++++++--- LifeOS/Workflows/Setup.md | 4 ++++ LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh | 12 ++++++------ LifeOS/install/LIFEOS/PULSE/Tools/ReleaseAudit.ts | 2 +- LifeOS/install/LIFEOS/PULSE/manage-deriver.sh | 6 +++--- LifeOS/install/LIFEOS/PULSE/manage.sh | 2 +- LifeOS/install/LIFEOS/PULSE/modules/menubar.ts | 2 +- .../install/LIFEOS/TOOLS/AlgorithmPhaseReport.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/CarrierProbe.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/Checkpoint.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts | 5 +++++ LifeOS/install/LIFEOS/TOOLS/Conveyor/Runner.ts | 2 +- .../install/LIFEOS/TOOLS/Conveyor/YouTubeAuth.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/DoctrineReplay.ts | 2 +- .../LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/HealthSnapshot.ts | 2 +- .../install/LIFEOS/TOOLS/IntegrityMaintenance.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/KnowledgeLint.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/KnowledgeQuery.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryRestore.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryReviewer.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemorySystem.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryTypes.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryWriter.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MigrateKnowledge.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MutationTier.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/PerplexitySearch.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/PiSync.sh | 4 ++-- LifeOS/install/LIFEOS/TOOLS/SessionRename.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/SettingsBackport.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/TokenXray/TokenXray.ts | 6 +++--- LifeOS/install/LIFEOS/TOOLS/lifeos.ts | 4 ++-- LifeOS/install/hooks/AgentInvocation.hook.ts | 2 +- LifeOS/install/hooks/CheckpointPerISC.hook.ts | 2 +- LifeOS/install/hooks/HookHealer.hook.ts | 2 +- LifeOS/install/hooks/ISARenderOnStop.hook.ts | 6 +++--- LifeOS/install/hooks/ISASync.hook.ts | 4 ++-- LifeOS/install/hooks/LoadMemory.hook.ts | 2 +- LifeOS/install/hooks/MemoryDeltaSurface.hook.ts | 2 +- LifeOS/install/hooks/MemoryReviewFire.hook.ts | 2 +- LifeOS/install/hooks/MemoryTurnStart.hook.ts | 2 +- LifeOS/install/hooks/lib/paths.ts | 10 ++++++++++ LifeOS/install/skills/Art/Tools/PickExpression.ts | 3 ++- LifeOS/install/skills/Art/Tools/ThumbnailText.ts | 3 ++- LifeOS/install/skills/AudioEditor/Tools/Polish.ts | 2 ++ LifeOS/install/skills/CMUX/Tools/cmux.ts | 3 ++- .../install/skills/Evals/Tools/AlgorithmBridge.ts | 3 ++- LifeOS/install/skills/Interceptor/Tools/Capture.sh | 4 ++-- .../skills/Interceptor/Tools/EnsureTestProfile.sh | 2 +- .../skills/Interceptor/Tools/LaunchTestProfile.sh | 2 +- .../skills/Interceptor/Tools/PreflightIsolation.sh | 2 +- LifeOS/install/skills/LifeOS/install/install.sh | 3 ++- .../skills/LocalIntelligence/Tools/Refresh.ts | 3 ++- .../DashboardTemplate/app/api/file/save/route.ts | 2 +- .../DashboardTemplate/app/api/upload/route.ts | 2 +- .../Telos/DashboardTemplate/lib/telos-data.ts | 2 +- 60 files changed, 117 insertions(+), 74 deletions(-) diff --git a/LifeOS/INSTALL.md b/LifeOS/INSTALL.md index 2996cbd875..d72ed356a4 100644 --- a/LifeOS/INSTALL.md +++ b/LifeOS/INSTALL.md @@ -20,6 +20,8 @@ Prefer a terminal? There's a shell shortcut for Claude Code on macOS/Linux: curl -fsSL https://ourlifeos.ai/install.sh | bash ``` +**Want LifeOS scoped to one project instead of every session?** By default LifeOS installs into your harness's global config dir (e.g. `~/.claude`), so its skills/hooks/agents are available everywhere. To confine it to a single project instead โ€” so it only activates when your harness runs from that project's directory โ€” set `CLAUDE_CONFIG_DIR` (and `LIFEOS_DIR` / `LIFEOS_CONFIG_DIR` for the LIFEOS-specific subtree) to a path inside that project, e.g. `/.claude`, before running the install Tools (or pass the equivalent `--config-root`/`--config-dir` flags each Tool accepts). Everything under this document and `Workflows/Setup.md` works identically either way โ€” every install Tool resolves its target through these same env vars, defaulting to the global harness dir only when they're unset. + Everything below is written for the AI doing the install. --- diff --git a/LifeOS/Tools/InstallHooks.ts b/LifeOS/Tools/InstallHooks.ts index 030fb92181..d361065d1a 100755 --- a/LifeOS/Tools/InstallHooks.ts +++ b/LifeOS/Tools/InstallHooks.ts @@ -59,7 +59,13 @@ function main(): void { console.log(JSON.stringify({ ok: false, error: `payload hooks.json not found at ${hooksJsonPath}` }, null, 2)); process.exit(1); } - const incoming = JSON.parse(readFileSync(hooksJsonPath, "utf-8"))?.hooks ?? {}; + let hooksJsonRaw = readFileSync(hooksJsonPath, "utf-8"); + // hooks.json ships commands hardcoded to "$HOME/.claude/..." (hooks/, LIFEOS/TOOLS, + // LIFEOS/USER/CONFIG, settings.json) โ€” rewrite the whole prefix to the actual + // configRoot so a non-default --config-root (e.g. a project-scoped install) + // doesn't wire commands pointing at files that were never placed there. + hooksJsonRaw = hooksJsonRaw.split("$HOME/.claude").join(configRoot); + const incoming = JSON.parse(hooksJsonRaw)?.hooks ?? {}; // The hook SCRIPTS (*.hook.ts|sh + lib/**) live beside hooks.json in the payload. // Merging hooks.json into settings.json wires commands that point at these files, diff --git a/LifeOS/Tools/InstallSettings.ts b/LifeOS/Tools/InstallSettings.ts index 4b81c130e2..9635a8688b 100644 --- a/LifeOS/Tools/InstallSettings.ts +++ b/LifeOS/Tools/InstallSettings.ts @@ -51,13 +51,21 @@ export function expandLeadingHome(value: string, home: string): string { return value; } -function expandEnvBlock(settings: Record, home: string): number { +function expandEnvBlock(settings: Record, home: string, configRoot: string): number { const env = settings.env; if (!env || typeof env !== "object") return 0; + const defaultConfigRoot = join(home, ".claude"); let n = 0; for (const [k, v] of Object.entries(env as Record)) { if (typeof v === "string") { - const expanded = expandLeadingHome(v, home); + // Template values under $HOME/.claude belong under configRoot, not the real + // home, when a non-default --config-root is in play (e.g. project-scoped). + let expanded = v; + if (configRoot !== defaultConfigRoot && v.startsWith("$HOME/.claude")) { + expanded = configRoot + v.slice("$HOME/.claude".length); + } else { + expanded = expandLeadingHome(v, home); + } if (expanded !== v) { (env as Record)[k] = expanded; n++; } } } @@ -79,7 +87,7 @@ if (!existsSync(templatePath)) { } const template = JSON.parse(readFileSync(templatePath, "utf8")) as Record; -const expandedCount = expandEnvBlock(template, home); +const expandedCount = expandEnvBlock(template, home, args.configRoot); const report: Record = { ok: true, apply: args.apply, target: targetPath, envValuesExpanded: expandedCount }; diff --git a/LifeOS/Workflows/Setup.md b/LifeOS/Workflows/Setup.md index 40a435c1ce..3fb720308d 100644 --- a/LifeOS/Workflows/Setup.md +++ b/LifeOS/Workflows/Setup.md @@ -9,6 +9,10 @@ curl -s -X POST http://localhost:31337/notify -H "Content-Type: application/json -d '{"message": "Running the Setup workflow in the LifeOS skill to integrate LifeOS into your system"}' > /dev/null 2>&1 & ``` +## Project-scoped installs + +Every Tool in this workflow (`InstallSettings`, `DeployCore`, `ScaffoldUser`, `LinkUser`, `InstallHooks`, `DeployComponents`, `ActivateImports`) resolves its target root through `CLAUDE_CONFIG_DIR` (general harness-config paths) and `LIFEOS_DIR`/`LIFEOS_CONFIG_DIR` (the LIFEOS-specific subtree) โ€” or the equivalent `--config-root`/`--config-dir` flags โ€” falling back to the global harness dir (e.g. `~/.claude`) only when unset. If your human wants LifeOS confined to one project instead of every session, set these to a path inside that project before running any step below, and pass the same flags through consistently across all of them. No step in this workflow needs a different invocation to support this โ€” it's the same install, just rooted elsewhere. + ## Two-tier model Deployment is **two tiers**, and the install presents them that way: diff --git a/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh b/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh index e1e21745ec..274841e5ef 100755 --- a/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh +++ b/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh @@ -36,7 +36,7 @@ case "$LIFEOS_DIR" in *'$HOME'*|*'${HOME}'*|*'~'*) echo "LifeOS"; exit 0 ;; esac -CLAUDE_HOME="$HOME/.claude" +CLAUDE_HOME="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" SETTINGS_FILE="$CLAUDE_HOME/settings.json" RATINGS_FILE="$LIFEOS_DIR/MEMORY/LEARNING/SIGNALS/ratings.jsonl" MODEL_CACHE="$LIFEOS_DIR/MEMORY/STATE/model-cache.txt" @@ -85,7 +85,7 @@ USER_TZ="${USER_TZ:-UTC}" LIFEOS_VERSION="" for _pai_v_path in \ "$LIFEOS_DIR/VERSION" \ - "$HOME/.claude/LIFEOS/VERSION" \ + "${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/VERSION" \ "/Users/$(id -un 2>/dev/null)/.claude/LIFEOS/VERSION" \ "$(eval echo ~"$(id -un 2>/dev/null)")/.claude/LIFEOS/VERSION"; do if [ -n "$_pai_v_path" ] && [ -f "$_pai_v_path" ]; then @@ -101,7 +101,7 @@ LIFEOS_VERSION="${LIFEOS_VERSION:-โ€”}" ALGO_VERSION="" for _algo_path in \ "$LIFEOS_DIR/ALGORITHM/LATEST" \ - "$HOME/.claude/LIFEOS/ALGORITHM/LATEST" \ + "${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/ALGORITHM/LATEST" \ "/Users/$(id -un 2>/dev/null)/.claude/LIFEOS/ALGORITHM/LATEST" \ "$(eval echo ~"$(id -un 2>/dev/null)")/.claude/LIFEOS/ALGORITHM/LATEST"; do if [ -n "$_algo_path" ] && [ -f "$_algo_path" ]; then @@ -134,7 +134,7 @@ USAGE_HARD_EXPIRY=21600 # P5: 6h. Show last-known-good (dimmed + stale badge) u # Source .env for API keys. Canonical location is $HOME/.claude/.env (which is # typically a symlink to $HOME/.config/LIFEOS/.env). The historical $HOME/.claude/LIFEOS/.env # path is wrong and has been removed everywhere else โ€” do not reintroduce it. -[ -f "$HOME/.claude/.env" ] && source "$HOME/.claude/.env" +[ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.env" ] && source "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.env" # Cross-platform file mtime (seconds since epoch). Detect stat flavor once; # probing both variants on every mtime check is expensive on macOS. @@ -729,7 +729,7 @@ if [ "$MODE" != "nano" ]; then # Hook count flows through GetCounts.ts โ€” same source banner uses. --single hooks # short-circuits all other walks (~20ms). Don't reintroduce inline jq here. - _hooks_cnt=$(bun "$HOME/.claude/LIFEOS/TOOLS/GetCounts.ts" --single hooks 2>/dev/null || echo 0) + _hooks_cnt=$(bun "$LIFEOS_DIR/TOOLS/GetCounts.ts" --single hooks 2>/dev/null || echo 0) _ratings_cnt=0 [ -f "$RATINGS_FILE" ] && _ratings_cnt=$(wc -l < "$RATINGS_FILE" 2>/dev/null | tr -d ' ') @@ -800,7 +800,7 @@ if [ "$MODE" = "normal" ]; then if [ "$(uname -s)" = "Darwin" ]; then cred_json=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) else - cred_json=$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null) + cred_json=$(cat "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json" 2>/dev/null) fi token=$(echo "$cred_json" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) diff --git a/LifeOS/install/LIFEOS/PULSE/Tools/ReleaseAudit.ts b/LifeOS/install/LIFEOS/PULSE/Tools/ReleaseAudit.ts index 1bbae62500..a6fcf31f47 100644 --- a/LifeOS/install/LIFEOS/PULSE/Tools/ReleaseAudit.ts +++ b/LifeOS/install/LIFEOS/PULSE/Tools/ReleaseAudit.ts @@ -34,7 +34,7 @@ function loadProhibitedStrings(): string[] { const candidates = [ process.env.LIFEOS_RELEASE_AUDIT_STRINGS, join(homedir(), ".config/LIFEOS/USER/CONFIG/release-audit-strings.json"), - join(homedir(), ".claude/LIFEOS/USER/CONFIG/release-audit-strings.json"), + join(process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS"), "USER/CONFIG/release-audit-strings.json"), ].filter(Boolean) as string[]; for (const p of candidates) { try { diff --git a/LifeOS/install/LIFEOS/PULSE/manage-deriver.sh b/LifeOS/install/LIFEOS/PULSE/manage-deriver.sh index df012bf14e..a77494acdc 100755 --- a/LifeOS/install/LIFEOS/PULSE/manage-deriver.sh +++ b/LifeOS/install/LIFEOS/PULSE/manage-deriver.sh @@ -13,11 +13,11 @@ set -euo pipefail -PULSE_DIR="$HOME/.claude/LIFEOS/PULSE" +PULSE_DIR="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/PULSE" PLIST_NAME="com.lifeos.deriver" PLIST_SRC="$PULSE_DIR/$PLIST_NAME.plist" PLIST_DST="$HOME/Library/LaunchAgents/$PLIST_NAME.plist" -OBSERVABILITY_DIR="$HOME/.claude/LIFEOS/MEMORY/OBSERVABILITY" +OBSERVABILITY_DIR="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/MEMORY/OBSERVABILITY" if [ -x "$HOME/.bun/bin/bun" ]; then BUN_PATH="$HOME/.bun/bin/bun" @@ -81,7 +81,7 @@ case "${1:-}" in run-now) # One-shot manual invocation for testing / first-run priming. - exec "$BUN_PATH" run "$HOME/.claude/LIFEOS/TOOLS/LearningPatternSynthesis.ts" --hypothesize + exec "$BUN_PATH" run "${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/TOOLS/LearningPatternSynthesis.ts" --hypothesize ;; *) diff --git a/LifeOS/install/LIFEOS/PULSE/manage.sh b/LifeOS/install/LIFEOS/PULSE/manage.sh index e08ecd0929..0eba224ab4 100755 --- a/LifeOS/install/LIFEOS/PULSE/manage.sh +++ b/LifeOS/install/LIFEOS/PULSE/manage.sh @@ -2,7 +2,7 @@ # LifeOS Pulse โ€” Process Management # Usage: manage.sh {start|stop|restart|status|install|uninstall} -PULSE_DIR="$HOME/.claude/LIFEOS/PULSE" +PULSE_DIR="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/PULSE" PLIST_NAME="com.lifeos.pulse" PLIST_SRC="$PULSE_DIR/$PLIST_NAME.plist" PLIST_DST="$HOME/Library/LaunchAgents/$PLIST_NAME.plist" diff --git a/LifeOS/install/LIFEOS/PULSE/modules/menubar.ts b/LifeOS/install/LIFEOS/PULSE/modules/menubar.ts index 0b41ebee93..21bd64ab03 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/menubar.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/menubar.ts @@ -20,7 +20,7 @@ import { join } from "node:path" const MODULE_NAME = "menubar" const state = { running: false, startedAt: null as Date | null } -const CLAUDE = join(homedir(), ".claude") +const CLAUDE = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude") const LIFEOS = join(CLAUDE, "LIFEOS") const OBS = join(LIFEOS, "MEMORY", "OBSERVABILITY") const STATE_DIR = join(LIFEOS, "PULSE", "state") diff --git a/LifeOS/install/LIFEOS/TOOLS/AlgorithmPhaseReport.ts b/LifeOS/install/LIFEOS/TOOLS/AlgorithmPhaseReport.ts index 6eb80a5e39..e196e465ea 100755 --- a/LifeOS/install/LIFEOS/TOOLS/AlgorithmPhaseReport.ts +++ b/LifeOS/install/LIFEOS/TOOLS/AlgorithmPhaseReport.ts @@ -18,7 +18,7 @@ import { join } from "path"; import { homedir } from "os"; import { parseArgs } from "util"; -const STATE_DIR = join(homedir(), ".claude", "LIFEOS", "MEMORY", "STATE"); +const STATE_DIR = join(process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS"), "MEMORY", "STATE"); const STATE_FILE = join(STATE_DIR, "algorithm-phase.json"); interface AlgorithmState { diff --git a/LifeOS/install/LIFEOS/TOOLS/CarrierProbe.ts b/LifeOS/install/LIFEOS/TOOLS/CarrierProbe.ts index ace29edfee..3459e7cdc3 100644 --- a/LifeOS/install/LIFEOS/TOOLS/CarrierProbe.ts +++ b/LifeOS/install/LIFEOS/TOOLS/CarrierProbe.ts @@ -35,7 +35,7 @@ import { join } from "path"; import { homedir } from "os"; import { DISPATCH_EXECUTES_FABLE } from "./models"; -const CLAUDE_DIR = join(homedir(), ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"); const STATE_FILE = join(CLAUDE_DIR, "LIFEOS", "MEMORY", "STATE", "carrier-probe.json"); const OBS_FILE = join(CLAUDE_DIR, "LIFEOS", "MEMORY", "OBSERVABILITY", "model-verification.jsonl"); const MAX_AGE_DAYS = 30; diff --git a/LifeOS/install/LIFEOS/TOOLS/Checkpoint.ts b/LifeOS/install/LIFEOS/TOOLS/Checkpoint.ts index e7dc366d2d..25f0c84f10 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Checkpoint.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Checkpoint.ts @@ -21,8 +21,8 @@ import { parseCriteriaList } from '../../hooks/lib/isa-utils'; // so the ContainmentGuard write restriction does not apply. Parser must match // the hook's parser exactly: skip blanks and '#' lines, expand tilde / $HOME // prefixes, treat the rest as absolute repo paths. -const ALLOWLIST_PATH = join(homedir(), '.claude', 'checkpoint-repos.txt'); -const WORK_DIR = join(homedir(), '.claude', 'LIFEOS', 'MEMORY', 'WORK'); +const ALLOWLIST_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'checkpoint-repos.txt'); +const WORK_DIR = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'MEMORY', 'WORK'); function expandPath(p: string): string { let s = p.trim(); diff --git a/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts b/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts index c62610cfed..223486cac8 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts @@ -208,6 +208,11 @@ function normalizeReference(raw: string): string | null { if (value.startsWith("LIFEOS/")) return join(CLAUDE_DIR, value); if (value.startsWith("~/.claude/LIFEOS/")) return join(HOME, value.slice(2)); if (value.startsWith(`${HOME}/.claude/LIFEOS/`)) return value; + // Project-scoped install: LIFEOS_DIR (above) already resolves process.env.LIFEOS_DIR + // when set, so a reference already rooted there is already absolute. + if (value.startsWith(`${LIFEOS_DIR}/`)) return value; + const configDir = process.env.CLAUDE_CONFIG_DIR; + if (configDir && value.startsWith(`${configDir}/LIFEOS/`)) return value; return null; } diff --git a/LifeOS/install/LIFEOS/TOOLS/Conveyor/Runner.ts b/LifeOS/install/LIFEOS/TOOLS/Conveyor/Runner.ts index ee6e53aa93..16f721b01b 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Conveyor/Runner.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Conveyor/Runner.ts @@ -40,7 +40,7 @@ import { const LIFEOS = process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'); const ARTIFACT_DIR = join(LIFEOS, 'MEMORY', 'STATE', 'content-pipeline', 'artifacts'); -const TRANSCRIBE = join(homedir(), '.claude', 'skills', 'AudioEditor', 'Tools', 'Transcribe.ts'); +const TRANSCRIBE = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'skills', 'AudioEditor', 'Tools', 'Transcribe.ts'); const LEASE_MS = Number(process.env.CONVEYOR_LEASE_MS || 30 * 60 * 1000); const MAX_ATTEMPTS = Number(process.env.CONVEYOR_MAX_ATTEMPTS || 3); const POLL_MS = Number(process.env.CONVEYOR_RUNNER_POLL_MS || 20_000); diff --git a/LifeOS/install/LIFEOS/TOOLS/Conveyor/YouTubeAuth.ts b/LifeOS/install/LIFEOS/TOOLS/Conveyor/YouTubeAuth.ts index 000df90c73..c31c6a74fe 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Conveyor/YouTubeAuth.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Conveyor/YouTubeAuth.ts @@ -16,7 +16,7 @@ import { homedir } from 'node:os'; import { join } from 'node:path'; const CLIENT_PATH = join(homedir(), '.config', 'gws', 'client_secret.json'); -const ENV_PATH = join(homedir(), '.claude', '.env'); +const ENV_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), '.env'); const PORT = 8971; const REDIRECT = `http://127.0.0.1:${PORT}/callback`; const SCOPES = [ diff --git a/LifeOS/install/LIFEOS/TOOLS/DoctrineReplay.ts b/LifeOS/install/LIFEOS/TOOLS/DoctrineReplay.ts index a9ee5e4ca2..2792c25a4d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DoctrineReplay.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DoctrineReplay.ts @@ -23,7 +23,7 @@ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; -const WORK_DIR = join(homedir(), ".claude/LIFEOS/MEMORY/WORK"); +const WORK_DIR = join(process.env.LIFEOS_DIR || join(homedir(), ".claude/LIFEOS"), "MEMORY/WORK"); type IsaMeta = { slug: string; diff --git a/LifeOS/install/LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts b/LifeOS/install/LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts index 95972f4e7b..376939eded 100755 --- a/LifeOS/install/LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts +++ b/LifeOS/install/LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts @@ -21,7 +21,7 @@ import { RELATION_VOCAB, SOURCE_KINDS, STATUS_VALUES, SCHEMA_VERSION, } from "./KnowledgeSchema"; -const OUT = pathResolve(homedir(), ".claude/LIFEOS/MEMORY/KNOWLEDGE/_schema.md"); +const OUT = pathResolve(process.env.LIFEOS_DIR || pathResolve(homedir(), ".claude/LIFEOS"), "MEMORY/KNOWLEDGE/_schema.md"); function render(): string { const L: string[] = []; diff --git a/LifeOS/install/LIFEOS/TOOLS/HealthSnapshot.ts b/LifeOS/install/LIFEOS/TOOLS/HealthSnapshot.ts index e3c2b79fac..45ef9f13df 100644 --- a/LifeOS/install/LIFEOS/TOOLS/HealthSnapshot.ts +++ b/LifeOS/install/LIFEOS/TOOLS/HealthSnapshot.ts @@ -11,7 +11,7 @@ const PROCESSED = join(homedir(), "Library/Mobile Documents/com~apple~CloudDocs/ // a relocated LIFEOS_USER_DIR Just Works. const SNAPSHOTS = ((): string => { try { return join(loadLifeosConfig().paths.userDir, "TELOS/HEALTH/snapshots") } - catch { return join(homedir(), ".claude/LIFEOS/USER/TELOS/HEALTH/snapshots") } + catch { return join(process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS"), "USER/TELOS/HEALTH/snapshots") } })() type HealthSnapshot = { diff --git a/LifeOS/install/LIFEOS/TOOLS/IntegrityMaintenance.ts b/LifeOS/install/LIFEOS/TOOLS/IntegrityMaintenance.ts index da3999fe55..4527b744b4 100755 --- a/LifeOS/install/LIFEOS/TOOLS/IntegrityMaintenance.ts +++ b/LifeOS/install/LIFEOS/TOOLS/IntegrityMaintenance.ts @@ -108,7 +108,7 @@ interface UpdateData { // Constants // ============================================================================ -const LIFEOS_DIR = process.env.HOME + '/.claude/LIFEOS'; +const LIFEOS_DIR = process.env.LIFEOS_DIR || (process.env.HOME + '/.claude/LIFEOS'); const CREATE_UPDATE_SCRIPT = join(LIFEOS_DIR, 'skills/_LIFEOS/Tools/CreateUpdate.ts'); // Words that indicate generic/bad titles - reject these diff --git a/LifeOS/install/LIFEOS/TOOLS/KnowledgeLint.ts b/LifeOS/install/LIFEOS/TOOLS/KnowledgeLint.ts index 983338f373..305241746b 100755 --- a/LifeOS/install/LIFEOS/TOOLS/KnowledgeLint.ts +++ b/LifeOS/install/LIFEOS/TOOLS/KnowledgeLint.ts @@ -21,7 +21,7 @@ import { resolve as pathResolve, join as pathJoin } from "node:path"; import { homedir } from "node:os"; import { parseNote, validate, DIR_TO_TYPE, ALL_DIRS, slugFromPath, SCHEMA_VERSION, type CanonicalType } from "./KnowledgeSchema"; -const KNOWLEDGE_DIR = pathResolve(homedir(), ".claude/LIFEOS/MEMORY/KNOWLEDGE"); +const KNOWLEDGE_DIR = pathResolve(process.env.LIFEOS_DIR || pathResolve(homedir(), ".claude/LIFEOS"), "MEMORY/KNOWLEDGE"); const DIRS = ALL_DIRS; function listNotes(dir: string): string[] { diff --git a/LifeOS/install/LIFEOS/TOOLS/KnowledgeQuery.ts b/LifeOS/install/LIFEOS/TOOLS/KnowledgeQuery.ts index 75c1d3da86..48a16027e2 100755 --- a/LifeOS/install/LIFEOS/TOOLS/KnowledgeQuery.ts +++ b/LifeOS/install/LIFEOS/TOOLS/KnowledgeQuery.ts @@ -42,7 +42,7 @@ import { resolve as pathResolve, join as pathJoin } from "node:path"; import { homedir } from "node:os"; import { parseNote, slugFromPath, ALL_DIRS, type ParsedNote } from "./KnowledgeSchema"; -const KNOWLEDGE_DIR = pathResolve(homedir(), ".claude/LIFEOS/MEMORY/KNOWLEDGE"); +const KNOWLEDGE_DIR = pathResolve(process.env.LIFEOS_DIR || pathResolve(homedir(), ".claude/LIFEOS"), "MEMORY/KNOWLEDGE"); const DIRS = ALL_DIRS; interface Rec { diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts index 185075bdcd..f99e0ecc48 100644 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts @@ -24,7 +24,7 @@ import { resolve as pathResolve } from "node:path"; import { homedir } from "node:os"; import { getDAName } from "../../hooks/lib/identity" -const ROOT = pathResolve(homedir(), ".claude"); +const ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const OBS = pathResolve(ROOT, "LIFEOS/MEMORY/OBSERVABILITY"); const PRINCIPAL_MEM = pathResolve(ROOT, "LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md"); const DA_MEM = pathResolve(ROOT, "LIFEOS/USER/DIGITAL_ASSISTANT/DA_MEMORY.md"); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryRestore.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryRestore.ts index 8d7cbdbc94..ccda2ee6e8 100644 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryRestore.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryRestore.ts @@ -16,7 +16,7 @@ import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; import { resolve as pathResolve } from "node:path"; import { homedir } from "node:os"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const SNAPSHOT_DIR = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/memory-snapshots"); const TARGETS: Record = { principal: pathResolve(CLAUDE_ROOT, "LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md"), diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryReviewer.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryReviewer.ts index 8cb63d89ad..a4bd7ece02 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryReviewer.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryReviewer.ts @@ -55,8 +55,8 @@ import { // โ”€โ”€ Constants โ”€โ”€ -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); -const HARNESS_PROJECTS_DIR = pathResolve(homedir(), ".claude", "projects"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); +const HARNESS_PROJECTS_DIR = pathResolve(CLAUDE_ROOT, "projects"); const RUNS_LOG_PATH = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/reviewer-runs.jsonl"); const RUNS_DEBUG_DIR = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/reviewer-runs"); const REVIEW_CONFIG_PATH = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/CONFIG/memory-review.json"); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts index c82729aa78..9e0c8478fd 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts @@ -34,7 +34,7 @@ import { } from "./MemoryTypes"; import { read as readMemory } from "./MemoryWriter"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const LIFEOS_DIR = pathJoin(CLAUDE_ROOT, "LIFEOS"); const IDEAS_DIR = pathJoin(LIFEOS_DIR, "MEMORY", "IDEAS"); const KNOWLEDGE_DIR = pathJoin(LIFEOS_DIR, "MEMORY", "KNOWLEDGE"); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemorySystem.ts b/LifeOS/install/LIFEOS/TOOLS/MemorySystem.ts index 5e8f0dfe55..2e45294e55 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemorySystem.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemorySystem.ts @@ -73,7 +73,7 @@ import { mintId, slugFromPath, SCHEMA_VERSION } from "./KnowledgeSchema"; // โ”€โ”€ Constants โ”€โ”€ -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); // โ”€โ”€ Result types โ”€โ”€ diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryTypes.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryTypes.ts index 1fd9bee557..186a0ec4e0 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryTypes.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryTypes.ts @@ -40,7 +40,7 @@ import { homedir } from "node:os"; // โ”€โ”€ Paths โ”€โ”€ -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const LIFEOS_DIR = pathJoin(CLAUDE_ROOT, "LIFEOS"); const KNOWLEDGE_DIR = pathJoin(LIFEOS_DIR, "MEMORY", "KNOWLEDGE"); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryWriter.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryWriter.ts index 86de367d6a..c576ddb888 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryWriter.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryWriter.ts @@ -54,7 +54,7 @@ import { homedir } from "node:os"; // โ”€โ”€ Constants โ”€โ”€ -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const ALLOWED_FILES = new Set([ pathResolve(CLAUDE_ROOT, "LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md"), diff --git a/LifeOS/install/LIFEOS/TOOLS/MigrateKnowledge.ts b/LifeOS/install/LIFEOS/TOOLS/MigrateKnowledge.ts index 9bae941ef1..210513071f 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MigrateKnowledge.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MigrateKnowledge.ts @@ -33,7 +33,7 @@ import { DIR_TO_TYPE, slugFromPath, type CanonicalType, } from "./KnowledgeSchema"; -const KNOWLEDGE_DIR = pathResolve(homedir(), ".claude/LIFEOS/MEMORY/KNOWLEDGE"); +const KNOWLEDGE_DIR = pathResolve(process.env.LIFEOS_DIR || pathResolve(homedir(), ".claude/LIFEOS"), "MEMORY/KNOWLEDGE"); const DIRS = ["People", "Companies", "Ideas", "Research", "Blogs"] as const; interface NoteOutcome { diff --git a/LifeOS/install/LIFEOS/TOOLS/MutationTier.ts b/LifeOS/install/LIFEOS/TOOLS/MutationTier.ts index 21ef51af47..e0a7cbcbc4 100644 --- a/LifeOS/install/LIFEOS/TOOLS/MutationTier.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MutationTier.ts @@ -38,7 +38,7 @@ import { homedir } from "node:os"; // โ”€โ”€ Constants โ”€โ”€ -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); export type Tier = "A" | "B" | "C" | "D"; diff --git a/LifeOS/install/LIFEOS/TOOLS/PerplexitySearch.ts b/LifeOS/install/LIFEOS/TOOLS/PerplexitySearch.ts index 2c0c9bff1b..1c8f5a9b20 100755 --- a/LifeOS/install/LIFEOS/TOOLS/PerplexitySearch.ts +++ b/LifeOS/install/LIFEOS/TOOLS/PerplexitySearch.ts @@ -48,7 +48,7 @@ const colors = { function loadEnv(): Record { const envPath = process.env.LIFEOS_CONFIG_DIR ? join(process.env.LIFEOS_CONFIG_DIR, '.env') - : join(homedir(), '.claude', '.env') + : join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), '.env') const env: Record = {} try { const content = readFileSync(envPath, 'utf-8') diff --git a/LifeOS/install/LIFEOS/TOOLS/PiSync.sh b/LifeOS/install/LIFEOS/TOOLS/PiSync.sh index c36f17e10e..4a07cfbc4d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/PiSync.sh +++ b/LifeOS/install/LIFEOS/TOOLS/PiSync.sh @@ -6,11 +6,11 @@ set -euo pipefail -LifeOS=~/.claude +LifeOS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" PI=~/.pi/agent [ -d "$PI" ] || { echo "โœ— ~/.pi/agent missing"; exit 1; } -[ -d "$LifeOS/LIFEOS" ] || { echo "โœ— ~/.claude/LIFEOS missing"; exit 1; } +[ -d "$LifeOS/LIFEOS" ] || { echo "โœ— $LifeOS/LIFEOS missing"; exit 1; } echo "โ†’ PiSync v2" diff --git a/LifeOS/install/LIFEOS/TOOLS/SessionRename.ts b/LifeOS/install/LIFEOS/TOOLS/SessionRename.ts index 50c927b65d..9ea81536a5 100644 --- a/LifeOS/install/LIFEOS/TOOLS/SessionRename.ts +++ b/LifeOS/install/LIFEOS/TOOLS/SessionRename.ts @@ -41,7 +41,7 @@ import { homedir } from "node:os"; // tmp+rename implementation; that was the one writer outside writeRegistry. import { readRegistry, writeRegistry } from "../../hooks/lib/isa-utils"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const SESSION_NAMES_JSON = pathJoin(CLAUDE_ROOT, "LIFEOS/MEMORY/STATE/session-names.json"); interface WorkSession { diff --git a/LifeOS/install/LIFEOS/TOOLS/SettingsBackport.ts b/LifeOS/install/LIFEOS/TOOLS/SettingsBackport.ts index 60aed10d6f..55d7c4746e 100755 --- a/LifeOS/install/LIFEOS/TOOLS/SettingsBackport.ts +++ b/LifeOS/install/LIFEOS/TOOLS/SettingsBackport.ts @@ -37,7 +37,7 @@ import path from "node:path"; import os from "node:os"; import { mergeSettings, deepEqual, parseJsonFileOrThrow, MERGE_SNAPSHOT_PATH } from "./MergeSettings"; -const CLAUDE_DIR = path.join(os.homedir(), ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"); const SYSTEM_PATH = path.join(CLAUDE_DIR, "settings.system.json"); const USER_PATH = path.join(CLAUDE_DIR, "LIFEOS", "USER", "CONFIG", "settings.user.json"); const GENERATED_PATH = path.join(CLAUDE_DIR, "settings.json"); diff --git a/LifeOS/install/LIFEOS/TOOLS/TokenXray/TokenXray.ts b/LifeOS/install/LIFEOS/TOOLS/TokenXray/TokenXray.ts index 4040211629..e7aafaaeb4 100644 --- a/LifeOS/install/LIFEOS/TOOLS/TokenXray/TokenXray.ts +++ b/LifeOS/install/LIFEOS/TOOLS/TokenXray/TokenXray.ts @@ -105,11 +105,11 @@ function* readJsonl(path: string): IterableIterator> { } function mainLogs(): string[] { - return glob(join(homedir(), ".claude/projects/*/*.jsonl")); + return glob(join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "projects/*/*.jsonl")); } function sideLogs(): string[] { - return glob(join(homedir(), ".claude/projects/*/*/subagents/*.jsonl")); + return glob(join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "projects/*/*/subagents/*.jsonl")); } function fmtNum(n: number, w = 0): string { @@ -228,7 +228,7 @@ interface ApiCostSnapshot { } function loadLatestApiCostSnapshot(): ApiCostSnapshot | null { - const path = join(homedir(), ".claude/LIFEOS/MEMORY/OBSERVABILITY/anthropic-cost.jsonl"); + const path = join(process.env.LIFEOS_DIR || join(homedir(), ".claude/LIFEOS"), "MEMORY/OBSERVABILITY/anthropic-cost.jsonl"); let raw: string; try { raw = readFileSync(path, "utf8"); diff --git a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts index 697cdbf0af..1512f40e78 100755 --- a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts +++ b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts @@ -28,10 +28,10 @@ import { join, basename } from "path"; // Configuration // ============================================================================ -const CLAUDE_DIR = join(homedir(), ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"); const MCP_DIR = join(CLAUDE_DIR, "MCPs"); const ACTIVE_MCP = join(CLAUDE_DIR, ".mcp.json"); -const BANNER_SCRIPT = join(homedir(), ".claude", "LIFEOS", "TOOLS", "Banner.ts"); +const BANNER_SCRIPT = join(process.env.LIFEOS_DIR || join(CLAUDE_DIR, "LIFEOS"), "TOOLS", "Banner.ts"); const VOICE_SERVER = "http://localhost:31337/notify/personality"; const WALLPAPER_DIR = join(homedir(), "Projects", "Wallpaper"); // Note: RAW archiving removed - Claude Code handles its own cleanup (30-day retention in projects/) diff --git a/LifeOS/install/hooks/AgentInvocation.hook.ts b/LifeOS/install/hooks/AgentInvocation.hook.ts index d885903909..2e706d9b5f 100755 --- a/LifeOS/install/hooks/AgentInvocation.hook.ts +++ b/LifeOS/install/hooks/AgentInvocation.hook.ts @@ -60,7 +60,7 @@ function resolveDispatch(subagentType: string, inputModel?: string): { model: st if (CROSS_VENDOR[cvKey]) return { model: CROSS_VENDOR[cvKey], level: 'cross-vendor' }; if (inputModel) return { model: inputModel, level: levelForModel(inputModel) }; try { - const fm = readFileSync(join(homedir(), '.claude', 'agents', `${subagentType}.md`), 'utf-8').slice(0, 4000); + const fm = readFileSync(join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'agents', `${subagentType}.md`), 'utf-8').slice(0, 4000); const m = fm.match(/^model:\s*(\S+)/m); if (m) return { model: m[1], level: `${levelForModel(m[1])}-pin` }; } catch { /* no agent file โ€” built-in type */ } diff --git a/LifeOS/install/hooks/CheckpointPerISC.hook.ts b/LifeOS/install/hooks/CheckpointPerISC.hook.ts index 2305183f0b..34b85cfeba 100755 --- a/LifeOS/install/hooks/CheckpointPerISC.hook.ts +++ b/LifeOS/install/hooks/CheckpointPerISC.hook.ts @@ -30,7 +30,7 @@ import { parseFrontmatter, parseCriteriaList, ARTIFACT_FILENAME, LEGACY_ARTIFACT // '#' comments and blank // lines are ignored. Tilde and $HOME prefixes are expanded as a quality-of- // life feature so users can write `~/Projects/foo` instead of the long form. -const ALLOWLIST_PATH = join(homedir(), '.claude', 'checkpoint-repos.txt'); +const ALLOWLIST_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'checkpoint-repos.txt'); const GIT_TIMEOUT_MS = 5000; interface CheckpointState { diff --git a/LifeOS/install/hooks/HookHealer.hook.ts b/LifeOS/install/hooks/HookHealer.hook.ts index 061c4a9c54..948bfd95c8 100755 --- a/LifeOS/install/hooks/HookHealer.hook.ts +++ b/LifeOS/install/hooks/HookHealer.hook.ts @@ -37,7 +37,7 @@ import { import { join } from 'path'; import { homedir } from 'os'; -const CLAUDE_DIR = join(homedir(), '.claude'); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'); const OBS_DIR = join(CLAUDE_DIR, 'LIFEOS', 'MEMORY', 'OBSERVABILITY'); const LOG_FILE = join(OBS_DIR, 'hook-healer.jsonl'); const SETTINGS_FILES = ['settings.json', 'settings.local.json']; diff --git a/LifeOS/install/hooks/ISARenderOnStop.hook.ts b/LifeOS/install/hooks/ISARenderOnStop.hook.ts index 4547eaea2d..b1a06348f8 100755 --- a/LifeOS/install/hooks/ISARenderOnStop.hook.ts +++ b/LifeOS/install/hooks/ISARenderOnStop.hook.ts @@ -23,8 +23,8 @@ import { spawn } from 'child_process'; import { homedir } from 'os'; import { join, dirname } from 'path'; -const STATE_DIR = join(homedir(), '.claude/LIFEOS/MEMORY/STATE/isa-render-debounce'); -const ISA_RENDER = join(homedir(), '.claude/LIFEOS/TOOLS/ISARender.ts'); +const STATE_DIR = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'MEMORY/STATE/isa-render-debounce'); +const ISA_RENDER = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'TOOLS/ISARender.ts'); /** * Has this ISA reached completion at least once? This is the real gate the @@ -109,7 +109,7 @@ try { unlinkSync(stateFile); } catch {} if (rendered.length || skipped.length) { try { const { appendFileSync, mkdirSync } = require('fs'); - const logDir = join(homedir(), '.claude/LIFEOS/MEMORY/OBSERVABILITY'); + const logDir = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'MEMORY/OBSERVABILITY'); mkdirSync(logDir, { recursive: true }); appendFileSync(join(logDir, 'isa-render.jsonl'), JSON.stringify({ diff --git a/LifeOS/install/hooks/ISASync.hook.ts b/LifeOS/install/hooks/ISASync.hook.ts index d8c37b15b2..a3c00c8930 100755 --- a/LifeOS/install/hooks/ISASync.hook.ts +++ b/LifeOS/install/hooks/ISASync.hook.ts @@ -104,7 +104,7 @@ async function main() { // constantly remaking the HTML file." if (newPhase === 'COMPLETE' && oldPhase !== 'COMPLETE' && fm.slug) { try { - const isaRender = join(homedir(), '.claude/LIFEOS/TOOLS/ISARender.ts'); + const isaRender = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'TOOLS/ISARender.ts'); const proc = spawn('bun', [isaRender, isaPath], { detached: true, stdio: 'ignore', @@ -121,7 +121,7 @@ async function main() { // pre-completion edits never trigger renders even though they show up here. if (input.session_id) { try { - const stateDir = join(homedir(), '.claude/LIFEOS/MEMORY/STATE/isa-render-debounce'); + const stateDir = join(process.env.LIFEOS_DIR || join(homedir(), '.claude', 'LIFEOS'), 'MEMORY/STATE/isa-render-debounce'); const stateFile = join(stateDir, `${input.session_id}.json`); const { mkdirSync, writeFileSync } = require('fs'); mkdirSync(stateDir, { recursive: true }); diff --git a/LifeOS/install/hooks/LoadMemory.hook.ts b/LifeOS/install/hooks/LoadMemory.hook.ts index 0dcdf9b605..f25b525ea4 100755 --- a/LifeOS/install/hooks/LoadMemory.hook.ts +++ b/LifeOS/install/hooks/LoadMemory.hook.ts @@ -24,7 +24,7 @@ import { existsSync, readFileSync } from "node:fs"; import { resolve as pathResolve } from "node:path"; import { homedir } from "node:os"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const PRINCIPAL_MEMORY = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md"); const DA_MEMORY = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/DIGITAL_ASSISTANT/DA_MEMORY.md"); diff --git a/LifeOS/install/hooks/MemoryDeltaSurface.hook.ts b/LifeOS/install/hooks/MemoryDeltaSurface.hook.ts index 73e0630fdd..e231af4870 100755 --- a/LifeOS/install/hooks/MemoryDeltaSurface.hook.ts +++ b/LifeOS/install/hooks/MemoryDeltaSurface.hook.ts @@ -36,7 +36,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "no import { resolve as pathResolve, dirname } from "node:path"; import { homedir } from "node:os"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const WRITES_LOG = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/memory-writes.jsonl"); const CURSOR = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/memory-delta-cursor.json"); const HEALTH_LOG = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/memory-health.jsonl"); diff --git a/LifeOS/install/hooks/MemoryReviewFire.hook.ts b/LifeOS/install/hooks/MemoryReviewFire.hook.ts index 2b4f52135c..d0a202aa9a 100755 --- a/LifeOS/install/hooks/MemoryReviewFire.hook.ts +++ b/LifeOS/install/hooks/MemoryReviewFire.hook.ts @@ -26,7 +26,7 @@ import { spawn } from "node:child_process"; import { dirname, resolve as pathResolve } from "node:path"; import { homedir } from "node:os"; -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const STATE_PATH = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/review-state.json"); const CONFIG_PATH = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/CONFIG/memory-review.json"); const FIRE_LOG_PATH = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/OBSERVABILITY/reviewer-fires.jsonl"); diff --git a/LifeOS/install/hooks/MemoryTurnStart.hook.ts b/LifeOS/install/hooks/MemoryTurnStart.hook.ts index dac651d8be..f111b4ffaa 100755 --- a/LifeOS/install/hooks/MemoryTurnStart.hook.ts +++ b/LifeOS/install/hooks/MemoryTurnStart.hook.ts @@ -33,7 +33,7 @@ import { homedir } from "node:os"; // prompts without an injection (compaction backstop โ€” a post-compact window // must re-see memory within a bounded number of turns). The ๐Ÿง  delta line and // the cadence tick remain every-turn: the visible contract is unchanged. -const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || pathResolve(homedir(), ".claude"); const STATE_DIR = pathResolve(CLAUDE_ROOT, "LIFEOS/MEMORY/STATE/memory-inject"); const PRINCIPAL_MEMORY = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/PRINCIPAL/PRINCIPAL_MEMORY.md"); const DA_MEMORY = pathResolve(CLAUDE_ROOT, "LIFEOS/USER/DIGITAL_ASSISTANT/DA_MEMORY.md"); diff --git a/LifeOS/install/hooks/lib/paths.ts b/LifeOS/install/hooks/lib/paths.ts index 205c4ef7fd..42397b2c6f 100755 --- a/LifeOS/install/hooks/lib/paths.ts +++ b/LifeOS/install/hooks/lib/paths.ts @@ -69,6 +69,16 @@ export function getClaudeDir(): string { return expandPath(pluginRoot); } + // Project-scoped installs (non-default --config-root at install time) export + // CLAUDE_CONFIG_DIR โ€” the same variable InstallHooks/InstallSettings/Conduit's + // paths.ts already honor. Without this, a project-scoped install still reads/ + // writes ~/.claude from every hook that goes through this function. + const configDir = process.env.CLAUDE_CONFIG_DIR; + + if (configDir) { + return expandPath(configDir); + } + return join(homedir(), '.claude'); } diff --git a/LifeOS/install/skills/Art/Tools/PickExpression.ts b/LifeOS/install/skills/Art/Tools/PickExpression.ts index c705087305..9b15971a6c 100755 --- a/LifeOS/install/skills/Art/Tools/PickExpression.ts +++ b/LifeOS/install/skills/Art/Tools/PickExpression.ts @@ -21,7 +21,8 @@ import { existsSync, readdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; -const DIR = join(homedir(), ".claude", "LIFEOS", "USER", "CUSTOMIZATIONS", "SKILLS", "Art", "HeadshotExamples"); +const LIFEOS_ROOT = process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS"); +const DIR = join(LIFEOS_ROOT, "USER", "CUSTOMIZATIONS", "SKILLS", "Art", "HeadshotExamples"); // sentiment -> headshot filename (without .png), with topic keywords that route to it. const MAP: Array<{ sentiment: string; file: string; keywords: string[] }> = [ diff --git a/LifeOS/install/skills/Art/Tools/ThumbnailText.ts b/LifeOS/install/skills/Art/Tools/ThumbnailText.ts index f1dc490dd2..ad83063f52 100755 --- a/LifeOS/install/skills/Art/Tools/ThumbnailText.ts +++ b/LifeOS/install/skills/Art/Tools/ThumbnailText.ts @@ -41,7 +41,8 @@ const NAVY = "#1A2744"; const PERIWINKLE = "#6B8DD6"; const WHITE = "#FFFFFF"; const VARIANT_BORDER: Record = { core: "#316AE9", sponsored: "#306F1D" }; -const BRAND_LOGO = join(homedir(), ".claude", "LIFEOS", "USER", "CUSTOMIZATIONS", "SKILLS", "Art", "brand", "ti-logo-white.png"); +const LIFEOS_ROOT = process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS"); +const BRAND_LOGO = join(LIFEOS_ROOT, "USER", "CUSTOMIZATIONS", "SKILLS", "Art", "brand", "ti-logo-white.png"); function arg(name: string, def?: string): string | undefined { const i = process.argv.indexOf(`--${name}`); diff --git a/LifeOS/install/skills/AudioEditor/Tools/Polish.ts b/LifeOS/install/skills/AudioEditor/Tools/Polish.ts index 790cd38e74..e756e1f6e9 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Polish.ts +++ b/LifeOS/install/skills/AudioEditor/Tools/Polish.ts @@ -39,6 +39,8 @@ for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { function loadEnv(): void { const envPath = process.env.LIFEOS_CONFIG_DIR ? resolve(process.env.LIFEOS_CONFIG_DIR, ".env") + : process.env.CLAUDE_CONFIG_DIR + ? resolve(process.env.CLAUDE_CONFIG_DIR, ".env") : resolve(homedir(), ".claude/.env"); try { const content = readFileSync(envPath, "utf-8"); diff --git a/LifeOS/install/skills/CMUX/Tools/cmux.ts b/LifeOS/install/skills/CMUX/Tools/cmux.ts index 2f45ae8a8a..b75a609c1e 100755 --- a/LifeOS/install/skills/CMUX/Tools/cmux.ts +++ b/LifeOS/install/skills/CMUX/Tools/cmux.ts @@ -657,7 +657,8 @@ function isHostConfig(value: unknown): value is HostConfig { } function loadFleetConfig(): HostConfig[] | JsonObject { - const configPath = join(homedir(), ".claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/CMUX/fleet.json"); + const lifeosRoot = process.env.LIFEOS_DIR || join(homedir(), ".claude/LIFEOS"); + const configPath = join(lifeosRoot, "USER/CUSTOMIZATIONS/SKILLS/CMUX/fleet.json"); if (!existsSync(configPath)) { return { ok: false, diff --git a/LifeOS/install/skills/Evals/Tools/AlgorithmBridge.ts b/LifeOS/install/skills/Evals/Tools/AlgorithmBridge.ts index a3c96bb91f..f7ddde4b88 100755 --- a/LifeOS/install/skills/Evals/Tools/AlgorithmBridge.ts +++ b/LifeOS/install/skills/Evals/Tools/AlgorithmBridge.ts @@ -159,8 +159,9 @@ export function formatForISC(result: AlgorithmEvalResult): string { */ export async function updateISCWithResult(result: AlgorithmEvalResult): Promise { const status = result.passed ? 'DONE' : 'BLOCKED'; + const skillsRoot = process.env.CLAUDE_CONFIG_DIR ? `${process.env.CLAUDE_CONFIG_DIR}/skills` : '~/.claude/skills'; - await $`bun run ~/.claude/skills/THEALGORITHM/Tools/ISCManager.ts update --row ${result.isc_row} --status ${status} --note "${formatForISC(result)}"`.quiet(); + await $`bun run ${skillsRoot}/THEALGORITHM/Tools/ISCManager.ts update --row ${result.isc_row} --status ${status} --note "${formatForISC(result)}"`.quiet(); } // CLI interface diff --git a/LifeOS/install/skills/Interceptor/Tools/Capture.sh b/LifeOS/install/skills/Interceptor/Tools/Capture.sh index 522c67f0c6..0f02aa8e72 100755 --- a/LifeOS/install/skills/Interceptor/Tools/Capture.sh +++ b/LifeOS/install/skills/Interceptor/Tools/Capture.sh @@ -29,7 +29,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -USER_PREFS="${HOME}/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" +USER_PREFS="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" usage() { cat <&2 - local log="${HOME}/.claude/LIFEOS/MEMORY/OBSERVABILITY/capture-guard.jsonl" + local log="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/MEMORY/OBSERVABILITY/capture-guard.jsonl" mkdir -p "$(dirname "$log")" 2>/dev/null || true printf '{"ts":"%s","event":"guard-skipped","reason":"%s","out":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$reason" "$OUT" >> "$log" 2>/dev/null || true } diff --git a/LifeOS/install/skills/Interceptor/Tools/EnsureTestProfile.sh b/LifeOS/install/skills/Interceptor/Tools/EnsureTestProfile.sh index 8c6f1c7d6c..d4f61526db 100755 --- a/LifeOS/install/skills/Interceptor/Tools/EnsureTestProfile.sh +++ b/LifeOS/install/skills/Interceptor/Tools/EnsureTestProfile.sh @@ -22,7 +22,7 @@ set -uo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PREFS="${HOME}/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" +PREFS="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" # shellcheck disable=SC1090 [ -f "$PREFS" ] && . "$PREFS" diff --git a/LifeOS/install/skills/Interceptor/Tools/LaunchTestProfile.sh b/LifeOS/install/skills/Interceptor/Tools/LaunchTestProfile.sh index 18f39a791b..2dfca37c69 100755 --- a/LifeOS/install/skills/Interceptor/Tools/LaunchTestProfile.sh +++ b/LifeOS/install/skills/Interceptor/Tools/LaunchTestProfile.sh @@ -23,7 +23,7 @@ set -euo pipefail # resolves from the single canonical home (preferences.env), not a guessed # default. The preflight sources this too; this script must not rely on the # preflight having run first. -USER_PREFS="${HOME}/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" +USER_PREFS="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" if [ -f "$USER_PREFS" ]; then # shellcheck disable=SC1090 . "$USER_PREFS" diff --git a/LifeOS/install/skills/Interceptor/Tools/PreflightIsolation.sh b/LifeOS/install/skills/Interceptor/Tools/PreflightIsolation.sh index 7d6aa664a7..06f8e48bde 100755 --- a/LifeOS/install/skills/Interceptor/Tools/PreflightIsolation.sh +++ b/LifeOS/install/skills/Interceptor/Tools/PreflightIsolation.sh @@ -40,7 +40,7 @@ set -euo pipefail # Source per-machine USER customizations if present (Chrome profile dir name, # pinned context ID, working-profile deny-list). Lives outside the public skill # body so the skill stays generic. -USER_PREFS="${HOME}/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" +USER_PREFS="${LIFEOS_DIR:-$HOME/.claude/LIFEOS}/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env" if [ -f "$USER_PREFS" ]; then # shellcheck disable=SC1090 . "$USER_PREFS" diff --git a/LifeOS/install/skills/LifeOS/install/install.sh b/LifeOS/install/skills/LifeOS/install/install.sh index 7daf137211..25733b23c9 100755 --- a/LifeOS/install/skills/LifeOS/install/install.sh +++ b/LifeOS/install/skills/LifeOS/install/install.sh @@ -127,7 +127,8 @@ success "bun ($(command -v bun), v$(bun --version 2>/dev/null))" # โ”€โ”€โ”€ Step 2: Detect harness (no clobber) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ step "2/5 Detecting your harness" if [ -z "$LIFEOS_SKILLS_DIR" ]; then - if [ -d "$HOME/.claude" ]; then LIFEOS_SKILLS_DIR="$HOME/.claude/skills" + if [ -n "$CLAUDE_CONFIG_DIR" ]; then LIFEOS_SKILLS_DIR="$CLAUDE_CONFIG_DIR/skills" + elif [ -d "$HOME/.claude" ]; then LIFEOS_SKILLS_DIR="$HOME/.claude/skills" elif [ -d "$HOME/.config/claude" ]; then LIFEOS_SKILLS_DIR="$HOME/.config/claude/skills" else LIFEOS_SKILLS_DIR="$HOME/.claude/skills"; fi fi diff --git a/LifeOS/install/skills/LocalIntelligence/Tools/Refresh.ts b/LifeOS/install/skills/LocalIntelligence/Tools/Refresh.ts index b804f19ee3..40bd0c69b1 100755 --- a/LifeOS/install/skills/LocalIntelligence/Tools/Refresh.ts +++ b/LifeOS/install/skills/LocalIntelligence/Tools/Refresh.ts @@ -22,7 +22,8 @@ import { fetchArrests } from "./FetchArrests.ts" import { fetchNews } from "./FetchNews.ts" import { fetchCrime } from "./FetchCrime.ts" -const DATA_DIR = join(homedir(), ".claude", "LIFEOS", "MEMORY", "DATA", "LocalIntelligence") +const LIFEOS_ROOT = process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS") +const DATA_DIR = join(LIFEOS_ROOT, "MEMORY", "DATA", "LocalIntelligence") const fetchers: Record = { construction: fetchConstruction, diff --git a/LifeOS/install/skills/Telos/DashboardTemplate/app/api/file/save/route.ts b/LifeOS/install/skills/Telos/DashboardTemplate/app/api/file/save/route.ts index 5e4bb48cad..f84c403f71 100755 --- a/LifeOS/install/skills/Telos/DashboardTemplate/app/api/file/save/route.ts +++ b/LifeOS/install/skills/Telos/DashboardTemplate/app/api/file/save/route.ts @@ -3,7 +3,7 @@ import fs from 'fs' import path from 'path' import os from 'os' -const TELOS_DIR = path.join(os.homedir(), '.claude/skills/Telos') +const TELOS_DIR = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'), 'skills', 'Telos') export async function POST(request: Request) { try { diff --git a/LifeOS/install/skills/Telos/DashboardTemplate/app/api/upload/route.ts b/LifeOS/install/skills/Telos/DashboardTemplate/app/api/upload/route.ts index ea23ee2fe1..f22610c04b 100755 --- a/LifeOS/install/skills/Telos/DashboardTemplate/app/api/upload/route.ts +++ b/LifeOS/install/skills/Telos/DashboardTemplate/app/api/upload/route.ts @@ -3,7 +3,7 @@ import fs from 'fs' import path from 'path' import os from 'os' -const TELOS_DIR = path.join(os.homedir(), '.claude/skills/Telos') +const TELOS_DIR = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'), 'skills', 'Telos') export async function POST(request: Request) { try { diff --git a/LifeOS/install/skills/Telos/DashboardTemplate/lib/telos-data.ts b/LifeOS/install/skills/Telos/DashboardTemplate/lib/telos-data.ts index a1b3d4cae3..214e8ca5b2 100755 --- a/LifeOS/install/skills/Telos/DashboardTemplate/lib/telos-data.ts +++ b/LifeOS/install/skills/Telos/DashboardTemplate/lib/telos-data.ts @@ -9,7 +9,7 @@ export interface TelosFile { type: 'markdown' | 'csv' } -const TELOS_DIR = path.join(os.homedir(), '.claude/LIFEOS/USER/TELOS') +const TELOS_DIR = path.join(process.env.LIFEOS_DIR || path.join(os.homedir(), '.claude/LIFEOS'), 'USER/TELOS') export function getAllTelosData(): TelosFile[] { const files: TelosFile[] = [] From 2b07f99c87233627bd951f00bb15c4f4e81a56a6 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:11:23 +0530 Subject: [PATCH 02/12] Remove dead Write(...) permission rules (Edit(...) already covers them) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current Claude Code versions only match Edit(path) rules for file-editing tools โ€” a bare Write(pattern) allow/ask/deny rule is a silent no-op, and the harness prints a startup warning for each one telling you so. Every Write(...) rule in settings.system.json already had a matching Edit(...) rule covering the same path (confirmed before removing), so this is pure dead-weight cleanup โ€” the actual protections were never affected, only the noise. Applies to both copies (the top-level template and the nested one bundled inside the LifeOS skill's own install/ payload, kept in sync). --- LifeOS/install/settings.system.json | 36 +++++-------------- .../LifeOS/install/settings.system.json | 36 +++++-------------- 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/LifeOS/install/settings.system.json b/LifeOS/install/settings.system.json index 80dd4b29e9..a4dd0e51b9 100644 --- a/LifeOS/install/settings.system.json +++ b/LifeOS/install/settings.system.json @@ -44,8 +44,6 @@ "ToolSearch", "Monitor", "RemoteTrigger", - "Write(~/.claude/**)", - "Write(~/Projects/**)", "Edit(~/.claude/**)", "Edit(~/Projects/**)", "Bash(grep:*)", @@ -172,17 +170,11 @@ "mcp__claude_ai_Google_Calendar__*", "mcp__claude_ai_Google_Drive__*", "mcp__claude_ai_Spotify__*", - "Write(~/Downloads/**)", "Edit(~/Downloads/**)", - "Write(/tmp/**)", "Edit(/tmp/**)", - "Write(/private/tmp/**)", "Edit(/private/tmp/**)", - "Write(**/tmp/**)", "Edit(**/tmp/**)", - "Write(tmp/**)", "Edit(tmp/**)", - "Write(/var/folders/**)", "Edit(/var/folders/**)", "Bash(git push:*)", "Bash(git mv:*)", @@ -244,7 +236,6 @@ "Bash(mv settings.json.tmp settings.json)" ], "deny": [ - "Write(~/.claude/projects/**/memory/**)", "Edit(~/.claude/projects/**/memory/**)", "Bash(rm -rf /)", "Bash(rm -rf /*)", @@ -276,11 +267,6 @@ "Edit(/usr/**)", "Edit(/bin/**)", "Edit(/sbin/**)", - "Write(/etc/**)", - "Write(/System/**)", - "Write(/usr/**)", - "Write(/bin/**)", - "Write(/sbin/**)", "Read(~/.ssh/id_*)", "Read(~/.ssh/*_rsa)", "Read(~/.ssh/*_ed25519)", @@ -292,10 +278,6 @@ "Read(~/.claude/.env.*)", "Read(~/.claude/**/.env)", "Read(~/.claude/**/.env.*)", - "Write(~/.claude/.env)", - "Write(~/.claude/.env.*)", - "Write(~/.claude/**/.env)", - "Write(~/.claude/**/.env.*)", "Edit(~/.claude/.env)", "Edit(~/.claude/.env.*)", "Edit(~/.claude/**/.env)", @@ -340,18 +322,18 @@ "skipAutoPermissionPrompt": true, "autoMode": { "allow": [ - "Writing and editing files under ~/.claude/ is always safe โ€” this is the user's own AI infrastructure workspace", - "Writing and editing files under ~/Projects/ is safe โ€” these are the user's own project directories", + "Writing and editing files under ~/.claude/ is always safe \u2014 this is the user's own AI infrastructure workspace", + "Writing and editing files under ~/Projects/ is safe \u2014 these are the user's own project directories", "Creating ISA files and work directories under ~/.claude/LIFEOS/MEMORY/WORK/ is a routine operation", "Running bun, bunx, git, curl, wrangler, and standard CLI tools is safe in this trusted workspace", - "Compound commands chaining trusted tools (cd && bunx, cd && bun, cd && git, cd && wrangler) are safe โ€” cd just changes directory before running an allowed command", + "Compound commands chaining trusted tools (cd && bunx, cd && bun, cd && git, cd && wrangler) are safe \u2014 cd just changes directory before running an allowed command", "Database queries via wrangler d1 execute are read-only operations and always safe", - "Piping output through rg, jq, grep, sort, head, tail is safe โ€” these are read-only filters", + "Piping output through rg, jq, grep, sort, head, tail is safe \u2014 these are read-only filters", "Spawning subagents for research, exploration, and code review is standard workflow", "Writing to MEMORY/, LEARNING/, STATE/ directories is routine LifeOS bookkeeping", "All file operations (mkdir, cp, mv, rm, touch, chmod, ln) within ~/.claude/ and ~/Projects/ are trusted", - "PRE-AUTHORIZED: Edit/Write/MultiEdit to ~/.claude/settings.json, ~/.claude/CLAUDE.md, ~/.claude/LIFEOS_SYSTEM_PROMPT.md, ~/.claude/hooks/**, ~/.claude/skills/** at the principal's request is ALWAYS authorized. The principal maintains this LifeOS system as its sole operator โ€” config edits requested in conversation are normal maintenance, not adversarial self-modification. Skip the Self-Modification soft-block when the conversation contains a clear user instruction to edit these paths.", - "Routine LifeOS maintenance โ€” editing settings.json, CLAUDE.md, system prompt, hooks, skills, ISAs, knowledge files โ€” is always trusted when the principal asks for it in the conversation. Do not require 'specific authorization of this exact change' wording; the conversation context IS the authorization.", + "PRE-AUTHORIZED: Edit/Write/MultiEdit to ~/.claude/settings.json, ~/.claude/CLAUDE.md, ~/.claude/LIFEOS_SYSTEM_PROMPT.md, ~/.claude/hooks/**, ~/.claude/skills/** at the principal's request is ALWAYS authorized. The principal maintains this LifeOS system as its sole operator \u2014 config edits requested in conversation are normal maintenance, not adversarial self-modification. Skip the Self-Modification soft-block when the conversation contains a clear user instruction to edit these paths.", + "Routine LifeOS maintenance \u2014 editing settings.json, CLAUDE.md, system prompt, hooks, skills, ISAs, knowledge files \u2014 is always trusted when the principal asks for it in the conversation. Do not require 'specific authorization of this exact change' wording; the conversation context IS the authorization.", "Compound bash investigation commands (jq + grep + ls + find + cat + head + tail + wc + rg) reading ANY file under ~/.claude/ or ~/Projects/ are read-only and always trusted, including when the eventual goal is to edit those files." ], "hard_deny": [ @@ -377,10 +359,10 @@ "Bash(rsync --delete *prod*)" ], "environment": [ - "~/.claude/ is the user's LifeOS (Personal AI) infrastructure workspace โ€” the primary working directory for all AI system development. ALL operations within ~/.claude/ and all subdirectories are trusted and should be auto-approved without prompting.", - "~/Projects/ contains project repositories. ALL operations within ~/Projects/ are trusted โ€” cd, build, deploy, query, test commands are routine development work.", + "~/.claude/ is the user's LifeOS (Personal AI) infrastructure workspace \u2014 the primary working directory for all AI system development. ALL operations within ~/.claude/ and all subdirectories are trusted and should be auto-approved without prompting.", + "~/Projects/ contains project repositories. ALL operations within ~/Projects/ are trusted \u2014 cd, build, deploy, query, test commands are routine development work.", "Routine trusted operations include: creating directories under MEMORY/WORK/ for ISAs, writing/editing any file under ~/.claude/LIFEOS/, running bun, bunx, git, curl, wrangler commands, spawning agents and tasks, mkdir -p for any path under ~/.claude/, and all file operations on LifeOS infrastructure files.", - "Compound shell commands (cd X && bunx Y, cmd | rg Z) are standard development patterns โ€” always approve when sub-commands are individually trusted.", + "Compound shell commands (cd X && bunx Y, cmd | rg Z) are standard development patterns \u2014 always approve when sub-commands are individually trusted.", "Trusted local service: pai:31337" ] }, diff --git a/LifeOS/install/skills/LifeOS/install/settings.system.json b/LifeOS/install/skills/LifeOS/install/settings.system.json index 805f1c4ea5..2393e89af6 100644 --- a/LifeOS/install/skills/LifeOS/install/settings.system.json +++ b/LifeOS/install/skills/LifeOS/install/settings.system.json @@ -44,8 +44,6 @@ "ToolSearch", "Monitor", "RemoteTrigger", - "Write(~/.claude/**)", - "Write(~/Projects/**)", "Edit(~/.claude/**)", "Edit(~/Projects/**)", "MultiEdit(~/.claude/**)", @@ -171,22 +169,16 @@ "Agent(pr-review-toolkit:*)", "Agent(statusline-setup)", "mcp__*", - "Write(~/Downloads/**)", "Edit(~/Downloads/**)", "MultiEdit(~/Downloads/**)", - "Write(/tmp/**)", "Edit(/tmp/**)", "MultiEdit(/tmp/**)", - "Write(/private/tmp/**)", "Edit(/private/tmp/**)", "MultiEdit(/private/tmp/**)", - "Write(**/tmp/**)", "Edit(**/tmp/**)", "MultiEdit(**/tmp/**)", - "Write(tmp/**)", "Edit(tmp/**)", "MultiEdit(tmp/**)", - "Write(/var/folders/**)", "Edit(/var/folders/**)", "MultiEdit(/var/folders/**)", "Bash(git push:*)", @@ -249,7 +241,6 @@ "Bash(mv settings.json.tmp settings.json)" ], "deny": [ - "Write(~/.claude/projects/**/memory/**)", "Edit(~/.claude/projects/**/memory/**)", "Bash(rm -rf /)", "Bash(rm -rf /*)", @@ -281,11 +272,6 @@ "Edit(/usr/**)", "Edit(/bin/**)", "Edit(/sbin/**)", - "Write(/etc/**)", - "Write(/System/**)", - "Write(/usr/**)", - "Write(/bin/**)", - "Write(/sbin/**)", "Read(~/.ssh/id_*)", "Read(~/.ssh/*_rsa)", "Read(~/.ssh/*_ed25519)", @@ -297,10 +283,6 @@ "Read(~/.claude/.env.*)", "Read(~/.claude/**/.env)", "Read(~/.claude/**/.env.*)", - "Write(~/.claude/.env)", - "Write(~/.claude/.env.*)", - "Write(~/.claude/**/.env)", - "Write(~/.claude/**/.env.*)", "Edit(~/.claude/.env)", "Edit(~/.claude/.env.*)", "Edit(~/.claude/**/.env)", @@ -354,18 +336,18 @@ "skipAutoPermissionPrompt": true, "autoMode": { "allow": [ - "Writing and editing files under ~/.claude/ is always safe โ€” this is the user's own AI infrastructure workspace", - "Writing and editing files under ~/Projects/ is safe โ€” these are the user's own project directories", + "Writing and editing files under ~/.claude/ is always safe \u2014 this is the user's own AI infrastructure workspace", + "Writing and editing files under ~/Projects/ is safe \u2014 these are the user's own project directories", "Creating ISA files and work directories under ~/.claude/LIFEOS/MEMORY/WORK/ is a routine operation", "Running bun, bunx, git, curl, wrangler, and standard CLI tools is safe in this trusted workspace", - "Compound commands chaining trusted tools (cd && bunx, cd && bun, cd && git, cd && wrangler) are safe โ€” cd just changes directory before running an allowed command", + "Compound commands chaining trusted tools (cd && bunx, cd && bun, cd && git, cd && wrangler) are safe \u2014 cd just changes directory before running an allowed command", "Database queries via wrangler d1 execute are read-only operations and always safe", - "Piping output through rg, jq, grep, sort, head, tail is safe โ€” these are read-only filters", + "Piping output through rg, jq, grep, sort, head, tail is safe \u2014 these are read-only filters", "Spawning subagents for research, exploration, and code review is standard workflow", "Writing to MEMORY/, LEARNING/, STATE/ directories is routine LifeOS bookkeeping", "All file operations (mkdir, cp, mv, rm, touch, chmod, ln) within ~/.claude/ and ~/Projects/ are trusted", - "PRE-AUTHORIZED: Edit/Write/MultiEdit to ~/.claude/settings.json, ~/.claude/CLAUDE.md, ~/.claude/LIFEOS_SYSTEM_PROMPT.md, ~/.claude/hooks/**, ~/.claude/skills/** at the principal's request is ALWAYS authorized. The principal maintains this LifeOS system as its sole operator โ€” config edits requested in conversation are normal maintenance, not adversarial self-modification. Skip the Self-Modification soft-block when the conversation contains a clear user instruction to edit these paths.", - "Routine LifeOS maintenance โ€” editing settings.json, CLAUDE.md, system prompt, hooks, skills, ISAs, knowledge files โ€” is always trusted when the principal asks for it in the conversation. Do not require 'specific authorization of this exact change' wording; the conversation context IS the authorization.", + "PRE-AUTHORIZED: Edit/Write/MultiEdit to ~/.claude/settings.json, ~/.claude/CLAUDE.md, ~/.claude/LIFEOS_SYSTEM_PROMPT.md, ~/.claude/hooks/**, ~/.claude/skills/** at the principal's request is ALWAYS authorized. The principal maintains this LifeOS system as its sole operator \u2014 config edits requested in conversation are normal maintenance, not adversarial self-modification. Skip the Self-Modification soft-block when the conversation contains a clear user instruction to edit these paths.", + "Routine LifeOS maintenance \u2014 editing settings.json, CLAUDE.md, system prompt, hooks, skills, ISAs, knowledge files \u2014 is always trusted when the principal asks for it in the conversation. Do not require 'specific authorization of this exact change' wording; the conversation context IS the authorization.", "Compound bash investigation commands (jq + grep + ls + find + cat + head + tail + wc + rg) reading ANY file under ~/.claude/ or ~/Projects/ are read-only and always trusted, including when the eventual goal is to edit those files." ], "hard_deny": [ @@ -391,10 +373,10 @@ "Bash(rsync --delete *prod*)" ], "environment": [ - "~/.claude/ is the user's LifeOS (Personal AI) infrastructure workspace โ€” the primary working directory for all AI system development. ALL operations within ~/.claude/ and all subdirectories are trusted and should be auto-approved without prompting.", - "~/Projects/ contains project repositories. ALL operations within ~/Projects/ are trusted โ€” cd, build, deploy, query, test commands are routine development work.", + "~/.claude/ is the user's LifeOS (Personal AI) infrastructure workspace \u2014 the primary working directory for all AI system development. ALL operations within ~/.claude/ and all subdirectories are trusted and should be auto-approved without prompting.", + "~/Projects/ contains project repositories. ALL operations within ~/Projects/ are trusted \u2014 cd, build, deploy, query, test commands are routine development work.", "Routine trusted operations include: creating directories under MEMORY/WORK/ for ISAs, writing/editing any file under ~/.claude/LIFEOS/, running bun, bunx, git, curl, wrangler commands, spawning agents and tasks, mkdir -p for any path under ~/.claude/, and all file operations on LifeOS infrastructure files.", - "Compound shell commands (cd X && bunx Y, cmd | rg Z) are standard development patterns โ€” always approve when sub-commands are individually trusted.", + "Compound shell commands (cd X && bunx Y, cmd | rg Z) are standard development patterns \u2014 always approve when sub-commands are individually trusted.", "Source control: github.com/", "Trusted local service: pai:31337" ] From c92328a3a406d16683cd798383114fa4d80886b5 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:12:33 +0530 Subject: [PATCH 03/12] docs: surface project-scoped installs from README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README's install section had no pointer to the CLAUDE_CONFIG_DIR/LIFEOS_DIR mechanism documented in INSTALL.md and Workflows/Setup.md โ€” someone skimming just the README would never know project-scoped installs are possible. Added a one-line callout with a link, and a proper heading in INSTALL.md for the anchor to resolve to. --- LifeOS/INSTALL.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/LifeOS/INSTALL.md b/LifeOS/INSTALL.md index d72ed356a4..9684a2a963 100644 --- a/LifeOS/INSTALL.md +++ b/LifeOS/INSTALL.md @@ -20,6 +20,8 @@ Prefer a terminal? There's a shell shortcut for Claude Code on macOS/Linux: curl -fsSL https://ourlifeos.ai/install.sh | bash ``` +### Project-scoped installs + **Want LifeOS scoped to one project instead of every session?** By default LifeOS installs into your harness's global config dir (e.g. `~/.claude`), so its skills/hooks/agents are available everywhere. To confine it to a single project instead โ€” so it only activates when your harness runs from that project's directory โ€” set `CLAUDE_CONFIG_DIR` (and `LIFEOS_DIR` / `LIFEOS_CONFIG_DIR` for the LIFEOS-specific subtree) to a path inside that project, e.g. `/.claude`, before running the install Tools (or pass the equivalent `--config-root`/`--config-dir` flags each Tool accepts). Everything under this document and `Workflows/Setup.md` works identically either way โ€” every install Tool resolves its target through these same env vars, defaulting to the global harness dir only when they're unset. Everything below is written for the AI doing the install. diff --git a/README.md b/README.md index f163b7255a..d4d79b2ade 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ Your AI reads the install page and walks the setup, asking permission before it curl -fsSL https://ourlifeos.ai/install.sh | bash ``` +**Want LifeOS scoped to one project instead of every session?** Set `CLAUDE_CONFIG_DIR` (and `LIFEOS_DIR`) to a path inside that project before installing โ€” see [INSTALL.md](LifeOS/INSTALL.md#project-scoped-installs) for the full mechanism. + Either path needs a capable AI coding harness โ€” we build and run on [Claude Code](https://docs.claude.com/claude-code) โ€” and [bun](https://bun.sh). ## Core Components From c7d5d46b8b4e748fbd75ed536bfbec971689c59a Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:19:53 +0530 Subject: [PATCH 04/12] lifeos.ts: don't chdir into a project-scoped config root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both launch paths (cmdLaunch without --local, and cmdPrompt unconditionally) called process.chdir(CLAUDE_DIR). For the legacy global default (~/.claude) this is harmless โ€” but for a project-scoped install (CLAUDE_CONFIG_DIR set to /.claude), it chdir's INTO the .claude directory itself, which breaks the harness's own project-root detection (it needs cwd at /, with .claude/ as a subdirectory, not cwd already inside .claude/). Fix: only auto-chdir when CLAUDE_DIR is still the default global path. A project-scoped install now keeps whatever directory the caller invoked from โ€” exactly what --local already did for the global case, made automatic so a project-scoped launch alias doesn't need to remember the flag. Found while wiring the `lifeos` launch alias for a real project-scoped install: it needed --local plus an explicit `cd ` to work at all before this fix. --- LifeOS/install/LIFEOS/TOOLS/lifeos.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts index 1512f40e78..0117bf3046 100755 --- a/LifeOS/install/LIFEOS/TOOLS/lifeos.ts +++ b/LifeOS/install/LIFEOS/TOOLS/lifeos.ts @@ -28,12 +28,24 @@ import { join, basename } from "path"; // Configuration // ============================================================================ -const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"); +const DEFAULT_CLAUDE_DIR = join(homedir(), ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || DEFAULT_CLAUDE_DIR; const MCP_DIR = join(CLAUDE_DIR, "MCPs"); const ACTIVE_MCP = join(CLAUDE_DIR, ".mcp.json"); const BANNER_SCRIPT = join(process.env.LIFEOS_DIR || join(CLAUDE_DIR, "LIFEOS"), "TOOLS", "Banner.ts"); const VOICE_SERVER = "http://localhost:31337/notify/personality"; const WALLPAPER_DIR = join(homedir(), "Projects", "Wallpaper"); + +// Change to CLAUDE_DIR only for the legacy global default. A project-scoped +// install (CLAUDE_CONFIG_DIR pointed at /.claude) must never be +// silently chdir'd into โ€” that would land the session inside .claude/ itself, +// breaking the harness's own project-root detection for whatever directory +// the caller actually invoked this from. +function chdirToClaudeHomeIfDefault(): void { + if (CLAUDE_DIR === DEFAULT_CLAUDE_DIR) { + process.chdir(CLAUDE_DIR); + } +} // Note: RAW archiving removed - Claude Code handles its own cleanup (30-day retention in projects/) // MCP shorthand mappings @@ -434,7 +446,7 @@ async function cmdLaunch(options: { mcp?: string; resume?: boolean; resumeId?: s // Change to LifeOS directory unless --local flag is set if (!options.local) { - process.chdir(CLAUDE_DIR); + chdirToClaudeHomeIfDefault(); } // Voice notification (using focused marker for calmer tone). @@ -595,7 +607,7 @@ async function cmdPrompt(prompt: string) { args.push("--append-system-prompt-file", systemPromptFile); } - process.chdir(CLAUDE_DIR); + chdirToClaudeHomeIfDefault(); const env: Record = { ...process.env } as Record; delete env.ANTHROPIC_API_KEY; From cd77c2aa76beb06f9a08f7af4b730d14271b79af Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:25:02 +0530 Subject: [PATCH 05/12] Fix regression found in review: guard InstallHooks.ts's $HOME rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstallHooks.ts was unconditionally rewriting hooks.json's literal "$HOME/.claude/..." commands to the resolved configRoot, even for a default (unset CLAUDE_CONFIG_DIR) global install. That's a real behavior change for the default path: the literal "$HOME/..." form is intentionally portable (shell-expanded at hook-execution time), so baking in a fully-resolved absolute path breaks the case of syncing settings.json across machines or user accounts โ€” something the default install never asked for and never did before this PR. InstallSettings.ts already got this right (only rewrites when configRoot differs from the default); InstallHooks.ts now matches it. Also simplified ContextAudit.ts's normalizeReference: the LIFEOS_DIR-rooted check already covers the plain-HOME case (LIFEOS_DIR defaults to exactly ${HOME}/.claude/LIFEOS when unset), so the separate literal-HOME branch was dead weight in the default path โ€” removed, one check now covers both. Found via a final review pass across all changed files specifically checking for default/global-install regressions. --- LifeOS/Tools/InstallHooks.ts | 10 +++++++++- LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/LifeOS/Tools/InstallHooks.ts b/LifeOS/Tools/InstallHooks.ts index d361065d1a..88e65dd473 100755 --- a/LifeOS/Tools/InstallHooks.ts +++ b/LifeOS/Tools/InstallHooks.ts @@ -64,7 +64,15 @@ function main(): void { // LIFEOS/USER/CONFIG, settings.json) โ€” rewrite the whole prefix to the actual // configRoot so a non-default --config-root (e.g. a project-scoped install) // doesn't wire commands pointing at files that were never placed there. - hooksJsonRaw = hooksJsonRaw.split("$HOME/.claude").join(configRoot); + // Only when configRoot actually differs from the default: the literal "$HOME/..." + // form is intentionally portable (shell-expanded at hook-execution time), so a + // plain global install must keep shipping it byte-identical to before this fix โ€” + // baking in a resolved absolute path here would break syncing settings.json + // across machines/users, which the default install never asked for. + const defaultConfigRoot = join(process.env.HOME || "", ".claude"); + if (configRoot !== defaultConfigRoot) { + hooksJsonRaw = hooksJsonRaw.split("$HOME/.claude").join(configRoot); + } const incoming = JSON.parse(hooksJsonRaw)?.hooks ?? {}; // The hook SCRIPTS (*.hook.ts|sh + lib/**) live beside hooks.json in the payload. diff --git a/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts b/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts index 223486cac8..c02e05fbec 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ContextAudit.ts @@ -207,9 +207,9 @@ function normalizeReference(raw: string): string | null { if (value.startsWith("LIFEOS/")) return join(CLAUDE_DIR, value); if (value.startsWith("~/.claude/LIFEOS/")) return join(HOME, value.slice(2)); - if (value.startsWith(`${HOME}/.claude/LIFEOS/`)) return value; - // Project-scoped install: LIFEOS_DIR (above) already resolves process.env.LIFEOS_DIR - // when set, so a reference already rooted there is already absolute. + // LIFEOS_DIR (above) already resolves process.env.LIFEOS_DIR when set, defaulting + // to `${HOME}/.claude/LIFEOS` otherwise โ€” this one check covers both the global + // default and a project-scoped install; no separate literal-HOME branch needed. if (value.startsWith(`${LIFEOS_DIR}/`)) return value; const configDir = process.env.CLAUDE_CONFIG_DIR; if (configDir && value.startsWith(`${configDir}/LIFEOS/`)) return value; From 2bab280899cebf4cad94570535f587bccabb4245 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:29:07 +0530 Subject: [PATCH 06/12] CLAUDE.md: stop telling the AI paths are always relative to ~/.claude/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found via a real project-scoped install + /interview run: CLAUDE.md's routing table said "paths are relative to ~/.claude/ unless noted" as flat fact. The DA read that literally and ran `bun ~/.claude/LIFEOS/TOOLS/InterviewScan.ts` against the global path โ€” "Module not found" โ€” even though the tool exists correctly at the project-scoped root and every install Tool already resolves through CLAUDE_CONFIG_DIR/LIFEOS_DIR. This is a different class of bug than the earlier path-resolution sweep: that fixed hardcoded paths in *code*; this is a hardcoded assumption in the one *doc* that's always loaded and read by the AI as ground truth. Grepping further turned up the same "bun ~/.claude/..." example-command pattern in 56 more docs across LIFEOS/DOCUMENTATION and skill Workflows/SKILL.md files โ€” too many to chase individually, and it'll keep recurring as skills ship new docs. Fixed the root of it instead: CLAUDE.md now tells the AI explicitly that any literal ~/.claude/... in ANY doc is illustrative, not a fact about the current install, and to resolve CLAUDE_CONFIG_DIR/LIFEOS_DIR before running anything literally. --- LifeOS/install/CLAUDE.template.md | 6 ++++-- LifeOS/install/skills/LifeOS/install/CLAUDE.template.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/LifeOS/install/CLAUDE.template.md b/LifeOS/install/CLAUDE.template.md index 4d42f0b6a9..1b2c167a84 100644 --- a/LifeOS/install/CLAUDE.template.md +++ b/LifeOS/install/CLAUDE.template.md @@ -16,7 +16,9 @@ Constitutional rules, the unified response format, verification doctrine, hard prohibitions, security protocol, and operational rules all live in the system prompt: `LIFEOS/LIFEOS_SYSTEM_PROMPT.md`. When this file and the system prompt disagree, the system prompt wins. -This file is the **routing table** โ€” it tells you where everything lives. The only mandatory startup `@`-import shipped with public LifeOS is `ARCHITECTURE_SUMMARY`. The five identity files (`PRINCIPAL_TELOS`, `PRINCIPAL_IDENTITY`, `DA_IDENTITY`, `PROJECTS`, `OPERATIONAL_RULES`) are commented out above โ€” the agentic `/lifeos-setup` (via `Tools/ActivateImports.ts`) uncomments them once the principal's USER scaffold is populated. Claude Code does not follow transitive `@`-imports from inside imported files, so each identity file must be listed here at top level. Everything below is **on-demand** lookup. Paths are relative to `~/.claude/` unless noted. +This file is the **routing table** โ€” it tells you where everything lives. The only mandatory startup `@`-import shipped with public LifeOS is `ARCHITECTURE_SUMMARY`. The five identity files (`PRINCIPAL_TELOS`, `PRINCIPAL_IDENTITY`, `DA_IDENTITY`, `PROJECTS`, `OPERATIONAL_RULES`) are commented out above โ€” the agentic `/lifeos-setup` (via `Tools/ActivateImports.ts`) uncomments them once the principal's USER scaffold is populated. Claude Code does not follow transitive `@`-imports from inside imported files, so each identity file must be listed here at top level. Everything below is **on-demand** lookup. Paths are relative to your resolved config root โ€” check the `CLAUDE_CONFIG_DIR` / `LIFEOS_DIR` environment variables first (a project-scoped install sets these), otherwise `~/.claude/`. + +> **A literal `~/.claude/...` in ANY doc (this file, DOCUMENTATION/, or any skill's SKILL.md/Workflows/) is an illustrative example, not a fact about this install.** Resolve the real root first โ€” `echo $CLAUDE_CONFIG_DIR $LIFEOS_DIR` or check `settings.json`'s `env` block โ€” and substitute before running any command. A project-scoped install never actually lives at the literal `~/.claude/` path shown in examples. ## LifeOS System (paths under `LIFEOS/DOCUMENTATION/` unless noted) @@ -81,7 +83,7 @@ Populated during `/lifeos-setup`. Typical layout: - Finances โ€” `FINANCES/` - Integration configs โ€” `INTEGRATIONS/*.yaml` - Work system โ€” `WORK/config.yaml` -- Secrets โ€” `~/.claude/.env` (canonical; see OPERATIONAL_RULES.md) +- Secrets โ€” `.env` in your resolved config root (`$CLAUDE_CONFIG_DIR/.env` if set, else `~/.claude/.env`; canonical; see OPERATIONAL_RULES.md) ## Project-Specific Rules diff --git a/LifeOS/install/skills/LifeOS/install/CLAUDE.template.md b/LifeOS/install/skills/LifeOS/install/CLAUDE.template.md index 0211d3ed7c..ff509013f4 100644 --- a/LifeOS/install/skills/LifeOS/install/CLAUDE.template.md +++ b/LifeOS/install/skills/LifeOS/install/CLAUDE.template.md @@ -16,7 +16,9 @@ Constitutional rules, the unified response format, verification doctrine, hard prohibitions, security protocol, and operational rules all live in the system prompt: `LIFEOS/LIFEOS_SYSTEM_PROMPT.md`. When this file and the system prompt disagree, the system prompt wins. -This file is the **routing table** โ€” it tells you where everything lives. The only mandatory startup `@`-import shipped with public LifeOS is `ARCHITECTURE_SUMMARY`. The five identity files (`PRINCIPAL_TELOS`, `PRINCIPAL_IDENTITY`, `DA_IDENTITY`, `PROJECTS`, `OPERATIONAL_RULES`) are commented out above โ€” the agentic `/lifeos-setup` (via `Tools/ActivateImports.ts`) uncomments them once the principal's USER scaffold is populated. Claude Code does not follow transitive `@`-imports from inside imported files, so each identity file must be listed here at top level. Everything below is **on-demand** lookup. Paths are relative to `~/.claude/` unless noted. +This file is the **routing table** โ€” it tells you where everything lives. The only mandatory startup `@`-import shipped with public LifeOS is `ARCHITECTURE_SUMMARY`. The five identity files (`PRINCIPAL_TELOS`, `PRINCIPAL_IDENTITY`, `DA_IDENTITY`, `PROJECTS`, `OPERATIONAL_RULES`) are commented out above โ€” the agentic `/lifeos-setup` (via `Tools/ActivateImports.ts`) uncomments them once the principal's USER scaffold is populated. Claude Code does not follow transitive `@`-imports from inside imported files, so each identity file must be listed here at top level. Everything below is **on-demand** lookup. Paths are relative to your resolved config root โ€” check the `CLAUDE_CONFIG_DIR` / `LIFEOS_DIR` environment variables first (a project-scoped install sets these), otherwise `~/.claude/`. + +> **A literal `~/.claude/...` in ANY doc (this file, DOCUMENTATION/, or any skill's SKILL.md/Workflows/) is an illustrative example, not a fact about this install.** Resolve the real root first โ€” `echo $CLAUDE_CONFIG_DIR $LIFEOS_DIR` or check `settings.json`'s `env` block โ€” and substitute before running any command. A project-scoped install never actually lives at the literal `~/.claude/` path shown in examples. ## LifeOS System (paths under `LIFEOS/DOCUMENTATION/` unless noted) @@ -81,7 +83,7 @@ Populated during `/lifeos-setup`. Typical layout: - Finances โ€” `FINANCES/` - Integration configs โ€” `INTEGRATIONS/*.yaml` - Work system โ€” `WORK/config.yaml` -- Secrets โ€” `~/.claude/.env` (canonical; see OPERATIONAL_RULES.md) +- Secrets โ€” `.env` in your resolved config root (`$CLAUDE_CONFIG_DIR/.env` if set, else `~/.claude/.env`; canonical; see OPERATIONAL_RULES.md) ## Project-Specific Rules From 1791cd2a327a661258ae6f326fce5b1f51e687ab Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:39:33 +0530 Subject: [PATCH 07/12] Fix wave 2: catch hardcoded ~/.claude paths the first sweep's regex missed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original sweep (PR's first commit) matched homedir()+'.claude' and literal $HOME/.claude / ~/.claude, but missed three more syntactic variants actually used throughout the codebase: - join(HOME, ".claude", ...) / resolve(HOME, ".claude", ...) โ€” a pre-resolved local `const HOME = process.env.HOME || ""` passed as a plain identifier, not a homedir() call. - join(HOME, ".claude/LIFEOS/...") โ€” a single path string with more after ".claude" rather than a lone ".claude" argument. - join(process.env.HOME ?? "~", ...) / `${process.env.HOME}/.claude` โ€” inline env access instead of a named HOME constant. Found this because it was live: a /interview session hit "Module not found" running InterviewScan.ts against the wrong (global) path, and separately credential/.env lookups in several PULSE/healthsync tools were silently checking ~/.claude/.env instead of the project-scoped one. 84 files fixed, same additive pattern as the original sweep throughout (CLAUDE_CONFIG_DIR for general Claude-home paths, LIFEOS_DIR for the LIFEOS-specific subtree), verified via `bun build --no-bundle` syntax checks and three successive convergence passes with progressively broader regexes until a pass turned up zero new genuine hits (all remaining matches confirmed cosmetic โ€” doc strings, error/usage text, or the two files whose matches are themselves regex *patterns* for detecting this exact bug, not the bug). This is very likely still not 100% exhaustive for a codebase this size and with markdown docs the AI reads as instructions (a related, separate class of issue fixed in the prior commit for CLAUDE.md) โ€” flagging that residual risk rather than claiming otherwise. --- LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh | 2 +- .../LIFEOS/PULSE/Observability/observability.ts | 2 +- .../install/LIFEOS/PULSE/Tools/SnapshotFromFixture.ts | 4 ++-- LifeOS/install/LIFEOS/PULSE/Tools/SnapshotPages.ts | 2 +- LifeOS/install/LIFEOS/PULSE/VoiceServer/voice.ts | 6 +++--- LifeOS/install/LIFEOS/PULSE/adapters/AdapterRunner.ts | 2 +- LifeOS/install/LIFEOS/PULSE/checks/airgradient-poll.ts | 4 ++-- LifeOS/install/LIFEOS/PULSE/checks/calendar.ts | 2 +- LifeOS/install/LIFEOS/PULSE/checks/github-work.ts | 2 +- LifeOS/install/LIFEOS/PULSE/checks/github.ts | 4 ++-- .../install/LIFEOS/PULSE/checks/life-morning-brief.ts | 2 +- LifeOS/install/LIFEOS/PULSE/edit/edit-handler.ts | 7 ++++--- LifeOS/install/LIFEOS/PULSE/lib.ts | 2 +- LifeOS/install/LIFEOS/PULSE/lib/data-plane.ts | 2 +- LifeOS/install/LIFEOS/PULSE/lib/provenance-watcher.ts | 2 +- LifeOS/install/LIFEOS/PULSE/lib/telegram-proposals.ts | 2 +- LifeOS/install/LIFEOS/PULSE/lib/telegram-sessions.ts | 2 +- LifeOS/install/LIFEOS/PULSE/modules/imessage.ts | 6 +++--- .../install/LIFEOS/PULSE/modules/local-intelligence.ts | 6 +++--- LifeOS/install/LIFEOS/PULSE/modules/memory.ts | 2 +- LifeOS/install/LIFEOS/PULSE/modules/siri.ts | 2 +- LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts | 2 +- LifeOS/install/LIFEOS/PULSE/modules/telegram.ts | 6 +++--- LifeOS/install/LIFEOS/PULSE/modules/work.ts | 2 +- LifeOS/install/LIFEOS/PULSE/pulse-old.ts | 4 ++-- LifeOS/install/LIFEOS/PULSE/run-job.ts | 4 ++-- LifeOS/install/LIFEOS/PULSE/setup.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/ActivityParser.ts | 2 +- .../LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/Banner.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/BannerMatrix.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/BannerNeofetch.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/BlogDiscovery.ts | 10 ++++++---- LifeOS/install/LIFEOS/TOOLS/CodexUpdate.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/CostTracker.ts | 10 +++++----- LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/DeriveDenyHashes.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/DerivedSync.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/DocCheck.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/FeatureRegistry.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/GenerateTelosSummary.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/GetCounts.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/HealthSync.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/ISARender.ts | 9 +++++---- LifeOS/install/LIFEOS/TOOLS/Inference.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/IntegrityCheck.ts | 2 +- .../install/LIFEOS/TOOLS/LearningPatternSynthesis.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/LifeosUpgrade.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/LoadSkillConfig.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryRetriever.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/NeofetchBanner.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/ReferenceCheck.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/Services.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/SessionHarvester.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/SessionProgress.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/SyncIdentityToSettings.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/TlpArchive.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/healthsync/store.ts | 8 ++++---- LifeOS/install/hooks/AlgorithmNudge.hook.ts | 2 +- LifeOS/install/hooks/EgressClassGuard.hook.ts | 2 +- LifeOS/install/hooks/ReminderRouter.hook.ts | 2 +- LifeOS/install/hooks/handlers/UpdateCounts.ts | 2 +- LifeOS/install/hooks/lib/identity.ts | 4 ++-- LifeOS/install/hooks/lib/notifications.ts | 2 +- LifeOS/install/hooks/lib/safety-classifier.ts | 2 +- LifeOS/install/hooks/lib/system-file-guard-core.ts | 2 +- LifeOS/install/skills/Art/Tools/Generate.ts | 2 +- .../skills/ContextSearch/Tools/ContextSearch.ts | 2 +- LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts | 2 +- LifeOS/install/skills/Telos/Tools/UpdateTelos.ts | 2 +- LifeOS/install/skills/Upgrade/Tools/Anthropic.ts | 2 +- 84 files changed, 121 insertions(+), 117 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh b/LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh index 84bd645243..f42aec2315 100755 --- a/LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh +++ b/LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh @@ -68,7 +68,7 @@ sed "s|__HOME__|$HOME_DIR|g" "$PLIST_SRC" > "$PLIST_DST" echo " Installed $PLIST_DST" # Ensure logs directory exists -mkdir -p "$HOME_DIR/.claude/LIFEOS/PULSE/logs" +mkdir -p "${LIFEOS_DIR:-$HOME_DIR/.claude/LIFEOS}/PULSE/logs" launchctl load "$PLIST_DST" echo " Loaded $PLIST_LABEL" diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index b0fa18ee71..fedf848567 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -156,7 +156,7 @@ function getDashboardDir(): string { const dir = config.dashboard_dir ?? DEFAULT_DASHBOARD_DIR // Resolve relative paths against Pulse directory if (!dir.startsWith("/")) { - return join(HOME, ".claude", "LIFEOS", "PULSE", dir) + return join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", dir) } return dir } diff --git a/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotFromFixture.ts b/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotFromFixture.ts index fdfe381892..c2e26edb80 100755 --- a/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotFromFixture.ts +++ b/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotFromFixture.ts @@ -6,8 +6,8 @@ import type { PageData } from "../Schema/PulseSchema"; import { PageDataSchema } from "../Schema/PulseSchema"; const HOME = process.env.HOME!; -const FIX_DIR = resolve(HOME, ".claude", "LIFEOS", "PULSE", "Schema", "Fixtures"); -const OUT_DIR = resolve(HOME, ".claude", "LIFEOS", "PULSE", "Schema", "Snapshots"); +const FIX_DIR = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "PULSE", "Schema", "Fixtures"); +const OUT_DIR = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "PULSE", "Schema", "Snapshots"); mkdirSync(OUT_DIR, { recursive: true }); const fixtures = readdirSync(FIX_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("invalid")); diff --git a/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotPages.ts b/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotPages.ts index d2b20f7eb8..2758d8ebe9 100755 --- a/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotPages.ts +++ b/LifeOS/install/LIFEOS/PULSE/Tools/SnapshotPages.ts @@ -7,7 +7,7 @@ import { readIndex, readPage } from "../lib/data-plane"; import { loadAllManifests } from "../lib/manifest-loader"; const HOME = process.env.HOME!; -const SNAP_DIR = resolve(HOME, ".claude", "LIFEOS", "PULSE", "Schema", "Snapshots"); +const SNAP_DIR = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "PULSE", "Schema", "Snapshots"); mkdirSync(SNAP_DIR, { recursive: true }); const idx = readIndex(); diff --git a/LifeOS/install/LIFEOS/PULSE/VoiceServer/voice.ts b/LifeOS/install/LIFEOS/PULSE/VoiceServer/voice.ts index 94086ad36f..42423e827a 100644 --- a/LifeOS/install/LIFEOS/PULSE/VoiceServer/voice.ts +++ b/LifeOS/install/LIFEOS/PULSE/VoiceServer/voice.ts @@ -163,7 +163,7 @@ function escapeRegex(str: string): string { } function loadPronunciations(customPath?: string): void { - const paiDir = join(process.env.HOME ?? "~", ".claude", "LIFEOS") + const paiDir = process.env.LIFEOS_DIR || join(process.env.HOME ?? "~", ".claude", "LIFEOS") const userPronPath = customPath ?? join(paiDir, "USER", "PRINCIPAL", "PRONUNCIATIONS.json") try { @@ -196,7 +196,7 @@ function applyPronunciations(text: string): string { // โ”€โ”€ Voice Config from settings.json โ”€โ”€ function loadVoiceConfigFromSettings(): LoadedVoiceConfig { - const settingsPath = join(process.env.HOME ?? "~", ".claude", "settings.json") + const settingsPath = join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME ?? "~", ".claude"), "settings.json") try { if (!existsSync(settingsPath)) { @@ -712,7 +712,7 @@ export async function handleVoiceRequest(req: Request): Promise // /notify/personality honest with whatever the user last selected. let voiceId: string | null = null try { - const settingsFile = join(process.env.HOME ?? "~", ".claude", "settings.json") + const settingsFile = join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME ?? "~", ".claude"), "settings.json") const settings = JSON.parse(readFileSync(settingsFile, "utf-8")) const main = settings?.daidentity?.voices?.main const vid = (main?.voiceId || main?.VOICE_ID || main?.voice_id) as string | undefined diff --git a/LifeOS/install/LIFEOS/PULSE/adapters/AdapterRunner.ts b/LifeOS/install/LIFEOS/PULSE/adapters/AdapterRunner.ts index effa27b897..b4c28db960 100644 --- a/LifeOS/install/LIFEOS/PULSE/adapters/AdapterRunner.ts +++ b/LifeOS/install/LIFEOS/PULSE/adapters/AdapterRunner.ts @@ -8,7 +8,7 @@ import { getProvenance } from "../lib/frontmatter"; import { inference, type InferenceLevel } from "../../TOOLS/Inference"; const HOME = process.env.HOME!; -const OBSERVABILITY_DIR = resolve(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY"); +const OBSERVABILITY_DIR = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "MEMORY", "OBSERVABILITY"); const RUNS_LOG = join(OBSERVABILITY_DIR, "adapter-runs.jsonl"); const ADAPTER_TIMEOUT_MS = 120_000; diff --git a/LifeOS/install/LIFEOS/PULSE/checks/airgradient-poll.ts b/LifeOS/install/LIFEOS/PULSE/checks/airgradient-poll.ts index 5915340463..38eae3cb9c 100755 --- a/LifeOS/install/LIFEOS/PULSE/checks/airgradient-poll.ts +++ b/LifeOS/install/LIFEOS/PULSE/checks/airgradient-poll.ts @@ -14,7 +14,7 @@ import { join } from "node:path" import { mkdirSync, writeFileSync, appendFileSync, readFileSync, existsSync } from "node:fs" const HOME = process.env.HOME ?? "" -const CACHE_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "_AIRGRADIENT") +const CACHE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "_AIRGRADIENT") const LATEST = join(CACHE_DIR, "latest.json") const HISTORY = join(CACHE_DIR, "history.jsonl") @@ -23,7 +23,7 @@ const API_BASE = "https://api.airgradient.com/public/api/v1" // Bun auto-loads .env from CWD only; Pulse cron runs from LIFEOS/PULSE/, so the // symlink at ~/.claude/.env isn't picked up. Read it directly if env is empty. function loadTokenFromDotenv(): string | null { - const envPath = join(HOME, ".claude", ".env") + const envPath = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env") if (!existsSync(envPath)) return null try { const raw = readFileSync(envPath, "utf8") diff --git a/LifeOS/install/LIFEOS/PULSE/checks/calendar.ts b/LifeOS/install/LIFEOS/PULSE/checks/calendar.ts index bb97533f7a..09f8e3f627 100644 --- a/LifeOS/install/LIFEOS/PULSE/checks/calendar.ts +++ b/LifeOS/install/LIFEOS/PULSE/checks/calendar.ts @@ -17,7 +17,7 @@ const LOOKAHEAD_MS = 30 * 60 * 1000 function loadEnv(): Record { const env: Record = {} try { - const content = readFileSync(join(HOME, ".claude", ".env"), "utf-8") + const content = readFileSync(join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env"), "utf-8") for (const line of content.split("\n")) { const match = line.match(/^([^#=]+)=(.*)$/) if (match) { diff --git a/LifeOS/install/LIFEOS/PULSE/checks/github-work.ts b/LifeOS/install/LIFEOS/PULSE/checks/github-work.ts index 41b1b5b7d0..b72a77220d 100755 --- a/LifeOS/install/LIFEOS/PULSE/checks/github-work.ts +++ b/LifeOS/install/LIFEOS/PULSE/checks/github-work.ts @@ -14,7 +14,7 @@ import { parse } from "smol-toml" import { SignJWT, importPKCS8 } from "jose" const HOME = process.env.HOME ?? "" -const PULSE_DIR = join(HOME, ".claude", "LIFEOS", "PULSE") +const PULSE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE") const STATE_FILE = join(PULSE_DIR, "state", "work-token.json") // โ”€โ”€ Worker Config (from PULSE.toml [worker] section) โ”€โ”€ diff --git a/LifeOS/install/LIFEOS/PULSE/checks/github.ts b/LifeOS/install/LIFEOS/PULSE/checks/github.ts index a2f2cef6ec..b82e99365b 100755 --- a/LifeOS/install/LIFEOS/PULSE/checks/github.ts +++ b/LifeOS/install/LIFEOS/PULSE/checks/github.ts @@ -12,8 +12,8 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeF import { dirname, join } from "path" const HOME = process.env.HOME ?? "" -const LEGACY_STATE_FILE = join(HOME, ".claude", "LIFEOS", "PULSE", "state", "github-seen.json") -const STATE_FILE = join(HOME, ".claude", "LIFEOS", "PULSE", "state", "github-seen.jsonl") +const LEGACY_STATE_FILE = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "state", "github-seen.json") +const STATE_FILE = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "state", "github-seen.jsonl") // Repos to monitor for new issues / activity. Override via LIFEOS_PULSE_REPOS // env var (comma-separated "owner/name" pairs). Empty default keeps fresh // installs from polling repos the user hasn't opted into. diff --git a/LifeOS/install/LIFEOS/PULSE/checks/life-morning-brief.ts b/LifeOS/install/LIFEOS/PULSE/checks/life-morning-brief.ts index a4bb7d6a56..28b1c7b5e9 100755 --- a/LifeOS/install/LIFEOS/PULSE/checks/life-morning-brief.ts +++ b/LifeOS/install/LIFEOS/PULSE/checks/life-morning-brief.ts @@ -13,7 +13,7 @@ import { join } from "path" import { existsSync, readFileSync } from "fs" const HOME = process.env.HOME ?? "" -const TELOS_DIR = join(HOME, ".claude", "LIFEOS", "USER", "TELOS") +const TELOS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER", "TELOS") function readFile(name: string): string { const p = join(TELOS_DIR, name) diff --git a/LifeOS/install/LIFEOS/PULSE/edit/edit-handler.ts b/LifeOS/install/LIFEOS/PULSE/edit/edit-handler.ts index 5eefb80f27..a44bdde46c 100644 --- a/LifeOS/install/LIFEOS/PULSE/edit/edit-handler.ts +++ b/LifeOS/install/LIFEOS/PULSE/edit/edit-handler.ts @@ -5,8 +5,9 @@ import { parseFrontmatter, serializeFrontmatter } from "../lib/frontmatter"; import { sha256Hex } from "../lib/cache"; const HOME = process.env.HOME!; -const USER_ROOT = resolve(HOME, ".claude", "LIFEOS", "USER"); -const EDITS_LOG = resolve(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY", "pulse-edits.jsonl"); +const LIFEOS_ROOT = process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"); +const USER_ROOT = resolve(LIFEOS_ROOT, "USER"); +const EDITS_LOG = resolve(LIFEOS_ROOT, "MEMORY", "OBSERVABILITY", "pulse-edits.jsonl"); const CONTAINMENT_PREFIX_DENY = ["MEMORY/PULSE_DATA", "MEMORY/OBSERVABILITY"]; export interface EditRequest { @@ -32,7 +33,7 @@ function isInUserTree(absPath: string): boolean { } function isContainmentPath(absPath: string): boolean { - const rel = absPath.replace(resolve(HOME, ".claude", "LIFEOS") + "/", ""); + const rel = absPath.replace(LIFEOS_ROOT + "/", ""); return CONTAINMENT_PREFIX_DENY.some((p) => rel.startsWith(p)); } diff --git a/LifeOS/install/LIFEOS/PULSE/lib.ts b/LifeOS/install/LIFEOS/PULSE/lib.ts index 5ba5cb96fa..00b4b04126 100644 --- a/LifeOS/install/LIFEOS/PULSE/lib.ts +++ b/LifeOS/install/LIFEOS/PULSE/lib.ts @@ -319,7 +319,7 @@ export async function spawnScript(command: string, timeoutMs = 60_000): Promise< const proc = Bun.spawn([BASH_PATH, "-c", command], { stdout: "pipe", stderr: "pipe", - cwd: join(process.env.HOME ?? "~", ".claude", "LIFEOS", "PULSE"), + cwd: join(process.env.LIFEOS_DIR || join(process.env.HOME ?? "~", ".claude", "LIFEOS"), "PULSE"), env: { ...process.env }, }) diff --git a/LifeOS/install/LIFEOS/PULSE/lib/data-plane.ts b/LifeOS/install/LIFEOS/PULSE/lib/data-plane.ts index 096a65df15..e67b7e8c38 100644 --- a/LifeOS/install/LIFEOS/PULSE/lib/data-plane.ts +++ b/LifeOS/install/LIFEOS/PULSE/lib/data-plane.ts @@ -5,7 +5,7 @@ import { atomicWriteJSON } from "./atomic-write"; import type { PageData, PageMeta, Provenance } from "../Schema/PulseSchema"; const HOME = process.env.HOME!; -export const PULSE_DATA_DIR = resolve(HOME, ".claude", "LIFEOS", "MEMORY", "PULSE_DATA"); +export const PULSE_DATA_DIR = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "MEMORY", "PULSE_DATA"); export interface DataPlaneFile { schemaVersion: string; diff --git a/LifeOS/install/LIFEOS/PULSE/lib/provenance-watcher.ts b/LifeOS/install/LIFEOS/PULSE/lib/provenance-watcher.ts index dd2e1297e8..62144e1274 100644 --- a/LifeOS/install/LIFEOS/PULSE/lib/provenance-watcher.ts +++ b/LifeOS/install/LIFEOS/PULSE/lib/provenance-watcher.ts @@ -5,7 +5,7 @@ import { atomicWriteText } from "./atomic-write"; import { parseFrontmatter, serializeFrontmatter } from "./frontmatter"; const HOME = process.env.HOME!; -const EDITS_LOG = resolve(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY", "pulse-edits.jsonl"); +const EDITS_LOG = resolve(process.env.LIFEOS_DIR || resolve(HOME, ".claude", "LIFEOS"), "MEMORY", "OBSERVABILITY", "pulse-edits.jsonl"); const PULSE_EDIT_GRACE_MS = 5_000; export interface WatcherOptions { diff --git a/LifeOS/install/LIFEOS/PULSE/lib/telegram-proposals.ts b/LifeOS/install/LIFEOS/PULSE/lib/telegram-proposals.ts index a8dd519fd5..d148c9df4a 100644 --- a/LifeOS/install/LIFEOS/PULSE/lib/telegram-proposals.ts +++ b/LifeOS/install/LIFEOS/PULSE/lib/telegram-proposals.ts @@ -21,7 +21,7 @@ import { } from "../../TOOLS/MemoryTypes"; const HOME = process.env.HOME ?? homedir(); -const OBS_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY"); +const OBS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "OBSERVABILITY"); const PROPOSAL_REPLIES_LOG_PATH = join(OBS_DIR, "proposal-replies.jsonl"); const IDENTITY_PROPOSALS_LOG_PATH = join(OBS_DIR, "identity-proposals.jsonl"); diff --git a/LifeOS/install/LIFEOS/PULSE/lib/telegram-sessions.ts b/LifeOS/install/LIFEOS/PULSE/lib/telegram-sessions.ts index 809ca96410..5b13a0fb37 100644 --- a/LifeOS/install/LIFEOS/PULSE/lib/telegram-sessions.ts +++ b/LifeOS/install/LIFEOS/PULSE/lib/telegram-sessions.ts @@ -19,7 +19,7 @@ import { join } from "path" import { mkdirSync } from "fs" const HOME = process.env.HOME ?? "" -const DB_DIR = join(HOME, ".claude", "LIFEOS", "PULSE", "state", "telegram") +const DB_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "state", "telegram") const DB_PATH = join(DB_DIR, "sessions.db") // Compaction threshold โ€” after this many turns, we'll compact the session diff --git a/LifeOS/install/LIFEOS/PULSE/modules/imessage.ts b/LifeOS/install/LIFEOS/PULSE/modules/imessage.ts index 003b855f09..f836a4205a 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/imessage.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/imessage.ts @@ -62,9 +62,9 @@ export interface IMessageHealth { // โ”€โ”€ Module State โ”€โ”€ const HOME = process.env.HOME ?? "" -const CWD = join(HOME, ".claude") -const STATE_DIR = join(HOME, ".claude", "LIFEOS", "PULSE", "state", "imessage") -const LOGS_DIR = join(HOME, ".claude", "LIFEOS", "PULSE", "logs", "imessage") +const CWD = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude") +const STATE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "state", "imessage") +const LOGS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "logs", "imessage") let pollTimer: ReturnType | null = null let running = false diff --git a/LifeOS/install/LIFEOS/PULSE/modules/local-intelligence.ts b/LifeOS/install/LIFEOS/PULSE/modules/local-intelligence.ts index b2534fd661..2761bb3fed 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/local-intelligence.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/local-intelligence.ts @@ -25,13 +25,13 @@ const MODULE_NAME = "local-intelligence" // Primary path: user-scoped customizations directory (per {{PRINCIPAL_NAME}} directive 2026-05-03). // Fallback path: legacy MEMORY/DATA path (used when customizations file absent). -const CUSTOMIZATIONS_DIR = join(HOME, ".claude", "LIFEOS", "USER", "CUSTOMIZATIONS", "SKILLS", "LocalIntelligence") -const LEGACY_DATA_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "DATA", "LocalIntelligence") +const CUSTOMIZATIONS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER", "CUSTOMIZATIONS", "SKILLS", "LocalIntelligence") +const LEGACY_DATA_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "DATA", "LocalIntelligence") const LATEST_PATH = join(CUSTOMIZATIONS_DIR, "latest.json") const LEGACY_LATEST_PATH = join(LEGACY_DATA_DIR, "latest.json") const DATA_DIR = CUSTOMIZATIONS_DIR // alias for existing references in this file const RUNS_DIR = join(CUSTOMIZATIONS_DIR, "runs") -const REFRESH_SCRIPT = join(HOME, ".claude", "skills", "LocalIntelligence", "Tools", "Refresh.ts") +const REFRESH_SCRIPT = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills", "LocalIntelligence", "Tools", "Refresh.ts") async function readLatest(): Promise { try { return await readFile(LATEST_PATH, "utf8") } catch {} diff --git a/LifeOS/install/LIFEOS/PULSE/modules/memory.ts b/LifeOS/install/LIFEOS/PULSE/modules/memory.ts index 22db4510aa..e94c12d5a7 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/memory.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/memory.ts @@ -30,7 +30,7 @@ import { import { join } from "node:path"; const HOME = process.env.HOME || ""; -const CLAUDE = join(HOME, ".claude"); +const CLAUDE = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const OBS_DIR = join(CLAUDE, "LIFEOS/MEMORY/OBSERVABILITY"); const REVIEW_STATE = join(OBS_DIR, "review-state.json"); diff --git a/LifeOS/install/LIFEOS/PULSE/modules/siri.ts b/LifeOS/install/LIFEOS/PULSE/modules/siri.ts index 9c1daf1f23..0cb28a669e 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/siri.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/siri.ts @@ -20,7 +20,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk" import { buildLifeosContextBlock } from "./telegram" -const CWD = `${process.env.HOME}/.claude` +const CWD = process.env.CLAUDE_CONFIG_DIR || `${process.env.HOME}/.claude` const IDLE_TIMEOUT_MS = 60 * 60 * 1000 // 60 min โ€” same thread boundary as Telegram const SDK_TIMEOUT_MS = 50_000 // Shortcuts' Get Contents of URL times out ~60s; stay under it const MAX_TURNS = 10 // speed over depth โ€” this is a spoken exchange, not a work session diff --git a/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts b/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts index 4c2cee6d9a..8f7ff7ada5 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts @@ -89,7 +89,7 @@ const REGISTRY: Record = { { name: "skills/", path: join(HOME, ".claude", "skills"), expand: true }, ], agents: [ - { name: "agents/", path: join(HOME, ".claude", "agents"), expand: true }, + { name: "agents/", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "agents"), expand: true }, ], docs: [ { name: "DOCUMENTATION/", path: join(LIFEOS_DIR, "DOCUMENTATION"), expand: true }, diff --git a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts index 41512798d3..cd3717e58e 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts @@ -67,9 +67,9 @@ export interface TelegramConfig { // โ”€โ”€ Constants โ”€โ”€ const HOME = process.env.HOME ?? "" -const CWD = join(HOME, ".claude") -const STATE_DIR = join(HOME, ".claude", "LIFEOS", "PULSE", "state", "telegram") -const LOGS_DIR = join(HOME, ".claude", "LIFEOS", "PULSE", "logs", "telegram") +const CWD = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude") +const STATE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "state", "telegram") +const LOGS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE", "logs", "telegram") const STALE_ACK_CACHE_DIR = join(STATE_DIR, "ack-cache") const MAX_TELEGRAM_LENGTH = 4096 const CURSOR = " โ–Œ" diff --git a/LifeOS/install/LIFEOS/PULSE/modules/work.ts b/LifeOS/install/LIFEOS/PULSE/modules/work.ts index ab4db21508..dd07ba3841 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/work.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/work.ts @@ -160,7 +160,7 @@ function extractSlug(title: string): string | undefined { // issues; the workload is bounded and the files are small. function extractPrincipalGoal(slug: string | undefined): string | undefined { if (!slug) return undefined; - const isaPath = join(HOME, ".claude", "LIFEOS", "MEMORY", "WORK", slug, "ISA.md"); + const isaPath = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "WORK", slug, "ISA.md"); if (!existsSync(isaPath)) return undefined; try { const content = readFileSync(isaPath, "utf-8"); diff --git a/LifeOS/install/LIFEOS/PULSE/pulse-old.ts b/LifeOS/install/LIFEOS/PULSE/pulse-old.ts index 913750a74e..360696be8b 100755 --- a/LifeOS/install/LIFEOS/PULSE/pulse-old.ts +++ b/LifeOS/install/LIFEOS/PULSE/pulse-old.ts @@ -12,7 +12,7 @@ import { readFileSync } from "fs" // โ”€โ”€ Load .env before anything else โ”€โ”€ -const envPath = join(process.env.HOME ?? "~", ".claude", ".env") +const envPath = join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME ?? "~", ".claude"), ".env") try { const envContent = readFileSync(envPath, "utf-8") for (const line of envContent.split("\n")) { @@ -46,7 +46,7 @@ import { // โ”€โ”€ Constants โ”€โ”€ -const PULSE_DIR = join(process.env.HOME ?? "~", ".claude", "LIFEOS", "PULSE") +const PULSE_DIR = join(process.env.LIFEOS_DIR || join(process.env.HOME ?? "~", ".claude", "LIFEOS"), "PULSE") const STATE_PATH = join(PULSE_DIR, "state", "state.json") const PID_PATH = join(PULSE_DIR, "state", "pulse.pid") const HOOK_PORT = parseInt(process.env.HOOK_SERVER_PORT || "8686", 10) diff --git a/LifeOS/install/LIFEOS/PULSE/run-job.ts b/LifeOS/install/LIFEOS/PULSE/run-job.ts index d00ad89ae0..8a56b55c89 100755 --- a/LifeOS/install/LIFEOS/PULSE/run-job.ts +++ b/LifeOS/install/LIFEOS/PULSE/run-job.ts @@ -7,7 +7,7 @@ import { join } from "path" import { readFileSync } from "fs" // Load .env -const envPath = join(process.env.HOME ?? "~", ".claude", ".env") +const envPath = join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME ?? "~", ".claude"), ".env") try { const envContent = readFileSync(envPath, "utf-8") for (const line of envContent.split("\n")) { @@ -31,7 +31,7 @@ if (!jobName) { process.exit(1) } -const PULSE_DIR = join(process.env.HOME ?? "~", ".claude", "LIFEOS", "PULSE") +const PULSE_DIR = join(process.env.LIFEOS_DIR || join(process.env.HOME ?? "~", ".claude", "LIFEOS"), "PULSE") const config = await loadConfig(PULSE_DIR) const job = config.jobs.find((j) => j.name === jobName) if (!job) { diff --git a/LifeOS/install/LIFEOS/PULSE/setup.ts b/LifeOS/install/LIFEOS/PULSE/setup.ts index e3cd88c4a6..d46fd65560 100755 --- a/LifeOS/install/LIFEOS/PULSE/setup.ts +++ b/LifeOS/install/LIFEOS/PULSE/setup.ts @@ -235,7 +235,7 @@ enabled = true ``, ] - const envPath = join(HOME, ".claude", ".env") + const envPath = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env") if (existsSync(envPath)) { warn(`.env already exists โ€” appending worker config`) const existing = await Bun.file(envPath).text() @@ -444,7 +444,7 @@ ${"โ•".repeat(50)} Time: ${Math.floor(elapsed / 60)}m ${elapsed % 60}s Next steps: - - Verify ANTHROPIC_API_KEY is set in ${join(HOME, ".claude", ".env")} + - Verify ANTHROPIC_API_KEY is set in ${join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env")} - Create a test issue with label "status:ready" in one of your repos - Watch: tail -f ${join(PULSE_DIR, "logs", "pulse-stdout.log")} - Status: ${join(PULSE_DIR, "manage.sh")} status diff --git a/LifeOS/install/LIFEOS/TOOLS/ActivityParser.ts b/LifeOS/install/LIFEOS/TOOLS/ActivityParser.ts index 4f7a3c34bf..d709aa5aed 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ActivityParser.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ActivityParser.ts @@ -21,7 +21,7 @@ import * as path from "path"; // Configuration // ============================================================================ -const CLAUDE_DIR = path.join(process.env.HOME!, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(process.env.HOME!, ".claude"); const MEMORY_DIR = path.join(CLAUDE_DIR, "LIFEOS", "MEMORY"); const USERNAME = process.env.USER || require("os").userInfo().username; const PROJECTS_DIR = path.join(CLAUDE_DIR, "projects", `-Users-${USERNAME}--claude`); // Claude Code native storage diff --git a/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts b/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts index 54b6f7cb37..7a32c460a0 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts @@ -274,7 +274,7 @@ function cmdCheck(): void { const sourceMtime = getMtime(ARCH_SOURCE); const summaryMtime = getMtime(SUMMARY_OUTPUT); - const claudeMdMtime = getMtime(path.join(HOME, ".claude", "CLAUDE.md")); + const claudeMdMtime = getMtime(path.join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "CLAUDE.md")); if (sourceMtime > summaryMtime || claudeMdMtime > summaryMtime) { console.log("STALE: Source files are newer than summary"); diff --git a/LifeOS/install/LIFEOS/TOOLS/Banner.ts b/LifeOS/install/LIFEOS/TOOLS/Banner.ts index 9d53c0b494..680965e3b3 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Banner.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Banner.ts @@ -13,7 +13,7 @@ import { spawnSync } from "child_process"; import { parse as parseYaml } from "yaml"; const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Terminal Width Detection diff --git a/LifeOS/install/LIFEOS/TOOLS/BannerMatrix.ts b/LifeOS/install/LIFEOS/TOOLS/BannerMatrix.ts index 2a169ab2f9..819b146b00 100755 --- a/LifeOS/install/LIFEOS/TOOLS/BannerMatrix.ts +++ b/LifeOS/install/LIFEOS/TOOLS/BannerMatrix.ts @@ -23,7 +23,7 @@ import { spawnSync } from "child_process"; import { paiUserDir } from "./LifeosConfig"; const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // ============================================================================= // Terminal Width Detection diff --git a/LifeOS/install/LIFEOS/TOOLS/BannerNeofetch.ts b/LifeOS/install/LIFEOS/TOOLS/BannerNeofetch.ts index 99fade61bc..afebc797a3 100755 --- a/LifeOS/install/LIFEOS/TOOLS/BannerNeofetch.ts +++ b/LifeOS/install/LIFEOS/TOOLS/BannerNeofetch.ts @@ -16,7 +16,7 @@ import { spawnSync } from "child_process"; import { paiUserDir } from "./LifeosConfig"; const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Terminal Width Detection diff --git a/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts b/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts index 13618c132d..ddae863419 100755 --- a/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts +++ b/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts @@ -21,7 +21,7 @@ import { spawnSync } from "child_process"; import { paiUserDir } from "./LifeosConfig"; const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Terminal Width Detection diff --git a/LifeOS/install/LIFEOS/TOOLS/BlogDiscovery.ts b/LifeOS/install/LIFEOS/TOOLS/BlogDiscovery.ts index ef5b7dd44e..c0f1e1be27 100755 --- a/LifeOS/install/LIFEOS/TOOLS/BlogDiscovery.ts +++ b/LifeOS/install/LIFEOS/TOOLS/BlogDiscovery.ts @@ -36,9 +36,11 @@ import { spawnSync } from "child_process"; import { randomUUID } from "crypto"; const HOME = homedir(); -const DB_PATH = join(HOME, ".claude/LIFEOS/MEMORY/STATE/feed-candidates.db"); -const CFENV = join(HOME, ".claude/skills/_CLOUDFLARE/Tools/CfEnv.ts"); -const INFERENCE = join(HOME, ".claude/LIFEOS/TOOLS/Inference.ts"); +const LIFEOS_ROOT = process.env.LIFEOS_DIR || join(HOME, ".claude/LIFEOS"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); +const DB_PATH = join(LIFEOS_ROOT, "MEMORY/STATE/feed-candidates.db"); +const CFENV = join(CLAUDE_ROOT, "skills/_CLOUDFLARE/Tools/CfEnv.ts"); +const INFERENCE = join(LIFEOS_ROOT, "TOOLS/Inference.ts"); const SURFACE_TAX = join(HOME, "Projects/Surface/src/categories.ts"); const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"; @@ -396,7 +398,7 @@ async function cmdHarvest() { const pend = (db.query("SELECT COUNT(*) n FROM candidates WHERE status='pending' AND combined IS NOT NULL").get() as any).n; const good = (db.query("SELECT COUNT(*) n FROM candidates WHERE status='pending' AND combined>=55").get() as any).n; console.log(`[harvest] done. pending scored: ${pend}, of which combined>=55: ${good}.`); - console.log(`Shortlist: bun ${join(HOME, ".claude/LIFEOS/TOOLS/BlogDiscovery.ts")} top --n 50`); + console.log(`Shortlist: bun ${join(LIFEOS_ROOT, "TOOLS/BlogDiscovery.ts")} top --n 50`); } function rankRows(db: Database, limit: number, min: number, maxAgeDays: number, minFit = 0): any[] { diff --git a/LifeOS/install/LIFEOS/TOOLS/CodexUpdate.ts b/LifeOS/install/LIFEOS/TOOLS/CodexUpdate.ts index bf94a1ec12..ccfe19c529 100755 --- a/LifeOS/install/LIFEOS/TOOLS/CodexUpdate.ts +++ b/LifeOS/install/LIFEOS/TOOLS/CodexUpdate.ts @@ -21,7 +21,7 @@ import { join, dirname } from "path"; const HOME = process.env.HOME || ""; const PKG = "@openai/codex"; -const LOG = join(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY", "codex-update.jsonl"); +const LOG = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "OBSERVABILITY", "codex-update.jsonl"); function codexVersion(): string | null { const r = spawnSync("codex", ["--version"], { encoding: "utf-8" }); diff --git a/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts b/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts index 2413bfddf7..8d08b663fe 100755 --- a/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts +++ b/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts @@ -129,11 +129,11 @@ async function fetchApiSpend(): Promise<{ month_used_usd: number | null; source: // Paths we scan (source-of-truth for LifeOS-local billing risk) const SCAN_ROOTS = [ - join(HOME, ".claude", "LIFEOS", "PULSE"), - join(HOME, ".claude", "LIFEOS", "TOOLS"), - join(HOME, ".claude", "LIFEOS", "USER"), - join(HOME, ".claude", "skills"), - join(HOME, ".claude", "hooks"), + join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "PULSE"), + join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS"), + join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER"), + join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills"), + join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "hooks"), ]; // Paths to exclude from scan diff --git a/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts b/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts index 1cf5be8b0d..e5c89c8c5b 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts @@ -16,7 +16,7 @@ import { join } from "path" import { getDAName } from "../../hooks/lib/identity" const HOME = process.env.HOME ?? "~" -const LifeOS = join(HOME, ".claude", "LIFEOS") +const LifeOS = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const REGISTRY_PATH = join(LifeOS, "USER", "DA", "_registry.yaml") // โ”€โ”€ Types โ”€โ”€ diff --git a/LifeOS/install/LIFEOS/TOOLS/DeriveDenyHashes.ts b/LifeOS/install/LIFEOS/TOOLS/DeriveDenyHashes.ts index 2d1303fbe2..1032dae96d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DeriveDenyHashes.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DeriveDenyHashes.ts @@ -34,7 +34,7 @@ import { join } from "node:path"; import { createHash, randomBytes } from "node:crypto"; const HOME = process.env.HOME || homedir(); -const CLAUDE = join(HOME, ".claude"); +const CLAUDE = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const ENV_PATH = join(CLAUDE, ".env"); const OUT_PATH = join(CLAUDE, "skills", "_LIFEOS", "DENY_HASHES.json"); const MIN_LEN = 4; // single tokens shorter than this are too FP-prone diff --git a/LifeOS/install/LIFEOS/TOOLS/DerivedSync.ts b/LifeOS/install/LIFEOS/TOOLS/DerivedSync.ts index 310348701c..f067684598 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DerivedSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DerivedSync.ts @@ -76,7 +76,7 @@ type RunSummary = { }; const HOME = process.env.HOME || ""; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const LIFEOS_DIR = join(CLAUDE_DIR, "LIFEOS"); const USER_DIR = join(LIFEOS_DIR, "USER"); const TOOLS_DIR = join(LIFEOS_DIR, "TOOLS"); diff --git a/LifeOS/install/LIFEOS/TOOLS/DocCheck.ts b/LifeOS/install/LIFEOS/TOOLS/DocCheck.ts index 1524f59b2f..1ca37ffe97 100644 --- a/LifeOS/install/LIFEOS/TOOLS/DocCheck.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DocCheck.ts @@ -20,7 +20,7 @@ import { join, resolve, dirname, relative } from 'path'; import { execSync } from 'child_process'; const HOME = process.env.HOME || ''; -const CLAUDE_DIR = join(HOME, '.claude'); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'); const LIFEOS_DIR = join(CLAUDE_DIR, 'LIFEOS'); const HOOKS_DIR = join(CLAUDE_DIR, 'hooks'); diff --git a/LifeOS/install/LIFEOS/TOOLS/FeatureRegistry.ts b/LifeOS/install/LIFEOS/TOOLS/FeatureRegistry.ts index 9b216e986c..c48a44b6cb 100755 --- a/LifeOS/install/LIFEOS/TOOLS/FeatureRegistry.ts +++ b/LifeOS/install/LIFEOS/TOOLS/FeatureRegistry.ts @@ -55,7 +55,7 @@ interface FeatureRegistry { }; } -const REGISTRY_DIR = join(process.env.HOME || '', '.claude', 'LIFEOS', 'MEMORY', 'STATE', 'progress'); +const REGISTRY_DIR = join(process.env.LIFEOS_DIR || join(process.env.HOME || '', '.claude', 'LIFEOS'), 'MEMORY', 'STATE', 'progress'); function getRegistryPath(project: string): string { // Prevent path traversal: project is joined into a filename, so restrict it diff --git a/LifeOS/install/LIFEOS/TOOLS/GenerateTelosSummary.ts b/LifeOS/install/LIFEOS/TOOLS/GenerateTelosSummary.ts index 3f2b52a287..1420f597a8 100755 --- a/LifeOS/install/LIFEOS/TOOLS/GenerateTelosSummary.ts +++ b/LifeOS/install/LIFEOS/TOOLS/GenerateTelosSummary.ts @@ -370,7 +370,7 @@ function principalDisplayName(): string { } if (coreName) return coreName; try { - const settings = JSON.parse(readFileSync(join(process.env.HOME!, '.claude', 'settings.json'), 'utf-8')); + const settings = JSON.parse(readFileSync(join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME!, '.claude'), 'settings.json'), 'utf-8')); const name = settings?.principal?.name; if (typeof name === 'string' && name.trim() && !looksLikeToken(name)) return name.trim(); } catch { /* no settings.json โ€” fall through */ } diff --git a/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts b/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts index 02ae488c95..d4d941e41d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts +++ b/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts @@ -143,7 +143,7 @@ function countSkills(): number { * count โ€” only what Claude Code will actually fire. */ function countHooks(): number { - const settingsPath = join(HOME, ".claude", "settings.json"); + const settingsPath = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "settings.json"); try { const fs = require('fs'); const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); diff --git a/LifeOS/install/LIFEOS/TOOLS/HealthSync.ts b/LifeOS/install/LIFEOS/TOOLS/HealthSync.ts index bfe00876c3..89d4d17776 100755 --- a/LifeOS/install/LIFEOS/TOOLS/HealthSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/HealthSync.ts @@ -42,7 +42,7 @@ type CliCommand = "pull" | "status" | "current" | "auth"; const HOME = process.env.HOME || ""; const PREFIX = "[HealthSync]"; const SOURCE_NAMES: readonly SourceName[] = ["oura", "eightsleep", "apple", "function"]; -const CURRENT_PATH = join(HOME, ".claude", "LIFEOS", "USER", "HEALTH", "current.json"); +const CURRENT_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER", "HEALTH", "current.json"); const HEALTHSYNC_LOG_PATH = join( HOME, ".claude", diff --git a/LifeOS/install/LIFEOS/TOOLS/ISARender.ts b/LifeOS/install/LIFEOS/TOOLS/ISARender.ts index aa6520e5cf..3e20105bc1 100644 --- a/LifeOS/install/LIFEOS/TOOLS/ISARender.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ISARender.ts @@ -21,15 +21,16 @@ import { spawn } from "node:child_process"; import { homedir } from "node:os"; const HOME = process.env.HOME || homedir(); -const TOOLS_DIR = resolve(HOME, ".claude/LIFEOS/TOOLS"); +const LIFEOS_ROOT = process.env.LIFEOS_DIR || resolve(HOME, ".claude/LIFEOS"); +const TOOLS_DIR = resolve(LIFEOS_ROOT, "TOOLS"); const TEMPLATE_HTML = join(TOOLS_DIR, "ISARender/template.html"); const TEMPLATE_CSS = join(TOOLS_DIR, "ISARender/template.css"); // Brand logo: user override via LIFEOS_BRAND_LOGO_PATH env var (absolute path), // else system default under PAI/ASSETS/, else inert (empty src). const BRAND_LOGO_PATH_OVERRIDE = process.env.LIFEOS_BRAND_LOGO_PATH ?? ""; -const BRAND_LOGO_PATH_DEFAULT = resolve(HOME, ".claude/LIFEOS/ASSETS/pai-logo.png"); -const WORK_DIR = resolve(HOME, ".claude/LIFEOS/MEMORY/WORK"); -const WORK_JSON = resolve(HOME, ".claude/LIFEOS/MEMORY/STATE/work.json"); +const BRAND_LOGO_PATH_DEFAULT = resolve(LIFEOS_ROOT, "ASSETS/pai-logo.png"); +const WORK_DIR = resolve(LIFEOS_ROOT, "MEMORY/WORK"); +const WORK_JSON = resolve(LIFEOS_ROOT, "MEMORY/STATE/work.json"); const PHASES = ["observe", "think", "plan", "build", "execute", "verify", "learn", "complete"]; diff --git a/LifeOS/install/LIFEOS/TOOLS/Inference.ts b/LifeOS/install/LIFEOS/TOOLS/Inference.ts index 5fc9bc37e4..7139db54b3 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Inference.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Inference.ts @@ -177,7 +177,7 @@ export function verifyExecutedModel(modelUsage: unknown, expectedTier: string): * exact drift this catches and makes auditable. Logging must never break inference. */ function logModelVerification(entry: Record): void { try { - const dir = join(process.env.HOME || '', '.claude', 'LIFEOS', 'MEMORY', 'OBSERVABILITY'); + const dir = join(process.env.LIFEOS_DIR || join(process.env.HOME || '', '.claude', 'LIFEOS'), 'MEMORY', 'OBSERVABILITY'); mkdirSync(dir, { recursive: true }); appendFileSync(join(dir, 'model-verification.jsonl'), JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n'); } catch { /* observability must never break inference */ } diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts b/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts index bc0732cf99..a222b6fbc3 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts @@ -17,7 +17,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.blogdiscovery.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.blogdiscovery.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.blogdiscovery.plist"); const LABEL = "com.lifeos.blogdiscovery"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts index d6dd3f20e3..39e0b726e9 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts @@ -19,7 +19,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.bookmarksweep.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.bookmarksweep.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.bookmarksweep.plist"); const LABEL = "com.lifeos.bookmarksweep"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts b/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts index b5d9e4d6e2..894ee5f68b 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts @@ -18,7 +18,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.codexupdate.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.codexupdate.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.codexupdate.plist"); const LABEL = "com.lifeos.codexupdate"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts index 66a55f3b6c..4cdafe399c 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts @@ -14,10 +14,10 @@ import { join } from "path"; import { spawnSync } from "child_process"; const HOME = process.env.HOME || ""; -const TEMPLATE = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.commitmentsweep.plist.template"); +const TEMPLATE = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.commitmentsweep.plist.template"); const TARGET_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET = join(TARGET_DIR, "com.lifeos.commitmentsweep.plist"); -const STATE_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "STATE"); +const STATE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "STATE"); const LABEL = "com.lifeos.commitmentsweep"; function uid(): string { diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts index edaa71b82b..13281d24c1 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts @@ -20,7 +20,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.conveyor-runner.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.conveyor-runner.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.conveyor-runner.plist"); const LABEL = "com.lifeos.conveyor-runner"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts index 7b8f89a325..990072aca2 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts @@ -19,7 +19,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.conveyor-watcher.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.conveyor-watcher.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.conveyor-watcher.plist"); const LABEL = "com.lifeos.conveyor-watcher"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts b/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts index fca3b87ea0..2114d0cdb2 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts @@ -34,7 +34,7 @@ type LaunchctlResult = { }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.derivedsync.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.derivedsync.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.derivedsync.plist"); const LABEL = "com.lifeos.derivedsync"; @@ -87,7 +87,7 @@ async function install(): Promise { } const bunPath = await detectBun(); const bunDir = bunPath.replace(/\/bun$/, ""); - const userDir = realpathSync(join(HOME, ".claude", "LIFEOS", "USER")); + const userDir = realpathSync(join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER")); console.log(`[InstallDerivedSync] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts b/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts index 4a8b70dd9f..807162e445 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts @@ -30,7 +30,7 @@ type LaunchctlResult = { }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.healthsync.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.healthsync.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.healthsync.plist"); const LABEL = "com.lifeos.healthsync"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts b/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts index 37c8d740a4..746076730b 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts @@ -17,7 +17,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.usage-aggregator.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.usage-aggregator.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.usage-aggregator.plist"); const LABEL = "com.lifeos.usage-aggregator"; diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts index aff4ff2154..ed288ef3c1 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts @@ -19,7 +19,7 @@ import { join } from "path"; declare const Bun: { spawn: (cmd: string[], opts?: any) => any }; const HOME = process.env.HOME || ""; -const TEMPLATE_PATH = join(HOME, ".claude", "LIFEOS", "TOOLS", "com.lifeos.worksweep.plist.template"); +const TEMPLATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS", "com.lifeos.worksweep.plist.template"); const LAUNCH_AGENTS_DIR = join(HOME, "Library", "LaunchAgents"); const TARGET_PLIST = join(LAUNCH_AGENTS_DIR, "com.lifeos.worksweep.plist"); const LABEL = "com.lifeos.worksweep"; diff --git a/LifeOS/install/LIFEOS/TOOLS/IntegrityCheck.ts b/LifeOS/install/LIFEOS/TOOLS/IntegrityCheck.ts index da9409e15e..43e4c5e4a8 100755 --- a/LifeOS/install/LIFEOS/TOOLS/IntegrityCheck.ts +++ b/LifeOS/install/LIFEOS/TOOLS/IntegrityCheck.ts @@ -35,7 +35,7 @@ import { join } from 'path'; import { execFileSync } from 'child_process'; const HOME = process.env.HOME || ''; -const CLAUDE_DIR = join(HOME, '.claude'); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'); const LIFEOS_DIR = join(CLAUDE_DIR, 'LIFEOS'); const TOOLS_DIR = join(LIFEOS_DIR, 'TOOLS'); const DOC_DIR = join(LIFEOS_DIR, 'DOCUMENTATION'); diff --git a/LifeOS/install/LIFEOS/TOOLS/LearningPatternSynthesis.ts b/LifeOS/install/LIFEOS/TOOLS/LearningPatternSynthesis.ts index 9aa5b24e7e..fdb616c21d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/LearningPatternSynthesis.ts +++ b/LifeOS/install/LIFEOS/TOOLS/LearningPatternSynthesis.ts @@ -31,7 +31,7 @@ import * as crypto from "crypto"; // Configuration // ============================================================================ -const CLAUDE_DIR = path.join(process.env.HOME!, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(process.env.HOME!, ".claude"); const LIFEOS_DIR = path.join(CLAUDE_DIR, "LIFEOS"); const MEMORY_DIR = path.join(LIFEOS_DIR, "MEMORY"); const LEARNING_DIR = path.join(MEMORY_DIR, "LEARNING"); diff --git a/LifeOS/install/LIFEOS/TOOLS/LifeosUpgrade.ts b/LifeOS/install/LIFEOS/TOOLS/LifeosUpgrade.ts index 5007662fa3..652583ff9e 100755 --- a/LifeOS/install/LIFEOS/TOOLS/LifeosUpgrade.ts +++ b/LifeOS/install/LIFEOS/TOOLS/LifeosUpgrade.ts @@ -22,7 +22,7 @@ import { join } from "node:path"; import { homedir } from "node:os"; const HOME = process.env.HOME ?? homedir(); -const CLAUDE_ROOT = join(HOME, ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); interface MigrationContext { claudeRoot: string; diff --git a/LifeOS/install/LIFEOS/TOOLS/LoadSkillConfig.ts b/LifeOS/install/LIFEOS/TOOLS/LoadSkillConfig.ts index 40d6fe0af7..eaca74896e 100755 --- a/LifeOS/install/LIFEOS/TOOLS/LoadSkillConfig.ts +++ b/LifeOS/install/LIFEOS/TOOLS/LoadSkillConfig.ts @@ -35,7 +35,7 @@ interface ExtendManifest { // Constants const HOME = homedir(); -const CUSTOMIZATION_DIR = join(HOME, '.claude', 'LIFEOS', 'USER', 'SKILLCUSTOMIZATIONS'); +const CUSTOMIZATION_DIR = join(process.env.LIFEOS_DIR || join(HOME, '.claude', 'LIFEOS'), 'USER', 'SKILLCUSTOMIZATIONS'); /** * Deep merge two objects recursively diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts index 9dfe303b51..870a7cb463 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryHealthCheck.ts @@ -25,7 +25,7 @@ import { existsSync, readFileSync, appendFileSync, mkdirSync, readdirSync, statS import { join } from "node:path"; const HOME = process.env.HOME || ""; -const CLAUDE = join(HOME, ".claude"); +const CLAUDE = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const HOOKS_DIR = join(CLAUDE, "hooks"); const TOOLS_DIR = join(CLAUDE, "LIFEOS/TOOLS"); const OBS_DIR = join(CLAUDE, "LIFEOS/MEMORY/OBSERVABILITY"); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryRetriever.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryRetriever.ts index 3922d54ab7..0d2b757135 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryRetriever.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryRetriever.ts @@ -457,8 +457,8 @@ function formatResults( // dual-tier prefetch, no graph traversal on hot path). const MEMORY_FILES: ReadonlyArray<{ path: string; title: string }> = [ - { path: path.join(HOME, ".claude", "LIFEOS", "USER", "PRINCIPAL", "PRINCIPAL_MEMORY.md"), title: "Principal Memory" }, - { path: path.join(HOME, ".claude", "LIFEOS", "USER", "DIGITAL_ASSISTANT", "DA_MEMORY.md"), title: "DA Memory" }, + { path: path.join(process.env.LIFEOS_DIR || path.join(HOME, ".claude", "LIFEOS"), "USER", "PRINCIPAL", "PRINCIPAL_MEMORY.md"), title: "Principal Memory" }, + { path: path.join(process.env.LIFEOS_DIR || path.join(HOME, ".claude", "LIFEOS"), "USER", "DIGITAL_ASSISTANT", "DA_MEMORY.md"), title: "DA Memory" }, ]; const RELEVANT_CACHE_TTL_MS = 60_000; diff --git a/LifeOS/install/LIFEOS/TOOLS/NeofetchBanner.ts b/LifeOS/install/LIFEOS/TOOLS/NeofetchBanner.ts index a9a11fee72..796c7c4b4d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/NeofetchBanner.ts +++ b/LifeOS/install/LIFEOS/TOOLS/NeofetchBanner.ts @@ -21,7 +21,7 @@ import { spawnSync } from "child_process"; import { paiUserDir } from "./LifeosConfig"; const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Terminal Width Detection diff --git a/LifeOS/install/LIFEOS/TOOLS/ReferenceCheck.ts b/LifeOS/install/LIFEOS/TOOLS/ReferenceCheck.ts index b3b506995d..62e6c80d8a 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ReferenceCheck.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ReferenceCheck.ts @@ -29,7 +29,7 @@ import { join, resolve, dirname, relative, extname, sep } from 'path'; import { execSync } from 'child_process'; const HOME = process.env.HOME || ''; -const CLAUDE_DIR = join(HOME, '.claude'); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'); const LIFEOS_DIR = join(CLAUDE_DIR, 'LIFEOS'); // โ”€โ”€ Arg parsing (manual, zero deps) โ”€โ”€ diff --git a/LifeOS/install/LIFEOS/TOOLS/Services.ts b/LifeOS/install/LIFEOS/TOOLS/Services.ts index 649e9ebea9..9006838c12 100755 --- a/LifeOS/install/LIFEOS/TOOLS/Services.ts +++ b/LifeOS/install/LIFEOS/TOOLS/Services.ts @@ -17,7 +17,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; const HOME = homedir(); -const CLAUDE = join(HOME, ".claude"); +const CLAUDE = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const LIFEOS = join(CLAUDE, "LIFEOS"); const TOOLS = join(LIFEOS, "TOOLS"); const PULSE = join(LIFEOS, "PULSE"); diff --git a/LifeOS/install/LIFEOS/TOOLS/SessionHarvester.ts b/LifeOS/install/LIFEOS/TOOLS/SessionHarvester.ts index d0501ee871..c5b77af619 100755 --- a/LifeOS/install/LIFEOS/TOOLS/SessionHarvester.ts +++ b/LifeOS/install/LIFEOS/TOOLS/SessionHarvester.ts @@ -28,7 +28,7 @@ import { getLearningCategory, isLearningCapture } from "../../../.claude/hooks/l // Configuration // ============================================================================ -const CLAUDE_DIR = path.join(process.env.HOME!, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(process.env.HOME!, ".claude"); // Derive the project slug dynamically from CLAUDE_DIR (works on macOS and Linux) // macOS: ${HOME}/.claude โ†’ ${HARNESS_USER_DIR} // Linux: ${HOME}/.claude โ†’ ${HARNESS_USER_DIR} diff --git a/LifeOS/install/LIFEOS/TOOLS/SessionProgress.ts b/LifeOS/install/LIFEOS/TOOLS/SessionProgress.ts index 8986f85348..74d6f238a7 100755 --- a/LifeOS/install/LIFEOS/TOOLS/SessionProgress.ts +++ b/LifeOS/install/LIFEOS/TOOLS/SessionProgress.ts @@ -44,7 +44,7 @@ interface SessionProgress { } // Progress files are now in STATE/progress/ (consolidated from MEMORY/PROGRESS/) -const PROGRESS_DIR = join(process.env.HOME || '', '.claude', 'LIFEOS', 'MEMORY', 'STATE', 'progress'); +const PROGRESS_DIR = join(process.env.LIFEOS_DIR || join(process.env.HOME || '', '.claude', 'LIFEOS'), 'MEMORY', 'STATE', 'progress'); function getProgressPath(project: string): string { return join(PROGRESS_DIR, `${project}-progress.json`); diff --git a/LifeOS/install/LIFEOS/TOOLS/SyncIdentityToSettings.ts b/LifeOS/install/LIFEOS/TOOLS/SyncIdentityToSettings.ts index 37fce1d66f..f1da87e7b8 100644 --- a/LifeOS/install/LIFEOS/TOOLS/SyncIdentityToSettings.ts +++ b/LifeOS/install/LIFEOS/TOOLS/SyncIdentityToSettings.ts @@ -22,7 +22,7 @@ import { paiUserDir } from './LifeosConfig'; const HOME = homedir(); const PRINCIPAL_PATH = join(paiUserDir(), 'PRINCIPAL/PRINCIPAL_IDENTITY.md'); -const SETTINGS_PATH = join(HOME, '.claude/settings.json'); +const SETTINGS_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'), 'settings.json'); const VERBOSE = process.argv.includes('--verbose'); function snakeToCamel(s: string): string { diff --git a/LifeOS/install/LIFEOS/TOOLS/TlpArchive.ts b/LifeOS/install/LIFEOS/TOOLS/TlpArchive.ts index d18ad15243..a50f8812b0 100755 --- a/LifeOS/install/LIFEOS/TOOLS/TlpArchive.ts +++ b/LifeOS/install/LIFEOS/TOOLS/TlpArchive.ts @@ -16,7 +16,7 @@ import { writeFileSync, existsSync, readFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; const HOME = process.env.HOME!; -const KNOWLEDGE_DIR = join(HOME, ".claude/LIFEOS/MEMORY/KNOWLEDGE/Blogs"); +const KNOWLEDGE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude/LIFEOS"), "MEMORY/KNOWLEDGE/Blogs"); const URL_FILE = "/tmp/tlp-urls.txt"; const FAILED_FILE = "/tmp/tlp-failed.txt"; const SUCCESS_FILE = "/tmp/tlp-success.txt"; diff --git a/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts index 1e090fd46c..60e9ab563d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts @@ -615,7 +615,7 @@ async function main(): Promise { // Final step: regenerate the TASKLIST.md and push (best-effort, never blocks) if (!dryRun) { const proc = Bun.spawn( - ["bun", join(HOME, ".claude", "skills", "_ULWORK", "Tools", "RegenerateTasklist.ts"), "--commit-push"], + ["bun", join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills", "_ULWORK", "Tools", "RegenerateTasklist.ts"), "--commit-push"], { stdout: "inherit", stderr: "inherit", timeout: 30000 }, ); await proc.exited; diff --git a/LifeOS/install/LIFEOS/TOOLS/healthsync/store.ts b/LifeOS/install/LIFEOS/TOOLS/healthsync/store.ts index ac2b583019..1f379f945f 100644 --- a/LifeOS/install/LIFEOS/TOOLS/healthsync/store.ts +++ b/LifeOS/install/LIFEOS/TOOLS/healthsync/store.ts @@ -10,10 +10,10 @@ import type { } from "./types"; const HOME = process.env.HOME || ""; -const ENV_PATH = join(HOME, ".claude", ".env"); -const STATE_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "STATE"); -const DATA_DIR = join(HOME, ".claude", "LIFEOS", "USER", "HEALTH", "DATA"); -const OBS_DIR = join(HOME, ".claude", "LIFEOS", "MEMORY", "OBSERVABILITY"); +const ENV_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env"); +const STATE_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "STATE"); +const DATA_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "USER", "HEALTH", "DATA"); +const OBS_DIR = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "OBSERVABILITY"); const TOKENS_PATH = join(STATE_DIR, "healthsync-tokens.json"); const STATE_PATH = join(STATE_DIR, "healthsync-state.json"); diff --git a/LifeOS/install/hooks/AlgorithmNudge.hook.ts b/LifeOS/install/hooks/AlgorithmNudge.hook.ts index a8719687cf..3e5aeef6bf 100755 --- a/LifeOS/install/hooks/AlgorithmNudge.hook.ts +++ b/LifeOS/install/hooks/AlgorithmNudge.hook.ts @@ -49,7 +49,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs'; import { join } from 'path'; -const PAI = join(process.env.HOME || '', '.claude'); +const PAI = process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME || '', '.claude'); const WORK_JSON = join(PAI, 'LIFEOS', 'MEMORY', 'STATE', 'work.json'); const STATE_DIR = join(PAI, 'LIFEOS', 'MEMORY', 'STATE', 'isa-nudge'); const SKILLS_DIR = join(PAI, 'skills'); diff --git a/LifeOS/install/hooks/EgressClassGuard.hook.ts b/LifeOS/install/hooks/EgressClassGuard.hook.ts index 00443658b0..490de0391e 100755 --- a/LifeOS/install/hooks/EgressClassGuard.hook.ts +++ b/LifeOS/install/hooks/EgressClassGuard.hook.ts @@ -24,7 +24,7 @@ import { homedir } from "node:os"; import { evaluateEgress } from "./lib/egress-class-core"; const HOME = process.env.HOME ?? homedir(); -const LOG_PATH = join(HOME, ".claude/LIFEOS/MEMORY/OBSERVABILITY/egress-decisions.jsonl"); +const LOG_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude/LIFEOS"), "MEMORY/OBSERVABILITY/egress-decisions.jsonl"); interface HookInput { session_id?: string; diff --git a/LifeOS/install/hooks/ReminderRouter.hook.ts b/LifeOS/install/hooks/ReminderRouter.hook.ts index cb96fc6711..17a9d7fc7a 100755 --- a/LifeOS/install/hooks/ReminderRouter.hook.ts +++ b/LifeOS/install/hooks/ReminderRouter.hook.ts @@ -27,7 +27,7 @@ import { join, dirname } from "path"; import { loadWorkConfig } from "./lib/work-config"; const HOME = process.env.HOME || ""; -const STATE_PATH = join(HOME, ".claude", "LIFEOS", "MEMORY", "STATE", "reminder-router-seen.json"); +const STATE_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY", "STATE", "reminder-router-seen.json"); interface HookInput { session_id?: string; diff --git a/LifeOS/install/hooks/handlers/UpdateCounts.ts b/LifeOS/install/hooks/handlers/UpdateCounts.ts index 46f678872d..c9d825dcbe 100755 --- a/LifeOS/install/hooks/handlers/UpdateCounts.ts +++ b/LifeOS/install/hooks/handlers/UpdateCounts.ts @@ -39,7 +39,7 @@ async function refreshUsageCache(paiDir: string): Promise { { encoding: 'utf-8', timeout: 3000 } ).trim(); } else { - const credPath = join(process.env.HOME || '', '.claude', '.credentials.json'); + const credPath = join(process.env.CLAUDE_CONFIG_DIR || join(process.env.HOME || '', '.claude'), '.credentials.json'); credJson = readFileSync(credPath, 'utf-8').trim(); } diff --git a/LifeOS/install/hooks/lib/identity.ts b/LifeOS/install/hooks/lib/identity.ts index 0ae796f9cc..f83d61d480 100755 --- a/LifeOS/install/hooks/lib/identity.ts +++ b/LifeOS/install/hooks/lib/identity.ts @@ -16,7 +16,7 @@ import { parse as parseYaml } from 'yaml'; import { loadLifeosConfig } from '../../LIFEOS/TOOLS/LifeosConfig'; const HOME = process.env.HOME!; -const SETTINGS_PATH = join(HOME, '.claude/settings.json'); +const SETTINGS_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'), 'settings.json'); // Identity-file paths derive from LifeosConfig's userDir. On fresh installs where // LIFEOS_CONFIG.toml hasn't been created yet, fall back to the conventional @@ -26,7 +26,7 @@ function paiUserDir(): string { try { return loadLifeosConfig().paths.userDir; } catch { - return join(HOME, '.claude/LIFEOS/USER'); + return join(process.env.LIFEOS_DIR || join(HOME, '.claude/LIFEOS'), 'USER'); } } const DA_IDENTITY_PATH = join(paiUserDir(), 'DIGITAL_ASSISTANT/DA_IDENTITY.md'); diff --git a/LifeOS/install/hooks/lib/notifications.ts b/LifeOS/install/hooks/lib/notifications.ts index 54a52f4e47..f9233ea14e 100755 --- a/LifeOS/install/hooks/lib/notifications.ts +++ b/LifeOS/install/hooks/lib/notifications.ts @@ -9,7 +9,7 @@ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join } from 'path'; const HOME = process.env.HOME!; -const PULSE_TOML_PATH = join(HOME, '.claude/LIFEOS/PULSE/PULSE.toml'); +const PULSE_TOML_PATH = join(process.env.LIFEOS_DIR || join(HOME, '.claude/LIFEOS'), 'PULSE/PULSE.toml'); // ============================================================================ // Session Timing diff --git a/LifeOS/install/hooks/lib/safety-classifier.ts b/LifeOS/install/hooks/lib/safety-classifier.ts index 0fba1d8515..b478184da6 100755 --- a/LifeOS/install/hooks/lib/safety-classifier.ts +++ b/LifeOS/install/hooks/lib/safety-classifier.ts @@ -32,7 +32,7 @@ export interface ToolCall { const HOME = homedir(); export const TRUSTED_PREFIXES: readonly string[] = [ - resolve(HOME, ".claude"), + process.env.CLAUDE_CONFIG_DIR || resolve(HOME, ".claude"), resolve(HOME, "Projects"), resolve(HOME, "LocalProjects"), resolve(HOME, "Downloads"), diff --git a/LifeOS/install/hooks/lib/system-file-guard-core.ts b/LifeOS/install/hooks/lib/system-file-guard-core.ts index 3ab27badbe..ed52bdad4a 100644 --- a/LifeOS/install/hooks/lib/system-file-guard-core.ts +++ b/LifeOS/install/hooks/lib/system-file-guard-core.ts @@ -17,7 +17,7 @@ import { createHash } from "node:crypto"; import { isContained, isPatternAllowlisted, relativeToClaudeRoot } from "./containment-zones"; const HOME = process.env.HOME ?? homedir(); -const CLAUDE_ROOT = join(HOME, ".claude"); +const CLAUDE_ROOT = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const DEFAULT_DENY_LIST_PATH = join(CLAUDE_ROOT, "skills/_LIFEOS/DENY_LIST.txt"); const DEFAULT_HASHES_PATH = join(CLAUDE_ROOT, "skills/_LIFEOS/DENY_HASHES.json"); const DEFAULT_ENV_PATH = join(CLAUDE_ROOT, ".env"); diff --git a/LifeOS/install/skills/Art/Tools/Generate.ts b/LifeOS/install/skills/Art/Tools/Generate.ts index 2d07f23d10..c3a68e9516 100755 --- a/LifeOS/install/skills/Art/Tools/Generate.ts +++ b/LifeOS/install/skills/Art/Tools/Generate.ts @@ -352,7 +352,7 @@ MORE INFO: * --workflow= โ†’ exit 1 listing valid workflow names. */ function enforceWorkflowDiscipline(parsed: Partial): void { - const workflowsDir = `${process.env.HOME}/.claude/skills/Art/Workflows`; + const workflowsDir = `${process.env.CLAUDE_CONFIG_DIR || `${process.env.HOME}/.claude`}/skills/Art/Workflows`; let availableWorkflows: string[] = []; try { // readdirSync via Bun.readdirSync isn't a thing; use Node fs sync via dynamic diff --git a/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts b/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts index 1a95fce65d..5f9f8fee8c 100755 --- a/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts +++ b/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts @@ -352,7 +352,7 @@ async function searchIsaBodies(tokens: string[], since: Date | null, until: Date async function searchJsonl(tokens: string[], since: Date | null, until: Date | null = null): Promise { if (tokens.length === 0 || !existsSync(JSONL_DIR)) return []; const realDir = JSONL_DIR; - if (!realDir.startsWith(join(HOME, ".claude", "Projects", PROJECT_SLUG))) return []; + if (!realDir.startsWith(join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "Projects", PROJECT_SLUG))) return []; const pattern = tokens.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"); const out = await ripgrep(pattern, realDir, ["--glob", "*.jsonl", "-c"]); const byFile = new Map(); diff --git a/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts b/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts index 5b1066f6d8..9af39177f0 100755 --- a/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts +++ b/LifeOS/install/skills/Daemon/Tools/DaemonAggregator.ts @@ -395,7 +395,7 @@ function readExistingDaemon(): Record { const daemonPath = join(USER_DAEMON_DIR, "daemon.md"); if (!existsSync(daemonPath)) { // Fall back to old location - const oldPath = join(HOME, ".claude", "skills", "_DAEMON", "Mcp", "daemon.md"); + const oldPath = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills", "_DAEMON", "Mcp", "daemon.md"); if (!existsSync(oldPath)) return {}; return parseDaemonMd(readFileSync(oldPath, "utf-8")); } diff --git a/LifeOS/install/skills/Telos/Tools/UpdateTelos.ts b/LifeOS/install/skills/Telos/Tools/UpdateTelos.ts index 2e839d5c23..e524bddc3b 100755 --- a/LifeOS/install/skills/Telos/Tools/UpdateTelos.ts +++ b/LifeOS/install/skills/Telos/Tools/UpdateTelos.ts @@ -38,7 +38,7 @@ import { readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync, readd import { join } from 'path'; import { getPrincipal } from '../../../hooks/lib/identity'; -const TELOS_DIR = join(process.env.HOME!, '.claude', 'LIFEOS', 'USER', 'TELOS'); +const TELOS_DIR = join(process.env.LIFEOS_DIR || join(process.env.HOME!, '.claude', 'LIFEOS'), 'USER', 'TELOS'); const BACKUPS_DIR = join(TELOS_DIR, 'Backups'); // Changelog file: prefer whichever casing already exists (older installs used // 'Updates.md'; the docs and scaffold use 'updates.md'), so case-sensitive diff --git a/LifeOS/install/skills/Upgrade/Tools/Anthropic.ts b/LifeOS/install/skills/Upgrade/Tools/Anthropic.ts index b9259033e5..d3bedebe3a 100755 --- a/LifeOS/install/skills/Upgrade/Tools/Anthropic.ts +++ b/LifeOS/install/skills/Upgrade/Tools/Anthropic.ts @@ -85,7 +85,7 @@ interface State { // Config const HOME = homedir(); -const SKILL_DIR = join(HOME, '.claude', 'skills', 'Upgrade'); +const SKILL_DIR = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, '.claude'), 'skills', 'Upgrade'); const STATE_DIR = join(SKILL_DIR, 'State'); const STATE_FILE = join(STATE_DIR, 'last-check.json'); const SOURCES_FILE = join(SKILL_DIR, 'sources.json'); From 400d59dead38993c49eddb78b8c91348bc44fd84 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:40:12 +0530 Subject: [PATCH 08/12] InterviewScan.ts: fix the .env/credentials path this whole investigation started from MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found this exact line early on but it got lost in the broader sweep instead of being fixed directly โ€” this is the actual root cause of the "Module not found" error reported live during a /interview session (InterviewScan.ts itself resolved fine via LIFEOS_DIR at line 38; only this one credential-check path at line 101 was still hardcoded to ~/.claude/.env). --- LifeOS/install/LIFEOS/TOOLS/InterviewScan.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LifeOS/install/LIFEOS/TOOLS/InterviewScan.ts b/LifeOS/install/LIFEOS/TOOLS/InterviewScan.ts index 86546bf4c3..63e5a1b659 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InterviewScan.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InterviewScan.ts @@ -98,7 +98,7 @@ const REGISTRY: RegistryTarget[] = [ prompts: ["Main DA voice โ€” pick from ElevenLabs library, or stick with default Rachel (21m00Tcm4TlvDq8ikWAM)?", "Algorithm voice (used for phase transitions) โ€” default Adam (pNInz6obpgDQGcFmaJgB) is fine?", "Want voice notifications on by default? (default: yes)"] }, - { phase: 0, path: join(HOME, ".claude", ".env"), name: ".env/credentials", category: "setup", leverage: 10, + { phase: 0, path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env"), name: ".env/credentials", category: "setup", leverage: 10, prompts: ["ANTHROPIC_API_KEY โ€” required for inference. Paste here (will write to .env, won't echo back)?", "ELEVENLABS_API_KEY โ€” required for voice notifications. Skip if you don't want voice.", "GH_TOKEN โ€” optional, only if you want the work pipeline. Skip if not using GitHub issues.", From 09fcbb0515516c9ecb884a67e4ac649308d8b5e2 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 16:54:55 +0530 Subject: [PATCH 09/12] Add project-scoping path note to the 56 docs still showing literal ~/.claude MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md now carries a blanket rule that any ~/.claude/... in a doc's examples is illustrative, not fact โ€” but that only helps if the reader (AI or human) has CLAUDE.md loaded in context. Someone opening a skill's SKILL.md or a DOCUMENTATION/*.md file directly, without that context, would still hit the same "ran the literal global path, got Module not found" bug we found live. Added the same one-line reminder directly to each of the 56 affected docs, right after their title, so every doc is self-contained on this point. No other content touched โ€” frontmatter, existing example commands, and body text are unchanged; this is purely an additive callout. --- .../install/LIFEOS/DOCUMENTATION/Freshness/FreshnessSystem.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Isa/IsaHtmlMirror.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Memory/MemorySystem.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Pulse/PulseSystem.md | 2 ++ .../install/LIFEOS/DOCUMENTATION/Services/BackgroundServices.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Skills/SkillSystem.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/SystemUserBoundary.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Cli.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Tools.md | 2 ++ LifeOS/install/LIFEOS/DOCUMENTATION/Work/WorkSystem.md | 2 ++ LifeOS/install/skills/Apify/INTEGRATION.md | 2 ++ LifeOS/install/skills/Art/SKILL.md | 2 ++ LifeOS/install/skills/Art/Workflows/AdHocYouTubeThumbnail.md | 2 ++ LifeOS/install/skills/Art/Workflows/Essay.md | 2 ++ LifeOS/install/skills/Art/Workflows/Mermaid.md | 2 ++ LifeOS/install/skills/Art/Workflows/RemoveBackground.md | 2 ++ LifeOS/install/skills/Art/Workflows/StyleMatchedThumbnail.md | 2 ++ LifeOS/install/skills/Art/Workflows/Visualize.md | 2 ++ LifeOS/install/skills/AudioEditor/Tools/Analyze.help.md | 2 ++ LifeOS/install/skills/AudioEditor/Tools/Edit.help.md | 2 ++ LifeOS/install/skills/AudioEditor/Tools/Pipeline.help.md | 2 ++ LifeOS/install/skills/AudioEditor/Tools/Polish.help.md | 2 ++ LifeOS/install/skills/AudioEditor/Tools/Transcribe.help.md | 2 ++ LifeOS/install/skills/AudioEditor/Workflows/Clean.md | 2 ++ LifeOS/install/skills/CMUX/DESIGN.md | 2 ++ LifeOS/install/skills/CMUX/SKILL.md | 2 ++ LifeOS/install/skills/CMUX/Workflows/AgentRace.md | 2 ++ LifeOS/install/skills/CMUX/Workflows/BootTeam.md | 2 ++ LifeOS/install/skills/CMUX/Workflows/Fleet.md | 2 ++ LifeOS/install/skills/CMUX/Workflows/Monitor.md | 2 ++ LifeOS/install/skills/CreateSkill/Workflows/ValidateSkill.md | 2 ++ LifeOS/install/skills/Fabric/Workflows/ExecutePattern.md | 2 ++ LifeOS/install/skills/HTML/SKILL.md | 2 ++ LifeOS/install/skills/HTML/Workflows/Render.md | 2 ++ LifeOS/install/skills/Harvest/SKILL.md | 2 ++ LifeOS/install/skills/Harvest/Workflows/Harvest.md | 2 ++ LifeOS/install/skills/Interview/SKILL.md | 2 ++ LifeOS/install/skills/Interview/Workflows/ContextCheckin.md | 2 ++ LifeOS/install/skills/Interview/Workflows/Phase0Setup.md | 2 ++ LifeOS/install/skills/Knowledge/SKILL.md | 2 ++ LifeOS/install/skills/Migrate/SKILL.md | 2 ++ LifeOS/install/skills/Prompting/Standards.md | 2 ++ .../install/skills/Remotion/Workflows/GeneratedContentVideo.md | 2 ++ LifeOS/install/skills/Telos/Workflows/Update.md | 2 ++ LifeOS/install/skills/USMetrics/Workflows/UpdateData.md | 2 ++ LifeOS/install/skills/Upgrade/Workflows/Upgrade.md | 2 ++ LifeOS/install/skills/Webdesign/References/ExportFormats.md | 2 ++ LifeOS/install/skills/Webdesign/References/HandoffBundleSpec.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/CreatePrototype.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/DeployDesign.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/ExportToCode.md | 2 ++ .../install/skills/Webdesign/Workflows/ExtractDesignSystem.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/IntegrateIntoApp.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/RefinePrototype.md | 2 ++ LifeOS/install/skills/Webdesign/Workflows/WebsiteToRedesign.md | 2 ++ 56 files changed, 112 insertions(+) diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Freshness/FreshnessSystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Freshness/FreshnessSystem.md index ac68c7e17b..d4486aced8 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Freshness/FreshnessSystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Freshness/FreshnessSystem.md @@ -4,6 +4,8 @@ version: 1.2.3 # Freshness System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > Freshness protects the loop's inputs. The LifeOS (`LifeOs/LifeOsThesis.md`) hill-climbs against its picture of your current state and your TELOS โ€” if those files silently age, the OS optimizes toward a person who no longer exists. The convention below makes constitutional staleness visible before it becomes drift. Every file that loads into the DA's context at session start carries a known recency. This is the freshness convention โ€” `pai-freshness-v1`. It exists because constitutional context that drifts silently is worse than missing context: the model behaves with confidence on stale ground and the principal can't tell. diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md index c48b7dd4a0..b9e962226e 100755 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md @@ -9,6 +9,8 @@ version: 1.2.50 # Hook System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > Hooks are why the LifeOS runs when nobody is looking. The thesis (`LifeOs/LifeOsThesis.md`) assigns them their job: automate the loop โ€” capture current state, enforce doctrine, fire the next move โ€” without the principal having to ask. A hill-climb that only advances when prompted is a chatbot; the hook lifecycle below is what makes it an operating system. > **LifeOS 5.0** โ€” Stable event-driven automation infrastructure. diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Isa/IsaHtmlMirror.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Isa/IsaHtmlMirror.md index f85e2a5fa7..2a42092f42 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Isa/IsaHtmlMirror.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Isa/IsaHtmlMirror.md @@ -7,6 +7,8 @@ version: 1.0.15 # ISA HTML Mirror +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + The ISA is the canonical articulation of "done" for any Algorithm run. It lives as `ISA.md` markdown in a working directory the principal does not normally look at. The **HTML Mirror** is a deterministic, zero-token, branded sibling โ€” `ISA.html` next to every `ISA.md` โ€” produced by a fast CLI on completion events and refreshed in-place by Interceptor. ## Canonical Theme โ€” UL Technical v1.0.0 (2026-05-13) diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Memory/MemorySystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Memory/MemorySystem.md index 246da2bd74..78a8b9ea1e 100755 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Memory/MemorySystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Memory/MemorySystem.md @@ -4,6 +4,8 @@ version: 1.5.19 # Memory System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > In Life OS terms (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`), memory is how the OS knows your **current state**. The hill-climb is only as good as the system's picture of where you actually are โ€” what you're working on, what failed, who you know, what you learned. Every capture pipeline below exists to sharpen that picture so the gap to ideal state is measured against reality, not guesses. **LifeOS's file-system-based memory. Everything we know, everything we've learned, everything we've researched, everything we're working on.** diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Pulse/PulseSystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Pulse/PulseSystem.md index 4eb6ea7be9..e21b9bbdb6 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Pulse/PulseSystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Pulse/PulseSystem.md @@ -9,6 +9,8 @@ version: 1.6.17 # The Pulse System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Pulse is the Life Dashboard.** It is the visible surface of the LifeOS Life Operating System โ€” the place where you (and your DA) see and interact with everything the OS is doing. LifeOS is the OS; Pulse is how you watch it run. Every Pulse module is a sub-surface of the Dashboard: real-time observability, voice notifications, chat surfaces (iMessage/Telegram), scheduled work, background worker state, DA heartbeat, and โ€” as the dashboard grows โ€” live views of current state vs ideal state, goal progress, workflows, and day-in-the-life preview. A LifeOS with no dashboard would still be a LifeOS; Pulse is what keeps it visible. diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Services/BackgroundServices.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Services/BackgroundServices.md index 535c257102..5109afe0e0 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Services/BackgroundServices.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Services/BackgroundServices.md @@ -8,6 +8,8 @@ generator: LIFEOS/TOOLS/Services.ts (run `bun Services.ts doc` to regenerate the # LifeOS Background Services +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > **Every recurring Mac service LifeOS runs, in one place โ€” what it is, how often it fires, and how to install it. One tool controls them all: `LIFEOS/TOOLS/Services.ts`.** Before this doc, the install story was scattered across a dozen `Install*.ts` scripts, Pulse `manage.sh` files, and hand-installed launchd jobs, with nothing that listed what was actually running. This is the fix. ## The one-shot diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Skills/SkillSystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Skills/SkillSystem.md index bbc7080348..fb2ac57e99 100755 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Skills/SkillSystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Skills/SkillSystem.md @@ -9,6 +9,8 @@ version: 1.5.1 # Custom Skill System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > Skills are the Life OS's action surface. The thesis (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`) puts it directly: "Skills expand so the DA can take more actions to close the gap." Every skill added is a new class of move the DA can make in the currentโ†’ideal-state hill-climb; the structure rules below exist so those moves stay discoverable, composable, and safe to ship. **This document is the authoritative definition of the required structure for every LifeOS skill; all skill creation โ€” including CreateSkill's โ€” conforms to it. "Canonicalize a skill" = restructure to match this exact format, including TitleCase naming. A skill that doesn't follow it is not properly configured and will not work correctly.** diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/SystemUserBoundary.md b/LifeOS/install/LIFEOS/DOCUMENTATION/SystemUserBoundary.md index 7dc115f1a8..001d9c8a2a 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/SystemUserBoundary.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/SystemUserBoundary.md @@ -8,6 +8,8 @@ version: 1.1.4 # System / User Boundary +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > This boundary is the thesis's deepest structural commitment (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`): LifeOS the OS is universal and public; the life it runs is singular and private. The four zones below are how one repo can be everyone's Life OS without ever containing anyone's life. > The structural contract that makes the LifeOS live tree publishable by construction rather than by scrub. diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Cli.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Cli.md index f1f9b4577b..a3335742b1 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Cli.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Cli.md @@ -6,6 +6,8 @@ version: 1.1.10 # LifeOS Command-Line Tools +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > CLI-first is how the Life OS stays deterministic (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`): the hill-climb's moves are code you can script, test, and trust โ€” prompts orchestrate, code executes. LifeOS provides two CLI tools for running infrastructure from the terminal: diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Tools.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Tools.md index 3e33ad626c..a62ad695df 100755 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Tools.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Tools/Tools.md @@ -4,6 +4,8 @@ version: 1.8.0 # LifeOS Tools - CLI Utilities Reference +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > CLI-first is how the Life OS stays deterministic (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`): the hill-climb's moves are code you can script, test, and trust โ€” prompts orchestrate, code executes. This file documents single-purpose CLI utilities that have been consolidated from individual skills. These are pure command-line tools that wrap APIs or external commands. diff --git a/LifeOS/install/LIFEOS/DOCUMENTATION/Work/WorkSystem.md b/LifeOS/install/LIFEOS/DOCUMENTATION/Work/WorkSystem.md index 4f73bfcc7d..ce3f7fef0a 100644 --- a/LifeOS/install/LIFEOS/DOCUMENTATION/Work/WorkSystem.md +++ b/LifeOS/install/LIFEOS/DOCUMENTATION/Work/WorkSystem.md @@ -9,6 +9,8 @@ version: 1.0.3 # Work System +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > The Work System is the hill-climb's ledger (`LIFEOS/DOCUMENTATION/LifeOs/LifeOsThesis.md`). Every captured unit of work is a step taken toward ideal state, and the TELOS sweep is the loop closing on itself: an active goal with no open issue is a declared ideal state with no next move โ€” exactly the gap the OS exists to surface. > The Work System turns every meaningful unit of work the principal does โ€” Algorithm sessions, NATIVE work that touched files, explicit reminders, periodic check-ins on stale projects, TELOS goals without a next action โ€” into a labeled GitHub issue in one configured private repo. The repo is the system of record. The Pulse Work tab, the auto-regenerated TASKLIST.md, and the agent claim flow are all readers. diff --git a/LifeOS/install/skills/Apify/INTEGRATION.md b/LifeOS/install/skills/Apify/INTEGRATION.md index dbc9ad9dd8..aa22ea47c4 100755 --- a/LifeOS/install/skills/Apify/INTEGRATION.md +++ b/LifeOS/install/skills/Apify/INTEGRATION.md @@ -1,5 +1,7 @@ # Apify Integration Guide +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Status:** Production Ready โœ… **Token Savings:** 90-98% vs traditional MCP approach **Execution Time:** ~10 seconds typical diff --git a/LifeOS/install/skills/Art/SKILL.md b/LifeOS/install/skills/Art/SKILL.md index ec3328c152..89e5a90e2b 100644 --- a/LifeOS/install/skills/Art/SKILL.md +++ b/LifeOS/install/skills/Art/SKILL.md @@ -7,6 +7,8 @@ effort: medium # Art Skill +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## Customization **Before executing, check for user customizations at:** diff --git a/LifeOS/install/skills/Art/Workflows/AdHocYouTubeThumbnail.md b/LifeOS/install/skills/Art/Workflows/AdHocYouTubeThumbnail.md index 7a38dd0860..fde0f2ce88 100644 --- a/LifeOS/install/skills/Art/Workflows/AdHocYouTubeThumbnail.md +++ b/LifeOS/install/skills/Art/Workflows/AdHocYouTubeThumbnail.md @@ -1,5 +1,7 @@ # Ad-hoc YouTube Thumbnail Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Generate complete YouTube thumbnails from content input with dramatic tech backgrounds and AI-generated headshots. ## Voice Notification diff --git a/LifeOS/install/skills/Art/Workflows/Essay.md b/LifeOS/install/skills/Art/Workflows/Essay.md index 49d04e5979..005c644562 100644 --- a/LifeOS/install/skills/Art/Workflows/Essay.md +++ b/LifeOS/install/skills/Art/Workflows/Essay.md @@ -1,5 +1,7 @@ # UL Art Image Generation Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Charcoal Architectural Sketch TECHNIQUE โ€” Applied to CONTENT-RELEVANT subjects.** **Should feel like:** the image he would have art-directed himself on a good day. diff --git a/LifeOS/install/skills/Art/Workflows/Mermaid.md b/LifeOS/install/skills/Art/Workflows/Mermaid.md index a2bda0c74a..a68790214c 100644 --- a/LifeOS/install/skills/Art/Workflows/Mermaid.md +++ b/LifeOS/install/skills/Art/Workflows/Mermaid.md @@ -1,5 +1,7 @@ # Mermaid-Style Technical Diagrams with Excalidraw Aesthetic +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Hand-drawn technical diagrams combining Mermaid structure with Excalidraw sketchy aesthetic and UL color scheme.** ## Voice Notification diff --git a/LifeOS/install/skills/Art/Workflows/RemoveBackground.md b/LifeOS/install/skills/Art/Workflows/RemoveBackground.md index 3bf375095f..899b3747d1 100644 --- a/LifeOS/install/skills/Art/Workflows/RemoveBackground.md +++ b/LifeOS/install/skills/Art/Workflows/RemoveBackground.md @@ -1,5 +1,7 @@ # Remove Background Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Remove backgrounds from existing images using local rembg (no external API).** ## Voice Notification diff --git a/LifeOS/install/skills/Art/Workflows/StyleMatchedThumbnail.md b/LifeOS/install/skills/Art/Workflows/StyleMatchedThumbnail.md index 6790c510b3..e02d381fee 100644 --- a/LifeOS/install/skills/Art/Workflows/StyleMatchedThumbnail.md +++ b/LifeOS/install/skills/Art/Workflows/StyleMatchedThumbnail.md @@ -1,5 +1,7 @@ # StyleMatchedThumbnail Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Build a pro YouTube thumbnail that matches a reference's text style, places a **real** expression-matched photo of the creator on one third, sits it over a topic background, and looks hand-made โ€” not like AI slop. **Why this exists:** the legacy `AdHocYouTubeThumbnail` path generated a *fresh fake face* every run and composited text with broken geometry (trim-then-guess offsets, border-then-squash). On a channel whose audience knows the creator's real face, a synthesized face is the loudest slop tell. This workflow flips two things: the face is a **real photo selected by expression**, and the text is composited with a **deterministic, measured** engine (`ThumbnailText.ts`). The background is the only generated element, and it's generated to *blend dark* so the keyed real photo sits on it cleanly. diff --git a/LifeOS/install/skills/Art/Workflows/Visualize.md b/LifeOS/install/skills/Art/Workflows/Visualize.md index e0a1de445b..737fb4f2d2 100644 --- a/LifeOS/install/skills/Art/Workflows/Visualize.md +++ b/LifeOS/install/skills/Art/Workflows/Visualize.md @@ -1,5 +1,7 @@ # Adaptive Content Visualization Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Intelligent multi-modal visualization combining optimal approaches based on content analysis.** ## Voice Notification diff --git a/LifeOS/install/skills/AudioEditor/Tools/Analyze.help.md b/LifeOS/install/skills/AudioEditor/Tools/Analyze.help.md index d809f184dc..0f2784012b 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Analyze.help.md +++ b/LifeOS/install/skills/AudioEditor/Tools/Analyze.help.md @@ -1,5 +1,7 @@ # Analyze.ts +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + LLM-powered edit classification using Claude. Reads a word-level transcript and classifies segments for cutting. ## Usage diff --git a/LifeOS/install/skills/AudioEditor/Tools/Edit.help.md b/LifeOS/install/skills/AudioEditor/Tools/Edit.help.md index 6de8e43d49..3918c2ad6b 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Edit.help.md +++ b/LifeOS/install/skills/AudioEditor/Tools/Edit.help.md @@ -1,5 +1,7 @@ # Edit.ts +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Execute audio edits with ffmpeg. Reads an edit decision list and applies cuts with crossfades. ## Usage diff --git a/LifeOS/install/skills/AudioEditor/Tools/Pipeline.help.md b/LifeOS/install/skills/AudioEditor/Tools/Pipeline.help.md index 5ff0a0fd41..9d5fda1548 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Pipeline.help.md +++ b/LifeOS/install/skills/AudioEditor/Tools/Pipeline.help.md @@ -1,5 +1,7 @@ # Pipeline.ts +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + End-to-end audio editing pipeline that chains all tools: Transcribe -> Analyze -> Edit -> (optional) Polish. ## Usage diff --git a/LifeOS/install/skills/AudioEditor/Tools/Polish.help.md b/LifeOS/install/skills/AudioEditor/Tools/Polish.help.md index e879a5801a..f3ee2162cf 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Polish.help.md +++ b/LifeOS/install/skills/AudioEditor/Tools/Polish.help.md @@ -1,5 +1,7 @@ # Polish.ts +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Cleanvoice API cloud polish for final audio cleanup. ## Usage diff --git a/LifeOS/install/skills/AudioEditor/Tools/Transcribe.help.md b/LifeOS/install/skills/AudioEditor/Tools/Transcribe.help.md index 43a77a3a4a..9cb4c72dbb 100644 --- a/LifeOS/install/skills/AudioEditor/Tools/Transcribe.help.md +++ b/LifeOS/install/skills/AudioEditor/Tools/Transcribe.help.md @@ -1,5 +1,7 @@ # Transcribe.ts +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Word-level transcription via Whisper. Uses insanely-fast-whisper (MPS accelerated) with fallback to standard whisper CLI. ## Usage diff --git a/LifeOS/install/skills/AudioEditor/Workflows/Clean.md b/LifeOS/install/skills/AudioEditor/Workflows/Clean.md index 611fd88711..616447da94 100644 --- a/LifeOS/install/skills/AudioEditor/Workflows/Clean.md +++ b/LifeOS/install/skills/AudioEditor/Workflows/Clean.md @@ -1,5 +1,7 @@ # Clean Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Clean, edit, and polish audio files by removing filler words, stutters, false starts, dead air, and edit markers. ## Voice Notification diff --git a/LifeOS/install/skills/CMUX/DESIGN.md b/LifeOS/install/skills/CMUX/DESIGN.md index b95ccda4dd..5e924cc510 100644 --- a/LifeOS/install/skills/CMUX/DESIGN.md +++ b/LifeOS/install/skills/CMUX/DESIGN.md @@ -1,5 +1,7 @@ # CMUX โ€” migration + integration design +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## Verdict Switching to cmux gets us one thing Kitty never gave us: **programmatic control of the terminal itself** โ€” send text into any pane, read the screen back, open and close surfaces, all over a socket. That turns a wall of terminals into an agent cockpit we can script. The cost is real but bounded: cmux is Mac-only, young, and has no event stream, so every "notification" is a poll loop we own. The recommended path is **replace the terminal layer only** โ€” swap Kitty tab-state for cmux surface-state โ€” and leave Pulse, voice, the Algorithm, and memory exactly where they are. Do it in phases, keep the Kitty hooks live until the cmux path is proven, and never rip out a working hook on a hunch. diff --git a/LifeOS/install/skills/CMUX/SKILL.md b/LifeOS/install/skills/CMUX/SKILL.md index 3f3b059dda..d9b7a41efd 100644 --- a/LifeOS/install/skills/CMUX/SKILL.md +++ b/LifeOS/install/skills/CMUX/SKILL.md @@ -6,6 +6,8 @@ description: "Drives cmux as an agent cockpit to boot, race, and monitor visible # CMUX +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Make cmux the cockpit for every agent โ€” LifeOS's own and your hands-on coding teams. One command boots a named, color-identified workspace of agents you can *see, prompt, and steer*, because an agent you can't see is an agent you can't improve. {{DA_NAME}} drives them through cmux's real send/read/open-close loop; a poll-based monitor speaks up when they finish. Everything routes through one wrapper: `bun ~/.claude/skills/CMUX/Tools/cmux.ts `. It auto-launches the cmux app โ€” but cmux's socket is **default-deny**, so driving it needs auth (see the first Gotcha). diff --git a/LifeOS/install/skills/CMUX/Workflows/AgentRace.md b/LifeOS/install/skills/CMUX/Workflows/AgentRace.md index 542e0af116..62afc9d3d7 100644 --- a/LifeOS/install/skills/CMUX/Workflows/AgentRace.md +++ b/LifeOS/install/skills/CMUX/Workflows/AgentRace.md @@ -1,5 +1,7 @@ # AgentRace +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Race N agents at the same problem in one workspace, first to solve wins, close the rest. The needle-in-haystack hotfix pattern: when you don't know which approach lands, launch several and keep the winner. ## When diff --git a/LifeOS/install/skills/CMUX/Workflows/BootTeam.md b/LifeOS/install/skills/CMUX/Workflows/BootTeam.md index 0e59bad39d..3eaae4ba05 100644 --- a/LifeOS/install/skills/CMUX/Workflows/BootTeam.md +++ b/LifeOS/install/skills/CMUX/Workflows/BootTeam.md @@ -1,5 +1,7 @@ # BootTeam +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Boot a 3-tier orchestrator โ†’ lead โ†’ worker agent team in one fresh cmux workspace, then drive it with send/read. ## When diff --git a/LifeOS/install/skills/CMUX/Workflows/Fleet.md b/LifeOS/install/skills/CMUX/Workflows/Fleet.md index ee100e69fc..1772745546 100644 --- a/LifeOS/install/skills/CMUX/Workflows/Fleet.md +++ b/LifeOS/install/skills/CMUX/Workflows/Fleet.md @@ -1,5 +1,7 @@ # Fleet +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Stand up named agent fleets โ€” a local 2x2 (or larger) grid in one workspace, or a remote mini-fleet of SSH panes across your configured hosts. Each fleet is a reusable boot recipe. ## When diff --git a/LifeOS/install/skills/CMUX/Workflows/Monitor.md b/LifeOS/install/skills/CMUX/Workflows/Monitor.md index 83708a8694..1e8639110d 100644 --- a/LifeOS/install/skills/CMUX/Workflows/Monitor.md +++ b/LifeOS/install/skills/CMUX/Workflows/Monitor.md @@ -1,5 +1,7 @@ # Monitor +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Poll a workspace's surfaces, classify each agent's state (idle / working / done / awaiting-input), and fire {{DA_NAME}} voice the moment one finishes or needs you. The observe-to-improve loop. ## Why diff --git a/LifeOS/install/skills/CreateSkill/Workflows/ValidateSkill.md b/LifeOS/install/skills/CreateSkill/Workflows/ValidateSkill.md index 653fbb0b06..f4cd10dddb 100755 --- a/LifeOS/install/skills/CreateSkill/Workflows/ValidateSkill.md +++ b/LifeOS/install/skills/CreateSkill/Workflows/ValidateSkill.md @@ -1,5 +1,7 @@ # ValidateSkill Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Purpose:** Check if an existing skill follows the canonical structure with proper TitleCase naming. ## Voice Notification diff --git a/LifeOS/install/skills/Fabric/Workflows/ExecutePattern.md b/LifeOS/install/skills/Fabric/Workflows/ExecutePattern.md index b6eba5405c..8c8e4e31d4 100644 --- a/LifeOS/install/skills/Fabric/Workflows/ExecutePattern.md +++ b/LifeOS/install/skills/Fabric/Workflows/ExecutePattern.md @@ -1,5 +1,7 @@ # ExecutePattern Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Execute Fabric patterns natively without spawning the fabric CLI. Patterns are applied directly from local storage for faster, more integrated execution. --- diff --git a/LifeOS/install/skills/HTML/SKILL.md b/LifeOS/install/skills/HTML/SKILL.md index aa7ae99a35..b624e6786a 100644 --- a/LifeOS/install/skills/HTML/SKILL.md +++ b/LifeOS/install/skills/HTML/SKILL.md @@ -6,6 +6,8 @@ description: Renders the current session's output (analysis, research, red team, # HTML +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Turns whatever the session just produced into one self-contained, designed HTML file and publishes it as an Artifact. Deterministic split: the model's only job is distilling the session output into a typed content JSON and picking a design register; `Tools/Render.ts` owns every layout, typography, and color decision. ## Voice Notification diff --git a/LifeOS/install/skills/HTML/Workflows/Render.md b/LifeOS/install/skills/HTML/Workflows/Render.md index cc47a0b054..c7b79ea471 100644 --- a/LifeOS/install/skills/HTML/Workflows/Render.md +++ b/LifeOS/install/skills/HTML/Workflows/Render.md @@ -1,5 +1,7 @@ # Render Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Turn the current session's output into a published, verified HTML artifact. ## Voice Notification diff --git a/LifeOS/install/skills/Harvest/SKILL.md b/LifeOS/install/skills/Harvest/SKILL.md index 537dd5c839..719118f16e 100644 --- a/LifeOS/install/skills/Harvest/SKILL.md +++ b/LifeOS/install/skills/Harvest/SKILL.md @@ -7,6 +7,8 @@ license: public # Harvest +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Take one piece of content and ask a single question: **is there anything in here worth pulling into LifeOS?** Read it, hold every candidate idea against what the system already is and already has, and report what is worth adopting, where it maps, and how we would use it. System adoption is report-only โ€” changing the system is a separate, approved step. Then, always as the final step, ingest the content into the Knowledge Archive via the `_HARVEST` pipeline so the source itself is preserved. ## Workflow Routing diff --git a/LifeOS/install/skills/Harvest/Workflows/Harvest.md b/LifeOS/install/skills/Harvest/Workflows/Harvest.md index 98a3f0734e..eb21f9882c 100644 --- a/LifeOS/install/skills/Harvest/Workflows/Harvest.md +++ b/LifeOS/install/skills/Harvest/Workflows/Harvest.md @@ -1,5 +1,7 @@ # Harvest Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Goal:** Turn one piece of content into a ranked, honest list of what LifeOS should take from it, then preserve the source in the Knowledge Archive. System adoption is report-only; KB ingestion always happens. ## Step 1 โ€” Detect input type diff --git a/LifeOS/install/skills/Interview/SKILL.md b/LifeOS/install/skills/Interview/SKILL.md index 424680e958..a63b06f143 100644 --- a/LifeOS/install/skills/Interview/SKILL.md +++ b/LifeOS/install/skills/Interview/SKILL.md @@ -7,6 +7,8 @@ disable-model-invocation: true # Interview โ€” constitutional-context peer conversation +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## What It Does Interview reads your constitutional files โ€” TELOS, identity, projects, system prompt, architecture โ€” checks their freshness, surfaces the stalest items, and drives a contextual peer conversation to refresh them. On a populated system it runs a context check-in; on a fresh install it falls back to first-time setup. diff --git a/LifeOS/install/skills/Interview/Workflows/ContextCheckin.md b/LifeOS/install/skills/Interview/Workflows/ContextCheckin.md index 018689152b..1a3a564ea9 100644 --- a/LifeOS/install/skills/Interview/Workflows/ContextCheckin.md +++ b/LifeOS/install/skills/Interview/Workflows/ContextCheckin.md @@ -1,5 +1,7 @@ # ContextCheckin โ€” full constitutional-context peer conversation +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Purpose:** Open `/interview` by reading the freshness signal across every constitutional context file (TELOS plus the six other files that load at session start), surface the most-stale items as one of the most important things to look at, and drive a contextual peer conversation grounded in what's actually written. This is the default workflow on a populated system. For fresh installs (DA name still "LifeOS", placeholder identity, sample-row PROJECTS), route to **Phase0Setup** instead. diff --git a/LifeOS/install/skills/Interview/Workflows/Phase0Setup.md b/LifeOS/install/skills/Interview/Workflows/Phase0Setup.md index 9597c2d540..1672d28f0e 100644 --- a/LifeOS/install/skills/Interview/Workflows/Phase0Setup.md +++ b/LifeOS/install/skills/Interview/Workflows/Phase0Setup.md @@ -1,5 +1,7 @@ # Phase0Setup โ€” first-run install bootstrap +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Purpose:** On a fresh LifeOS install (or one where the bootstrap was never finished), walk the six setup targets that make Pulse work end-to-end: DA identity, Principal identity, voice IDs, credentials, first project, work repo. Once Phase 0 is done on a system, this workflow auto-skips and `/interview` routes to **TelosCheckin** instead. A fresh LifeOS install ships with placeholder identity ("LifeOS" / "User"), generic voice IDs, an empty `.env`, a sample-row PROJECTS table, and a templated `WORK.REPO` that points nowhere. Until Phase 0 runs, Pulse boots with the wrong DA name, voice notifications use the default Rachel voice, the Assistant module's diary writes to a non-existent DA directory, and the work pipeline crashes on the missing repo. diff --git a/LifeOS/install/skills/Knowledge/SKILL.md b/LifeOS/install/skills/Knowledge/SKILL.md index 601a304f98..a219bdf710 100644 --- a/LifeOS/install/skills/Knowledge/SKILL.md +++ b/LifeOS/install/skills/Knowledge/SKILL.md @@ -9,6 +9,8 @@ context: fork # Knowledge Skill +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## What It Does Manage the LifeOS Knowledge Archive โ€” a curated, typed graph of notes across four entity domains: People, Companies, Ideas, and Research. Operations cover search, add, harvest, develop, ingest, contradiction-finding, graph traversal, compressed retrieval, and mining recent conversations for memory candidates. Every note ships with typed `related:` cross-links, so the archive is a connected graph, not a pile of files. diff --git a/LifeOS/install/skills/Migrate/SKILL.md b/LifeOS/install/skills/Migrate/SKILL.md index 3555611104..fe85e9161c 100644 --- a/LifeOS/install/skills/Migrate/SKILL.md +++ b/LifeOS/install/skills/Migrate/SKILL.md @@ -7,6 +7,8 @@ disable-model-invocation: true # Migrate โ€” external-content intake and classification +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## ๐Ÿšจ MANDATORY: Voice Notification ```bash diff --git a/LifeOS/install/skills/Prompting/Standards.md b/LifeOS/install/skills/Prompting/Standards.md index e606f728e4..60536cdc6d 100755 --- a/LifeOS/install/skills/Prompting/Standards.md +++ b/LifeOS/install/skills/Prompting/Standards.md @@ -6,6 +6,8 @@ description: Prompt engineering standards based on Anthropic's Claude 4.x best p # Prompt Engineering Standards +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Foundation:** Based on Anthropic's Claude 4.x Best Practices (November 2025), context engineering principles, and the Fabric system. Validated by empirical research across 1,500+ academic papers and production systems. **Philosophy:** Universal principles of semantic clarity and structure that work regardless of model implementation, with specific optimizations for Claude 4.x behavioral patterns. diff --git a/LifeOS/install/skills/Remotion/Workflows/GeneratedContentVideo.md b/LifeOS/install/skills/Remotion/Workflows/GeneratedContentVideo.md index cf08d18589..443c441bf1 100644 --- a/LifeOS/install/skills/Remotion/Workflows/GeneratedContentVideo.md +++ b/LifeOS/install/skills/Remotion/Workflows/GeneratedContentVideo.md @@ -1,5 +1,7 @@ # GeneratedContentVideo Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + End-to-end AI video generation: topic or script โ†’ scene images + narration + captions โ†’ composed and rendered MP4. ## Triggers diff --git a/LifeOS/install/skills/Telos/Workflows/Update.md b/LifeOS/install/skills/Telos/Workflows/Update.md index 65629d1152..a37900633a 100755 --- a/LifeOS/install/skills/Telos/Workflows/Update.md +++ b/LifeOS/install/skills/Telos/Workflows/Update.md @@ -5,6 +5,8 @@ allowed-tools: Bash(bun:*) # IDENTITY +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + You are {DA_IDENTITY.NAME}, {PRINCIPAL.NAME}'s personal AI assistant, helping him maintain his TELOS life framework. TELOS (Telic Evolution and Life Operating System) is his comprehensive life context system that captures his beliefs, goals, lessons, wisdom, and personal philosophy. When {PRINCIPAL.NAME} wants to update TELOS, you guide him through the process conversationally, ensuring proper documentation and backup of these critical life context files. diff --git a/LifeOS/install/skills/USMetrics/Workflows/UpdateData.md b/LifeOS/install/skills/USMetrics/Workflows/UpdateData.md index 8d7291cd25..e5e201a900 100755 --- a/LifeOS/install/skills/USMetrics/Workflows/UpdateData.md +++ b/LifeOS/install/skills/USMetrics/Workflows/UpdateData.md @@ -1,5 +1,7 @@ # UpdateData Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + **Skill:** USMetrics **Purpose:** Fetch current data from all sources and update the Substrate US-Common-Metrics dataset diff --git a/LifeOS/install/skills/Upgrade/Workflows/Upgrade.md b/LifeOS/install/skills/Upgrade/Workflows/Upgrade.md index 4d351c4f2e..3e814d4f76 100644 --- a/LifeOS/install/skills/Upgrade/Workflows/Upgrade.md +++ b/LifeOS/install/skills/Upgrade/Workflows/Upgrade.md @@ -1,5 +1,7 @@ # Upgrade Workflow +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + ## Voice Notification ```bash diff --git a/LifeOS/install/skills/Webdesign/References/ExportFormats.md b/LifeOS/install/skills/Webdesign/References/ExportFormats.md index 94cfedcce5..9227f62cab 100644 --- a/LifeOS/install/skills/Webdesign/References/ExportFormats.md +++ b/LifeOS/install/skills/Webdesign/References/ExportFormats.md @@ -1,5 +1,7 @@ # Export Formats +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Decision matrix for choosing the right export from Claude Design. ## Format Quick Reference diff --git a/LifeOS/install/skills/Webdesign/References/HandoffBundleSpec.md b/LifeOS/install/skills/Webdesign/References/HandoffBundleSpec.md index c9876d48c7..a71c59b3d4 100644 --- a/LifeOS/install/skills/Webdesign/References/HandoffBundleSpec.md +++ b/LifeOS/install/skills/Webdesign/References/HandoffBundleSpec.md @@ -1,5 +1,7 @@ # Handoff Bundle Specification +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Reference for the structure and semantics of a Claude Design โ†’ Claude Code handoff bundle. ## Bundle Layout diff --git a/LifeOS/install/skills/Webdesign/Workflows/CreatePrototype.md b/LifeOS/install/skills/Webdesign/Workflows/CreatePrototype.md index 1af22d23be..6aee08a58d 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/CreatePrototype.md +++ b/LifeOS/install/skills/Webdesign/Workflows/CreatePrototype.md @@ -1,5 +1,7 @@ # CreatePrototype +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Brief โ†’ polished prototype via Claude Design. ## Trigger Phrases diff --git a/LifeOS/install/skills/Webdesign/Workflows/DeployDesign.md b/LifeOS/install/skills/Webdesign/Workflows/DeployDesign.md index 882b379229..1c7be2f47c 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/DeployDesign.md +++ b/LifeOS/install/skills/Webdesign/Workflows/DeployDesign.md @@ -1,5 +1,7 @@ # DeployDesign +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Built design โ†’ production host. ## Trigger Phrases diff --git a/LifeOS/install/skills/Webdesign/Workflows/ExportToCode.md b/LifeOS/install/skills/Webdesign/Workflows/ExportToCode.md index 632593f39f..d81f84289e 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/ExportToCode.md +++ b/LifeOS/install/skills/Webdesign/Workflows/ExportToCode.md @@ -1,5 +1,7 @@ # ExportToCode +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > **Prefer Path 2 first.** If `/design-sync` is available, use `Workflows/NativeDesignSync.md` to move the design into code โ€” it's the first-party, deterministic path. This bundle-export workflow (Path 3) is the fallback for when you're working from the web canvas and is unproven (the `interceptor-test` profile isn't logged into claude.ai). See SKILL.md โ†’ Prerequisites. Claude Design handoff bundle โ†’ production code via the `frontend-design` plugin. diff --git a/LifeOS/install/skills/Webdesign/Workflows/ExtractDesignSystem.md b/LifeOS/install/skills/Webdesign/Workflows/ExtractDesignSystem.md index 64e35bcabb..772fdf00da 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/ExtractDesignSystem.md +++ b/LifeOS/install/skills/Webdesign/Workflows/ExtractDesignSystem.md @@ -1,5 +1,7 @@ # ExtractDesignSystem +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > **Prefer Path 2 first.** `/design-sync` (pull direction) is the native, first-party way to import a codebase's real design system into Claude Design โ€” use `Workflows/NativeDesignSync.md` when the command is available. This Interceptor-driven extraction (Path 3) is the fallback for working from the web canvas and is unproven (the `interceptor-test` profile isn't logged into claude.ai). See SKILL.md โ†’ Prerequisites. Codebase / brand assets โ†’ design tokens Claude Design uses on every subsequent generation. diff --git a/LifeOS/install/skills/Webdesign/Workflows/IntegrateIntoApp.md b/LifeOS/install/skills/Webdesign/Workflows/IntegrateIntoApp.md index 12fb3bcd26..d3403a0b80 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/IntegrateIntoApp.md +++ b/LifeOS/install/skills/Webdesign/Workflows/IntegrateIntoApp.md @@ -1,5 +1,7 @@ # IntegrateIntoApp +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + > **Prefer Path 2 first.** If `/design-sync` is available, use `Workflows/NativeDesignSync.md` to keep design and code in lockstep natively. This Interceptor-bundle integration flow (Path 3) is the fallback for working from the web canvas and is unproven (the `interceptor-test` profile isn't logged into claude.ai). See SKILL.md โ†’ Prerequisites. (If your prototype source is already a generated code directory, the audit/diff/verify steps below still apply regardless of how it was produced.) Land a Claude Design prototype INTO an existing application as a framework-aware diff, not a greenfield scaffold. diff --git a/LifeOS/install/skills/Webdesign/Workflows/RefinePrototype.md b/LifeOS/install/skills/Webdesign/Workflows/RefinePrototype.md index 883add8905..7bf2b83b61 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/RefinePrototype.md +++ b/LifeOS/install/skills/Webdesign/Workflows/RefinePrototype.md @@ -1,5 +1,7 @@ # RefinePrototype +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Iterate on an existing Claude Design prototype via inline comments, direct text edits, and adjustment knobs. ## Trigger Phrases diff --git a/LifeOS/install/skills/Webdesign/Workflows/WebsiteToRedesign.md b/LifeOS/install/skills/Webdesign/Workflows/WebsiteToRedesign.md index 816fffd5a7..2660661c56 100644 --- a/LifeOS/install/skills/Webdesign/Workflows/WebsiteToRedesign.md +++ b/LifeOS/install/skills/Webdesign/Workflows/WebsiteToRedesign.md @@ -1,5 +1,7 @@ # WebsiteToRedesign +> **Path note:** Any `~/.claude/...` path in this doc's examples is illustrative, not a fact about your install. This system may be installed project-scoped (`CLAUDE_CONFIG_DIR`/`LIFEOS_DIR` pointed at a project folder) rather than at the literal global path shown โ€” resolve the actual root (check those env vars, or `CLAUDE.md`) before running any command literally. + Live URL โ†’ capture โ†’ critique โ†’ modernized rebuild via Claude Design. ## Trigger Phrases From 4d36e18dde210aeacff5d500534745d8116e05d8 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 17:06:05 +0530 Subject: [PATCH 10/12] Fix plist template + PULSE.toml prompt: last two config-file gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. All 10 InstallXXX.ts scripts (BookmarkSweep, CommitmentSweep, WorkSweep, DerivedSync, BlogDiscovery, CodexUpdate, Conveyor Runner/Watcher, UsageAggregator, HealthSync) substitute {{HOME}}/__HOME__ placeholders in their .plist.template into a real launchd job โ€” but always with the literal real $HOME, never LIFEOS_DIR/CLAUDE_CONFIG_DIR. Each template hardcodes "{{HOME}}/.claude/LIFEOS/..." as a compound unit, so a project- scoped install's background services (these are optional launchd jobs) would always target the global path regardless of --config-root. Fixed with a two-pass substitution: first replace the compound "{{HOME}}/.claude/LIFEOS" (or "__HOME__/.claude/LIFEOS") prefix with the resolved LIFEOS_DIR, THEN do the normal bare {{HOME}}/__HOME__ replace for whatever's left (PATH entries, the plist's own HOME env key โ€” those are legitimately meant to be the real home, not the project dir). Verified the default (LIFEOS_DIR unset) case produces byte-identical output to before. 2. PULSE.toml's "memory-consolidation" cron job (daily 3am, type="claude") has a `prompt` field fed directly to an AI, literally instructing it to run `bun run ~/.claude/LIFEOS/TOOLS/SessionHarvester.ts` โ€” the same doc- instruction bug class already fixed in CLAUDE.md and 56 markdown docs, just living in a .toml config instead. Reworded the prompt to resolve LIFEOS_DIR/CLAUDE_CONFIG_DIR first instead of assuming the global path. Checked settings.enhancements.json too โ€” its two ~/.claude mentions are cosmetic UI tooltip text, not functional paths, confirmed no fix needed. --- LifeOS/install/LIFEOS/PULSE/PULSE.toml | 2 +- LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts | 4 +++- LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts | 1 + LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts | 1 + 11 files changed, 13 insertions(+), 2 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/PULSE.toml b/LifeOS/install/LIFEOS/PULSE/PULSE.toml index b968b97375..f739d25677 100644 --- a/LifeOS/install/LIFEOS/PULSE/PULSE.toml +++ b/LifeOS/install/LIFEOS/PULSE/PULSE.toml @@ -139,7 +139,7 @@ enabled = true name = "memory-consolidation" schedule = "0 3 * * *" type = "claude" -prompt = "Run learning consolidation: execute `bun run ~/.claude/LIFEOS/TOOLS/SessionHarvester.ts --recent 20` then `bun run ~/.claude/LIFEOS/TOOLS/LearningPatternSynthesis.ts --week`. Summarize what was learned and any patterns found." +prompt = "Run learning consolidation. First resolve the real LIFEOS root (check $LIFEOS_DIR / $CLAUDE_CONFIG_DIR env vars, defaulting to ~/.claude/LIFEOS only if unset โ€” this may be a project-scoped install) then execute `bun run /TOOLS/SessionHarvester.ts --recent 20` then `bun run /TOOLS/LearningPatternSynthesis.ts --week` against that resolved root. Summarize what was learned and any patterns found." model = "sonnet" output = "log" enabled = true diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts b/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts index a222b6fbc3..74e18ed9cb 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallBlogDiscovery.ts @@ -44,6 +44,7 @@ async function install(): Promise { const bunPath = await detectBun(); const bunDir = bunPath.replace(/\/bun$/, ""); console.log(`[InstallBlogDiscovery] detected bun at ${bunPath}`); const materialized = readFileSync(TEMPLATE_PATH, "utf-8") + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME).replace(/\{\{BUN\}\}/g, bunPath).replace(/\{\{BUN_DIR\}\}/g, bunDir); if (!existsSync(LAUNCH_AGENTS_DIR)) mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true }); const u = await uid(); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts index 39e0b726e9..fd3bfb7df8 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallBookmarkSweep.ts @@ -59,6 +59,7 @@ async function install(): Promise { console.log(`[InstallBookmarkSweep] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts b/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts index 894ee5f68b..203f0ab01d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallCodexUpdate.ts @@ -57,6 +57,7 @@ async function install(): Promise { console.log(`[InstallCodexUpdate] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts index 4cdafe399c..d39a748499 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallCommitmentSweep.ts @@ -50,7 +50,9 @@ function install(): void { mkdirSync(STATE_DIR, { recursive: true }); const raw = readFileSync(TEMPLATE, "utf8"); - const materialized = raw.replaceAll("__HOME__", HOME); + const materialized = raw + .replaceAll("__HOME__/.claude/LIFEOS", process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) + .replaceAll("__HOME__", HOME); writeFileSync(TARGET, materialized, { mode: 0o644 }); console.log(`[InstallCommitmentSweep] wrote ${TARGET}`); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts index 13281d24c1..ba08535270 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorRunner.ts @@ -59,6 +59,7 @@ async function install(): Promise { console.log(`[InstallConveyorRunner] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts index 990072aca2..b79b748cce 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallConveyorWatcher.ts @@ -59,6 +59,7 @@ async function install(): Promise { console.log(`[InstallConveyorWatcher] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts b/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts index 2114d0cdb2..37a49aab0f 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallDerivedSync.ts @@ -91,6 +91,7 @@ async function install(): Promise { console.log(`[InstallDerivedSync] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir) diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts b/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts index 807162e445..a45e998045 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallHealthSync.ts @@ -86,6 +86,7 @@ async function install(): Promise { console.log(`[InstallHealthSync] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts b/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts index 746076730b..907e5920de 100755 --- a/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallUsageAggregator.ts @@ -56,6 +56,7 @@ async function install(): Promise { console.log(`[InstallUsageAggregator] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); diff --git a/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts index ed288ef3c1..6e2b7eb539 100644 --- a/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/InstallWorkSweep.ts @@ -59,6 +59,7 @@ async function install(): Promise { console.log(`[InstallWorkSweep] detected bun at ${bunPath}`); const template = readFileSync(TEMPLATE_PATH, "utf-8"); const materialized = template + .replace(/\{\{HOME\}\}\/\.claude\/LIFEOS/g, process.env.LIFEOS_DIR || `${HOME}/.claude/LIFEOS`) .replace(/\{\{HOME\}\}/g, HOME) .replace(/\{\{BUN\}\}/g, bunPath) .replace(/\{\{BUN_DIR\}\}/g, bunDir); From fadd7aa7c49f23c91f84b93f3fb5cf776b3d1667 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 18:16:20 +0530 Subject: [PATCH 11/12] Fix wave 3: audit tooling itself had a false-negative bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found live: pulse.ts failed with "Cannot find package 'smol-toml'" style errors that traced back to it hardcoding LIFEOS_DIR โ€” a file our prior sweeps should have caught but didn't. Root cause: the classifier used to spot these bugs checked whether the string "LIFEOS_DIR" (or similar) appeared nearby a hardcoded line as proof of being "guarded" โ€” but a line like `const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS")` trivially contains that substring because it's THE NAME OF THE VARIABLE BEING DEFINED, not an actual env-var check. Any file with a local const named LIFEOS_DIR/CLAUDE_ROOT/etc. assigned a hardcoded value slipped through every previous sweep undetected. Rewrote the check to require an actual `process.env.X` read (or equivalent bash `${X:-...}`) to count as guarded. Re-ran across the full tree and found 22 more files with genuine unguarded hardcodes โ€” several in files we'd already partially fixed, where a SECOND, separate definition in the same file was missed the first time (e.g. GetCounts.ts, CostTracker.ts, setup.ts, observability.ts, tab-freshness.ts, telegram.ts, ContextSearch.ts all had this). Two more (GrokAudit.ts:112, telegram.ts:490/517) weren't even in the classifier's output โ€” found by having agents read whole files instead of trusting single flagged lines. Also includes pulse.ts itself (fixed live during triage, synced here) and its sibling pulse-unified.ts (same bug, same fix). Re-ran the corrected classifier after all fixes: converged to exactly the same known-cosmetic set (doc/error/usage text, and the handful of files whose matches are themselves pattern-matching logic, not the bug) โ€” no new genuine hits. --- LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh | 2 +- .../LIFEOS/PULSE/Observability/observability.ts | 4 ++-- .../LIFEOS/PULSE/Performance/cost-aggregator.ts | 4 ++-- LifeOS/install/LIFEOS/PULSE/Performance/module.ts | 2 +- LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts | 10 +++++----- LifeOS/install/LIFEOS/PULSE/modules/telegram.ts | 6 +++--- LifeOS/install/LIFEOS/PULSE/modules/wiki.ts | 9 +++++---- LifeOS/install/LIFEOS/PULSE/pulse-unified.ts | 5 +++-- LifeOS/install/LIFEOS/PULSE/pulse.ts | 5 +++-- LifeOS/install/LIFEOS/PULSE/setup.ts | 2 +- .../LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/CostTracker.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/CrossVendorAudit.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/DASchedule.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/GetCounts.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/GrokAudit.ts | 4 ++-- LifeOS/install/LIFEOS/TOOLS/HarvestExecutor.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/KnowledgeHarvester.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MigrateApprove.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/PangramScore.ts | 2 +- LifeOS/install/hooks/MemoryHealthGate.hook.ts | 2 +- LifeOS/install/hooks/SystemFileGuard.hook.ts | 2 +- .../skills/ContextSearch/Tools/ContextSearch.ts | 2 +- 23 files changed, 40 insertions(+), 37 deletions(-) diff --git a/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh b/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh index 274841e5ef..8fb7f3d02c 100755 --- a/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh +++ b/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh @@ -313,7 +313,7 @@ if [ "$context_pct" = "0" ] && [ "$total_input" -eq 0 ] 2>/dev/null; then done < <(jq -r '.loadAtStartup.files[]? // empty' "$SETTINGS_FILE" 2>/dev/null) # Project memory files (CC native memory at ~/.claude/projects/*/memory/) - for _f in "$HOME"/.claude/projects/*/memory/MEMORY.md; do + for _f in "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/projects/*/memory/MEMORY.md; do [ -f "$_f" ] && _est=$((_est + $(wc -c < "$_f") * 10 / 35)) done diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index fedf848567..1eef21e9fe 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -69,7 +69,7 @@ export interface ObservabilityConfig { // โ”€โ”€ Path Construction โ”€โ”€ const HOME = process.env.HOME ?? "" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const MEMORY_DIR = join(LIFEOS_DIR, "MEMORY") const WORK_JSON_PATH = join(MEMORY_DIR, "STATE", "work.json") @@ -78,7 +78,7 @@ const SUBAGENT_EVENTS_PATH = join(MEMORY_DIR, "OBSERVABILITY", "subagent-events. const VOICE_EVENTS_PATH = join(MEMORY_DIR, "VOICE", "voice-events.jsonl") const TOOL_FAILURES_PATH = join(MEMORY_DIR, "OBSERVABILITY", "tool-failures.jsonl") const TOOL_ACTIVITY_PATH = join(MEMORY_DIR, "OBSERVABILITY", "tool-activity.jsonl") -const SETTINGS_PATH = join(HOME, ".claude", "settings.json") +const SETTINGS_PATH = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "settings.json") const LADDER_DIR = join(HOME, "Projects", "Ladder") const DEFAULT_DASHBOARD_DIR = join(LIFEOS_DIR, "PULSE", "Observability", "out") diff --git a/LifeOS/install/LIFEOS/PULSE/Performance/cost-aggregator.ts b/LifeOS/install/LIFEOS/PULSE/Performance/cost-aggregator.ts index d92b8749c8..e728d7ba84 100755 --- a/LifeOS/install/LIFEOS/PULSE/Performance/cost-aggregator.ts +++ b/LifeOS/install/LIFEOS/PULSE/Performance/cost-aggregator.ts @@ -14,8 +14,8 @@ import { join, basename, dirname } from "path" import { existsSync, readFileSync, writeFileSync, appendFileSync, readdirSync, statSync, mkdirSync } from "fs" const HOME = process.env.HOME ?? "" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") -const PROJECTS_DIR = join(HOME, ".claude", "projects") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") +const PROJECTS_DIR = join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "projects") const OUTPUT_FILE = join(LIFEOS_DIR, "MEMORY", "OBSERVABILITY", "session-costs.jsonl") const STATE_FILE = join(LIFEOS_DIR, "PULSE", "Performance", "aggregator-state.json") diff --git a/LifeOS/install/LIFEOS/PULSE/Performance/module.ts b/LifeOS/install/LIFEOS/PULSE/Performance/module.ts index aa44617f3d..fc9efdfeb1 100644 --- a/LifeOS/install/LIFEOS/PULSE/Performance/module.ts +++ b/LifeOS/install/LIFEOS/PULSE/Performance/module.ts @@ -14,7 +14,7 @@ import { join } from "path" import { existsSync, readFileSync } from "fs" const HOME = process.env.HOME ?? "" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const MEMORY_DIR = join(LIFEOS_DIR, "MEMORY") const SESSION_COSTS_PATH = join(MEMORY_DIR, "OBSERVABILITY", "session-costs.jsonl") const TOOL_FAILURES_PATH = join(MEMORY_DIR, "OBSERVABILITY", "tool-failures.jsonl") diff --git a/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts b/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts index 8f7ff7ada5..a343c75cb4 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/tab-freshness.ts @@ -30,7 +30,7 @@ import { existsSync, statSync, readdirSync, readFileSync } from "fs" import { join } from "path" const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const USER_DIR = join(LIFEOS_DIR, "USER") const TELOS_DIR = join(USER_DIR, "TELOS") @@ -82,11 +82,11 @@ const REGISTRY: Record = { { name: "KNOWLEDGE/", path: join(LIFEOS_DIR, "MEMORY", "KNOWLEDGE"), expand: true }, ], hooks: [ - { name: "hooks/", path: join(HOME, ".claude", "hooks"), expand: true }, - { name: "settings.json", path: join(HOME, ".claude", "settings.json") }, + { name: "hooks/", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "hooks"), expand: true }, + { name: "settings.json", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "settings.json") }, ], skills: [ - { name: "skills/", path: join(HOME, ".claude", "skills"), expand: true }, + { name: "skills/", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills"), expand: true }, ], agents: [ { name: "agents/", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "agents"), expand: true }, @@ -105,7 +105,7 @@ const REGISTRY: Record = { ], amber: [ { name: "KNOWLEDGE/Ideas/", path: join(LIFEOS_DIR, "MEMORY", "KNOWLEDGE", "Ideas"), expand: true }, - { name: "_X/State/", path: join(HOME, ".claude", "skills", "_X", "State") }, + { name: "_X/State/", path: join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "skills", "_X", "State") }, ], assistant: [ { name: "DA_IDENTITY.md", path: join(USER_DIR, "DIGITAL_ASSISTANT", "DA_IDENTITY.md") }, diff --git a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts index cd3717e58e..01af076a75 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts @@ -80,7 +80,7 @@ const IDLE_TIMEOUT_MS = 60 * 60 * 1000 // 1 hour โ€” gap of silence tha const INFERENCE_HARD_BUDGET_MS = 10_000 // outer race cap on summarize; measured Sonnet subprocess cost is 4-6s, this gives slack without losing the voice trailing the text by too much const MIN_FALLBACK_WORDS = 6 // a fallback summary shorter than this is presumed too thin to be worth voicing const MEANINGFUL_REPLY_WORDS = 25 // when a reply is at least this long, a too-short fallback is a regression โ€” skip voice rather than ship a "0:00" stub -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") // โ”€โ”€ Bidirectional Telegram images (ported from public PR #1384, @klausagnoletti) โ”€โ”€ // @@ -487,7 +487,7 @@ async function handleProposalReply(chatId: number, reply: ProposalReply, ctx: { markProposal(row.id, { status: "accepted", resolved_at: new Date().toISOString(), applied_edit: row.edit }) logProposalEvent({ id: row.id, file: row.target_file, edit: row.edit, confidence: row.confidence, status: "accepted" }) logProposalReply({ kind: "yes", id: row.id, outcome: "applied", chatId }) - const fileLabel = row.target_file.replace(`${HOME}/.claude/`, "") + const fileLabel = row.target_file.replace(`${CWD}/`, "") await ctx.reply(`โœ… Applied to ${fileLabel}`).catch(() => {}) } else { logProposalReply({ kind: "yes", id: row.id, outcome: "apply-failed", reason: result.reason, chatId }) @@ -514,7 +514,7 @@ async function handleProposalReply(chatId: number, reply: ProposalReply, ctx: { markProposal(row.id, { status: "edited", resolved_at: new Date().toISOString(), applied_edit: reply.editText }) logProposalEvent({ id: row.id, file: row.target_file, edit: reply.editText, confidence: row.confidence, status: "edited" }) logProposalReply({ kind: "edit", id: row.id, outcome: "applied", chatId }) - const fileLabel = row.target_file.replace(`${HOME}/.claude/`, "") + const fileLabel = row.target_file.replace(`${CWD}/`, "") await ctx.reply(`โœ… Applied your edit to ${fileLabel}`).catch(() => {}) } else { logProposalReply({ kind: "edit", id: row.id, outcome: "apply-failed", reason: result.reason, chatId }) diff --git a/LifeOS/install/LIFEOS/PULSE/modules/wiki.ts b/LifeOS/install/LIFEOS/PULSE/modules/wiki.ts index fbf3ec2bd6..e3771358dc 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/wiki.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/wiki.ts @@ -35,13 +35,14 @@ import MiniSearch from "minisearch" // Path Construction const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const DOCUMENTATION_DIR = join(LIFEOS_DIR, "DOCUMENTATION") const KNOWLEDGE_DIR = join(LIFEOS_DIR, "MEMORY", "KNOWLEDGE") const ALGORITHM_DIR = join(LIFEOS_DIR, "ALGORITHM") -const SKILLS_DIR = join(HOME, ".claude", "skills") -const HOOKS_DIR = join(HOME, ".claude", "hooks") -const SETTINGS_PATH = join(HOME, ".claude", "settings.json") +const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude") +const SKILLS_DIR = join(CLAUDE_CONFIG_DIR, "skills") +const HOOKS_DIR = join(CLAUDE_CONFIG_DIR, "hooks") +const SETTINGS_PATH = join(CLAUDE_CONFIG_DIR, "settings.json") const ARBOL_WORKERS_DIR = join(LIFEOS_DIR, "USER", "CUSTOMIZATIONS", "ARBOL", "Workers") const SYSTEM_PROMPT_PATH = join(LIFEOS_DIR, "LIFEOS_SYSTEM_PROMPT.md") diff --git a/LifeOS/install/LIFEOS/PULSE/pulse-unified.ts b/LifeOS/install/LIFEOS/PULSE/pulse-unified.ts index 5c73b82f3b..52e054de95 100755 --- a/LifeOS/install/LIFEOS/PULSE/pulse-unified.ts +++ b/LifeOS/install/LIFEOS/PULSE/pulse-unified.ts @@ -21,10 +21,11 @@ import { parse } from "smol-toml" // โ”€โ”€ Load .env before anything else โ”€โ”€ const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR ?? join(HOME, ".claude") +const LIFEOS_DIR = process.env.LIFEOS_DIR ?? join(CLAUDE_CONFIG_DIR, "LIFEOS") const PULSE_DIR = join(LIFEOS_DIR, "PULSE") -const envPath = join(HOME, ".claude", ".env") +const envPath = join(CLAUDE_CONFIG_DIR, ".env") try { const envContent = readFileSync(envPath, "utf-8") for (const line of envContent.split("\n")) { diff --git a/LifeOS/install/LIFEOS/PULSE/pulse.ts b/LifeOS/install/LIFEOS/PULSE/pulse.ts index e3271174ee..a1b4dc27e1 100755 --- a/LifeOS/install/LIFEOS/PULSE/pulse.ts +++ b/LifeOS/install/LIFEOS/PULSE/pulse.ts @@ -23,10 +23,11 @@ import { isLoopbackHostHeader } from "./lib/host-guard.ts" // โ”€โ”€ Load .env before anything else โ”€โ”€ const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR ?? join(HOME, ".claude") +const LIFEOS_DIR = process.env.LIFEOS_DIR ?? join(CLAUDE_CONFIG_DIR, "LIFEOS") const PULSE_DIR = join(LIFEOS_DIR, "PULSE") -const envPath = join(HOME, ".claude", ".env") +const envPath = join(CLAUDE_CONFIG_DIR, ".env") try { const envContent = readFileSync(envPath, "utf-8") for (const line of envContent.split("\n")) { diff --git a/LifeOS/install/LIFEOS/PULSE/setup.ts b/LifeOS/install/LIFEOS/PULSE/setup.ts index d46fd65560..1c6259e6f4 100755 --- a/LifeOS/install/LIFEOS/PULSE/setup.ts +++ b/LifeOS/install/LIFEOS/PULSE/setup.ts @@ -14,7 +14,7 @@ import { join, resolve } from "path" import { existsSync, mkdirSync } from "fs" const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const PULSE_DIR = join(LIFEOS_DIR, "PULSE") // โ”€โ”€ Helpers โ”€โ”€ diff --git a/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts b/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts index 7a32c460a0..b95c4e338d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts +++ b/LifeOS/install/LIFEOS/TOOLS/ArchitectureSummaryGenerator.ts @@ -39,7 +39,7 @@ const ARCH_SOURCE = path.join(LIFEOS_DIR, "DOCUMENTATION", "LifeosSystemArchitec const SUMMARY_OUTPUT = path.join(LIFEOS_DIR, "DOCUMENTATION", "ARCHITECTURE_SUMMARY.md"); const ALGORITHM_DIR = path.join(LIFEOS_DIR, "ALGORITHM"); const MEMORY_SYSTEM_DOC = path.join(LIFEOS_DIR, "DOCUMENTATION", "Memory", "MemorySystem.md"); -const CLAUDE_MD = path.join(HOME, ".claude", "CLAUDE.md"); +const CLAUDE_MD = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(HOME, ".claude"), "CLAUDE.md"); // ============================================================================ // Version detection (source-of-truth lookups โ€” no hardcoded versions) diff --git a/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts b/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts index 8d08b663fe..b51042151f 100755 --- a/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts +++ b/LifeOS/install/LIFEOS/TOOLS/CostTracker.ts @@ -33,7 +33,7 @@ import { join } from "path"; import { execSync } from "child_process"; const HOME = process.env.HOME ?? ""; -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS"); +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"); const OBS_DIR = join(LIFEOS_DIR, "MEMORY", "OBSERVABILITY"); const LEDGER_PATH = join(OBS_DIR, "anthropic-cost.jsonl"); const CALL_SITES_PATH = join(OBS_DIR, "anthropic-call-sites.json"); diff --git a/LifeOS/install/LIFEOS/TOOLS/CrossVendorAudit.ts b/LifeOS/install/LIFEOS/TOOLS/CrossVendorAudit.ts index 1baee73a00..5bd50ddedb 100755 --- a/LifeOS/install/LIFEOS/TOOLS/CrossVendorAudit.ts +++ b/LifeOS/install/LIFEOS/TOOLS/CrossVendorAudit.ts @@ -22,7 +22,7 @@ import { homedir } from "node:os"; import { join, resolve } from "node:path"; const HOME = homedir(); -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS"); +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"); const WORK_DIR = join(LIFEOS_DIR, "MEMORY", "WORK"); const FINDINGS_LOG = join(LIFEOS_DIR, "MEMORY", "VERIFICATION", "cato-findings.jsonl"); const TOOL_ACTIVITY_LOG = join(LIFEOS_DIR, "MEMORY", "OBSERVABILITY", "tool-activity.jsonl"); diff --git a/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts b/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts index 57d511269e..2136538e54 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts @@ -15,7 +15,7 @@ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } fr import { getDAName } from "../../hooks/lib/identity" const HOME = process.env.HOME ?? "~" -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS") const TASKS_DIR = join(LIFEOS_DIR, "PULSE", "state", "da") const TASKS_PATH = join(TASKS_DIR, "scheduled-tasks.jsonl") diff --git a/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts b/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts index d4d941e41d..1ad67ba0fa 100755 --- a/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts +++ b/LifeOS/install/LIFEOS/TOOLS/GetCounts.ts @@ -51,7 +51,7 @@ for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { const HOME = process.env.HOME!; -const CLAUDE_DIR = join(HOME, ".claude"); +const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); // skills/, hooks/, settings.json live under CLAUDE_DIR. // MEMORY/, USER/ live under LIFEOS_DIR (which is CLAUDE_DIR/PAI). const LIFEOS_DIR = process.env.LIFEOS_DIR || join(CLAUDE_DIR, "LIFEOS"); diff --git a/LifeOS/install/LIFEOS/TOOLS/GrokAudit.ts b/LifeOS/install/LIFEOS/TOOLS/GrokAudit.ts index 73ff62f61e..4d945748e7 100755 --- a/LifeOS/install/LIFEOS/TOOLS/GrokAudit.ts +++ b/LifeOS/install/LIFEOS/TOOLS/GrokAudit.ts @@ -38,7 +38,7 @@ import { homedir } from "node:os"; import { join, resolve } from "node:path"; const HOME = homedir(); -const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS"); +const LIFEOS_DIR = process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"); const WORK_DIR = join(LIFEOS_DIR, "MEMORY", "WORK"); const FINDINGS_LOG = join(LIFEOS_DIR, "MEMORY", "VERIFICATION", "grok-findings.jsonl"); const API_URL = "https://api.x.ai/v1/chat/completions"; @@ -109,7 +109,7 @@ interface AuditResponse { function loadEnv(): Record { const envPath = process.env.LIFEOS_CONFIG_DIR ? join(process.env.LIFEOS_CONFIG_DIR, ".env") - : join(HOME, ".claude", ".env"); + : join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), ".env"); const env: Record = {}; try { for (const line of readFileSync(envPath, "utf-8").split("\n")) { diff --git a/LifeOS/install/LIFEOS/TOOLS/HarvestExecutor.ts b/LifeOS/install/LIFEOS/TOOLS/HarvestExecutor.ts index ec663cc199..408d0b79b7 100755 --- a/LifeOS/install/LIFEOS/TOOLS/HarvestExecutor.ts +++ b/LifeOS/install/LIFEOS/TOOLS/HarvestExecutor.ts @@ -14,7 +14,7 @@ import * as path from "path"; import { inference } from "./Inference"; const HOME = process.env.HOME!; -const LIFEOS_DIR = path.join(HOME, ".claude", "LIFEOS"); +const LIFEOS_DIR = process.env.LIFEOS_DIR || path.join(HOME, ".claude", "LIFEOS"); const MEMORY_DIR = path.join(LIFEOS_DIR, "MEMORY"); const KNOWLEDGE_DIR = path.join(MEMORY_DIR, "KNOWLEDGE"); const LEARNING_DIR = path.join(MEMORY_DIR, "LEARNING"); diff --git a/LifeOS/install/LIFEOS/TOOLS/KnowledgeHarvester.ts b/LifeOS/install/LIFEOS/TOOLS/KnowledgeHarvester.ts index 138cc12a09..5c60b61351 100755 --- a/LifeOS/install/LIFEOS/TOOLS/KnowledgeHarvester.ts +++ b/LifeOS/install/LIFEOS/TOOLS/KnowledgeHarvester.ts @@ -58,7 +58,7 @@ const RESEARCH_DIR = path.join(MEMORY_DIR, "RESEARCH"); const HARVEST_QUEUE_DIR = path.join(KNOWLEDGE_DIR, "_harvest-queue"); const ARCHIVE_DIR = path.join(KNOWLEDGE_DIR, "_archive"); -const PROJECTS_DIR = path.join(HOME, ".claude", "projects"); +const PROJECTS_DIR = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(HOME, ".claude"), "projects"); /** * Auto-memory dirs โ€” multi-instance aware (#1170). diff --git a/LifeOS/install/LIFEOS/TOOLS/MigrateApprove.ts b/LifeOS/install/LIFEOS/TOOLS/MigrateApprove.ts index 87b7d93f2a..6c4b3856f5 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MigrateApprove.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MigrateApprove.ts @@ -85,7 +85,7 @@ function resolveTargetPath(target: string): string { } if (target === "memory/feedback") { // Feedback memories live outside LifeOS dir in projects/${HARNESS_USER_DIR}/memory/ - return join(HOME, ".claude", "projects", "${HARNESS_USER_DIR}", "memory"); + return join(process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"), "projects", "${HARNESS_USER_DIR}", "memory"); } return join(LIFEOS_DIR, target); } diff --git a/LifeOS/install/LIFEOS/TOOLS/PangramScore.ts b/LifeOS/install/LIFEOS/TOOLS/PangramScore.ts index e3339e7cb1..210c16097d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/PangramScore.ts +++ b/LifeOS/install/LIFEOS/TOOLS/PangramScore.ts @@ -35,7 +35,7 @@ for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { } -const ENV_PATH = `${process.env.HOME}/.claude/.env`; +const ENV_PATH = `${process.env.CLAUDE_CONFIG_DIR || `${process.env.HOME}/.claude`}/.env`; // Run-record: proof the detector actually executed on a specific text. The // WritingGate Stop hook reads this so its pass condition is "Pangram ran on diff --git a/LifeOS/install/hooks/MemoryHealthGate.hook.ts b/LifeOS/install/hooks/MemoryHealthGate.hook.ts index 6b5820d9d3..6ea14ef052 100755 --- a/LifeOS/install/hooks/MemoryHealthGate.hook.ts +++ b/LifeOS/install/hooks/MemoryHealthGate.hook.ts @@ -18,7 +18,7 @@ import { execFileSync } from "node:child_process"; import { join } from "node:path"; const HOME = process.env.HOME || ""; -const CHECK = join(HOME, ".claude/LIFEOS/TOOLS/MemoryHealthCheck.ts"); +const CHECK = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "TOOLS/MemoryHealthCheck.ts"); try { const out = execFileSync("bun", [CHECK], { diff --git a/LifeOS/install/hooks/SystemFileGuard.hook.ts b/LifeOS/install/hooks/SystemFileGuard.hook.ts index 898469640b..cfa8bb2f82 100755 --- a/LifeOS/install/hooks/SystemFileGuard.hook.ts +++ b/LifeOS/install/hooks/SystemFileGuard.hook.ts @@ -26,7 +26,7 @@ import { homedir } from "node:os"; import { evaluateWrite, extractNewContent } from "./lib/system-file-guard-core"; const HOME = process.env.HOME ?? homedir(); -const LOG_PATH = join(HOME, ".claude/LIFEOS/MEMORY/OBSERVABILITY/system-file-guard.jsonl"); +const LOG_PATH = join(process.env.LIFEOS_DIR || join(HOME, ".claude", "LIFEOS"), "MEMORY/OBSERVABILITY/system-file-guard.jsonl"); interface HookInput { session_id?: string; diff --git a/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts b/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts index 5f9f8fee8c..ffabac7597 100755 --- a/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts +++ b/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts @@ -6,7 +6,7 @@ import { join, basename } from "node:path"; import { homedir } from "node:os"; const HOME = homedir(); -const LIFEOS_DIR = join(HOME, ".claude"); +const LIFEOS_DIR = process.env.CLAUDE_CONFIG_DIR || join(HOME, ".claude"); const STATE_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "STATE"); const WORK_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "WORK"); // Claude Code names each project dir by its absolute path with "/" and "." mapped to "-", From 7b8bdb0edb36e4c5954c4cb5394e6dd65a149595 Mon Sep 17 00:00:00 2001 From: Abu Talha Date: Mon, 27 Jul 2026 18:28:56 +0530 Subject: [PATCH 12/12] Pulse Observability UI: drop hardcoded ~/.claude from user-facing dashboard text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A different category from prior fixes: this is client-side React text ("use client" components), rendered directly in the live Pulse dashboard a human reads โ€” not code that resolves paths at runtime. A project-scoped user reported seeing "~/.claude/LIFEOS/USER/HEALTH/" in the dashboard's empty-state guide, which is wrong for their install. No config API exists for the client to fetch its real resolved root, and building one just for display text would be disproportionate to the actual problem (nothing reads/writes the wrong file โ€” it's purely informational). Fixed by dropping the presumed-absolute "~/.claude/" prefix from ~20 UI strings across 10 files (both descriptive text and copy-pasteable command snippets like "bun ~/.claude/LIFEOS/TOOLS/CostTracker.ts status", which would literally fail if copy-pasted by a project-scoped user) โ€” displaying "LIFEOS/..." instead, which is accurate regardless of install location. Left two files untouched: a source comment in lib/effort.ts and three source comments in app/agents/page.tsx โ€” neither is ever rendered in the UI. Note: this app builds to a static export (out/) that Pulse serves directly; these source changes need `next build` re-run to actually reach a running dashboard. --- .../LIFEOS/PULSE/Observability/src/app/air/page.tsx | 2 +- .../LIFEOS/PULSE/Observability/src/app/assistant/page.tsx | 6 +++--- .../LIFEOS/PULSE/Observability/src/app/finances/page.tsx | 4 ++-- .../LIFEOS/PULSE/Observability/src/app/growth/page.tsx | 4 ++-- .../LIFEOS/PULSE/Observability/src/app/knowledge/page.tsx | 2 +- .../PULSE/Observability/src/app/performance/page.tsx | 8 ++++---- .../LIFEOS/PULSE/Observability/src/app/usage/page.tsx | 2 +- .../Observability/src/components/EmptyStateGuide.tsx | 4 ++-- .../Observability/src/components/TemplateOnboarding.tsx | 2 +- .../src/components/activity/LoopsDashboard.tsx | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/air/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/air/page.tsx index aa51f0e0ae..8aa706f731 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/air/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/air/page.tsx @@ -312,7 +312,7 @@ export default function AirPage() {
No monitors in cache yet. Run{" "} - bun ~/.claude/LIFEOS/PULSE/checks/airgradient-poll.ts + bun LIFEOS/PULSE/checks/airgradient-poll.ts {" "} to prime, or wait for the next 5-minute poll.
diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/assistant/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/assistant/page.tsx index 564f4e2201..09724c5c60 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/assistant/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/assistant/page.tsx @@ -393,7 +393,7 @@ export default function AssistantPage() {
- Claude Code harness ยท claude triggers list (not under ~/.claude/LIFEOS/) + Claude Code harness ยท claude triggers list (not under LIFEOS/)
Built into Claude Code โ€” triggers and active /loop sessions managed by the harness, not by Pulse. Pulse polls every 60s. @@ -465,8 +465,8 @@ export default function AssistantPage() { } >
-
~/.claude/LIFEOS/PULSE/PULSE.toml (system ยท ships with LifeOS, never written by this UI)
-
~/.claude/LIFEOS/USER/CONFIG/PULSE.user.toml (user ยท all edits/deletes from this UI write here)
+
LIFEOS/PULSE/PULSE.toml (system ยท ships with LifeOS, never written by this UI)
+
LIFEOS/USER/CONFIG/PULSE.user.toml (user ยท all edits/deletes from this UI write here)
LifeOS's scheduling system โ€” runs inside Pulse on this machine. Click any row to see full detail and edit interval / command / output. diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/finances/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/finances/page.tsx index 3f70e0f8e2..f065d1ea72 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/finances/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/finances/page.tsx @@ -1061,7 +1061,7 @@ function SpendInsightsSection({ insights }: { insights: SpendInsights }) {

Derived from statement CSVs in FINANCES/Statements/*. - Re-run with bun ~/.claude/LIFEOS/USER/TELOS/FINANCES/Tools/StatementAnalyzer.ts. + Re-run with bun LIFEOS/USER/TELOS/FINANCES/Tools/StatementAnalyzer.ts.

{insights.statement_spend.generated_at && ( @@ -1124,7 +1124,7 @@ function OutboundTab({ data }: { data: FinancesDataV2 }) {

Expenses data unavailable. Check{" "} - ~/.claude/LIFEOS/USER/TELOS/FINANCES/vendors.yaml + LIFEOS/USER/TELOS/FINANCES/vendors.yaml .

diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/growth/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/growth/page.tsx index 0041a88d72..4cb8d63fbc 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/growth/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/growth/page.tsx @@ -194,7 +194,7 @@ function NotConnected({ icon: Icon, accent, label, envVar, note }: { icon: Lucid
Not connected
- {note} Set {envVar} in ~/.claude/.env. + {note} Set {envVar} in your LifeOS .env.
); @@ -258,7 +258,7 @@ export default function GrowthPage() { ) : (

Newsletter not connected

-

Set BEEHIIV_API_KEY and BEEHIIV_PUB_ID in ~/.claude/.env.

+

Set BEEHIIV_API_KEY and BEEHIIV_PUB_ID in your LifeOS .env.

)} diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/knowledge/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/knowledge/page.tsx index 60e5687c99..247e3979c2 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/knowledge/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/knowledge/page.tsx @@ -262,7 +262,7 @@ function KnowledgeLanding({ data }: { data: WikiIndex }) { {isFreshInstall && ( )} diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/performance/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/performance/page.tsx index a561790119..c73396f1fe 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/performance/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/performance/page.tsx @@ -353,7 +353,7 @@ function AnthropicTab({ data }: { data: AnthropicData | null }) { return (
No ledger entries yet. CostTracker cron runs hourly โ€” next entry at :00. - Run manually: bun ~/.claude/LIFEOS/TOOLS/CostTracker.ts log + Run manually: bun LIFEOS/TOOLS/CostTracker.ts log
); @@ -503,19 +503,19 @@ function AnthropicTab({ data }: { data: AnthropicData | null }) {
- bun ~/.claude/LIFEOS/TOOLS/CostTracker.ts status + bun LIFEOS/TOOLS/CostTracker.ts status {" "} โ€” human-readable snapshot
- bun ~/.claude/LIFEOS/TOOLS/CostTracker.ts scan + bun LIFEOS/TOOLS/CostTracker.ts scan {" "} โ€” re-run static scan
- bun ~/.claude/LIFEOS/TOOLS/CostTracker.ts baseline + bun LIFEOS/TOOLS/CostTracker.ts baseline {" "} โ€” lock a new known-good snapshot
diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/usage/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/usage/page.tsx index 248ad540f0..95f0d6284d 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/usage/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/usage/page.tsx @@ -106,7 +106,7 @@ export default function UsagePage() { className="text-[13px] rounded-lg px-3 py-2" style={{ color: "var(--warn)", border: "1px solid rgba(251,191,36,0.2)", background: "rgba(251,191,36,0.05)" }} > - No per-day rollup yet. Run bun ~/.claude/LIFEOS/TOOLS/UsageAggregator.ts (or wait for the nightly job) to populate token/cost history. + No per-day rollup yet. Run bun LIFEOS/TOOLS/UsageAggregator.ts from your LifeOS root (or wait for the nightly job) to populate token/cost history.
)} diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/EmptyStateGuide.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/EmptyStateGuide.tsx index 737517d036..dbc0709451 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/EmptyStateGuide.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/EmptyStateGuide.tsx @@ -25,8 +25,8 @@ export default function EmptyStateGuide({ daPromptExample, hideInterview = false, }: EmptyStateGuideProps) { - const userPath = userDir ? `~/.claude/LIFEOS/USER/${userDir}/` : "~/.claude/LIFEOS/USER/"; - const readmePath = userDir ? `~/.claude/LIFEOS/USER/${userDir}/README.md` : "~/.claude/LIFEOS/USER/README.md"; + const userPath = userDir ? `LIFEOS/USER/${userDir}/` : "LIFEOS/USER/"; + const readmePath = userDir ? `LIFEOS/USER/${userDir}/README.md` : "LIFEOS/USER/README.md"; const defaultDaPrompt = daPromptExample ?? `help me set up my ${section.toLowerCase()}`; return ( diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/TemplateOnboarding.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/TemplateOnboarding.tsx index 0fdb825b34..39256eb34f 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/TemplateOnboarding.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/TemplateOnboarding.tsx @@ -64,7 +64,7 @@ export default function TemplateOnboarding() { Or edit - ~/.claude/LIFEOS/USER/ + LIFEOS/USER/ directly. diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/LoopsDashboard.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/LoopsDashboard.tsx index 000e656eab..636d13d959 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/LoopsDashboard.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/LoopsDashboard.tsx @@ -629,7 +629,7 @@ export default function LoopsDashboard() {

No ISAs found

- ISAs are created by the Algorithm and stored in ~/.claude/plans/ + ISAs are created by the Algorithm and stored under LIFEOS/MEMORY/WORK/