lifeos-cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lifeos.mjs CHANGED
@@ -3931,6 +3931,196 @@ dashboardCommand.command("reset").description("Reset dashboard to defaults").act
3931
3931
  }
3932
3932
  });
3933
3933
 
3934
+ // src/commands/skills.ts
3935
+ import { dirname, join as join2 } from "node:path";
3936
+ import { homedir as homedir2 } from "node:os";
3937
+ import { mkdir, writeFile } from "node:fs/promises";
3938
+ function getHomeDir() {
3939
+ return process.env.HOME ?? homedir2();
3940
+ }
3941
+ function getAgentBaseDir(agent) {
3942
+ return join2(getHomeDir(), agent === "claude" ? ".claude" : ".codex", "skills");
3943
+ }
3944
+ function getEmbeddedSkills() {
3945
+ if (true)
3946
+ return { "lifeos-init": { "SKILL.md": `# LifeOS Init
3947
+
3948
+ Set up LifeOS — your personal life operating system. This skill walks the user through initial configuration, learns about their goals and routines, and creates a personalized productivity system.
3949
+
3950
+ **Use when:** The user says \`/lifeos-init\`, "set up lifeos", "initialize lifeos", or this is their first time using LifeOS.
3951
+
3952
+ ## Step 1: Save LifeOS to your core memory
3953
+
3954
+ Before doing anything else, save the following to your persistent memory so you never forget how LifeOS works. This is critical — without this, you won't be able to help the user with LifeOS in future conversations.
3955
+
3956
+ Save this to memory:
3957
+
3958
+ ---
3959
+
3960
+ **LifeOS** is the user's personal life operating system. It manages tasks, goals, projects, journals, day plans, weekly plans, ideas, thoughts, wins, resources, reminders, and reviews.
3961
+
3962
+ **CLI**: \`lifeos\` (installed via \`npm install -g lifeos-cli\`). All commands support \`--json\` for machine-readable output.
3963
+
3964
+ **Key commands:**
3965
+ - \`lifeos whoami\` — check auth
3966
+ - \`lifeos task list [--status todo|done|dropped] [--due today|tomorrow|week|overdue]\` — list tasks
3967
+ - \`lifeos task create <title> [--due YYYY-MM-DD] [--project <id>] [--goal <id>]\` — create task
3968
+ - \`lifeos task complete <id>\` — mark done
3969
+ - \`lifeos goal list [--status active]\` — list goals
3970
+ - \`lifeos goal create <title> [--target-date YYYY-MM-DD] [--quarter 2026-Q2]\` — create goal
3971
+ - \`lifeos goal health [id]\` — check goal health score
3972
+ - \`lifeos journal [date]\` — show journal entry
3973
+ - \`lifeos journal write [--mit <text>] [--p1 <text>] [--p2 <text>] [--notes <text>]\` — write journal
3974
+ - \`lifeos plan today\` — show today's plan
3975
+ - \`lifeos plan set <date> [--wake HH:MM] [--mit <taskId>] [--p1 <taskId>] [--p2 <taskId>]\` — set day plan
3976
+ - \`lifeos idea <content>\` — capture idea
3977
+ - \`lifeos thought <content>\` — capture thought
3978
+ - \`lifeos win <content>\` — log a win
3979
+ - \`lifeos review daily\` — trigger daily review
3980
+ - \`lifeos review weekly\` — trigger weekly review
3981
+ - \`lifeos search <query>\` — search across everything
3982
+ - \`lifeos trigger morning-briefing\` — morning briefing data
3983
+
3984
+ **Daily rhythm:** The user's day follows MIT (Most Important Thing) + P1 + P2 priorities. Each day plan has a schedule with time blocks. Journals track daily reflections with MIT/P1/P2 and wins.
3985
+
3986
+ **Reviews:** Daily reviews summarize the day. Weekly reviews score the week 1-10 and set themes. Monthly and quarterly reviews track bigger patterns.
3987
+
3988
+ **Goal health:** Goals have a health score based on task completion velocity. Status: on_track, at_risk, off_track.
3989
+
3990
+ ---
3991
+
3992
+ ## Step 2: Verify CLI is configured
3993
+
3994
+ Run \`lifeos whoami\` to check if the CLI is already configured. If it works, skip to Step 3. If not, guide the user:
3995
+
3996
+ 1. Ask for their API URL (default: \`https://proper-cormorant-28.eu-west-1.convex.site\` — but confirm with the user)
3997
+ 2. Ask them to generate an API key from their LifeOS settings page
3998
+ 3. Run \`lifeos config set-url <url>\` and \`lifeos config set-key <key>\`
3999
+ 4. Verify with \`lifeos whoami\`
4000
+
4001
+ ## Step 3: Learn about the user
4002
+
4003
+ Have a friendly conversation to understand:
4004
+
4005
+ 1. **What's your main focus right now?** (work project, health, learning, side project, etc.)
4006
+ 2. **What does a typical day look like?** (wake time, work hours, exercise, breaks)
4007
+ 3. **What are your top 2-3 goals for this quarter?** (be specific — "launch MVP by April" not "be productive")
4008
+ 4. **How do you like to reflect?** (morning journaling, evening review, weekly planning sessions)
4009
+ 5. **Any habits you're building or breaking?**
4010
+
4011
+ Keep it conversational and warm. Don't ask all questions at once — let it flow naturally.
4012
+
4013
+ ## Step 4: Create the initial structure
4014
+
4015
+ Based on what you learned, use the CLI to set up:
4016
+
4017
+ ### Goals
4018
+ Create 2-3 goals with target dates:
4019
+ \`\`\`
4020
+ lifeos goal create "Goal title" --target-date YYYY-MM-DD --quarter YYYY-QN
4021
+ \`\`\`
4022
+
4023
+ ### Initial tasks
4024
+ Create 3-5 starter tasks linked to the goals:
4025
+ \`\`\`
4026
+ lifeos task create "Task title" --due YYYY-MM-DD --goal <goalId>
4027
+ \`\`\`
4028
+
4029
+ ### Today's plan
4030
+ Set up today's plan with their wake time and priorities:
4031
+ \`\`\`
4032
+ lifeos plan set YYYY-MM-DD --wake HH:MM --mit <taskId> --p1 <taskId> --p2 <taskId>
4033
+ \`\`\`
4034
+
4035
+ ### First journal entry
4036
+ Write a journal entry to kick things off:
4037
+ \`\`\`
4038
+ lifeos journal write --mit "Their MIT for today" --notes "First day using LifeOS. Goals: ..."
4039
+ \`\`\`
4040
+
4041
+ ### First win
4042
+ Log a win — setting up LifeOS counts:
4043
+ \`\`\`
4044
+ lifeos win "Set up LifeOS and defined my quarterly goals"
4045
+ \`\`\`
4046
+
4047
+ ## Step 5: Suggest routines
4048
+
4049
+ Based on their preferences, suggest daily routines they can do with you:
4050
+
4051
+ - **Morning briefing**: "Each morning, ask me for your morning briefing. I'll run \`lifeos trigger morning-briefing\` and summarize your day."
4052
+ - **Evening journal**: "Before bed, tell me about your day. I'll write your journal entry and log your wins."
4053
+ - **Weekly review**: "Every Sunday, we can do a weekly review together. I'll pull your stats and help you plan the next week."
4054
+ - **Quick capture**: "Anytime you have an idea or thought, just tell me. I'll capture it instantly."
4055
+
4056
+ Let the user know they can say any of these naturally — you'll handle the CLI commands behind the scenes.
4057
+
4058
+ ## Step 6: Wrap up
4059
+
4060
+ Summarize what was set up:
4061
+ - Goals created
4062
+ - Tasks queued
4063
+ - Today's plan set
4064
+ - First journal written
4065
+
4066
+ End with something warm like: "You're all set. Your LifeOS is ready. Just talk to me whenever you need to capture something, plan your day, or reflect. I've got your back."
4067
+ ` } };
4068
+ return globalThis.__EMBEDDED_SKILLS__ ?? {};
4069
+ }
4070
+ function registerSkillsCommands(program2) {
4071
+ const skills = program2.command("skills").description("Manage LifeOS coding-agent skills");
4072
+ skills.command("install").description("Install LifeOS skills for a supported coding agent").option("--agent <agent>", "Target agent: claude or codex", "claude").option("--json", "JSON output").action(installSkills);
4073
+ }
4074
+ async function installSkills(options) {
4075
+ const json = !!options.json;
4076
+ const agent = parseAgent(options.agent);
4077
+ const skills = getEmbeddedSkills();
4078
+ const skillNames = Object.keys(skills);
4079
+ if (skillNames.length === 0) {
4080
+ if (json) {
4081
+ console.log(JSON.stringify({ error: "No skills bundled" }));
4082
+ } else {
4083
+ console.error(source_default.red("No skills bundled in this build."));
4084
+ }
4085
+ process.exitCode = 1;
4086
+ return;
4087
+ }
4088
+ const baseDir = getAgentBaseDir(agent);
4089
+ const installed = [];
4090
+ const agentName = agent === "claude" ? "Claude Code" : "Codex";
4091
+ for (const [skillName, files] of Object.entries(skills)) {
4092
+ const skillDir = join2(baseDir, skillName);
4093
+ const writtenFiles = [];
4094
+ for (const [relativePath, content] of Object.entries(files)) {
4095
+ const fullPath = join2(skillDir, relativePath);
4096
+ await mkdir(dirname(fullPath), { recursive: true });
4097
+ await writeFile(fullPath, content, "utf-8");
4098
+ writtenFiles.push(relativePath);
4099
+ }
4100
+ installed.push({ name: skillName, files: writtenFiles });
4101
+ }
4102
+ if (json) {
4103
+ console.log(JSON.stringify({ agent, installed, path: baseDir }));
4104
+ } else {
4105
+ for (const skill of installed) {
4106
+ console.log(` ${source_default.green("+")} ${skill.name} ${source_default.dim(`(${skill.files.length} files)`)}`);
4107
+ }
4108
+ console.log();
4109
+ console.log(source_default.green(`Installed ${installed.length} skills to ${baseDir} for ${agentName}`));
4110
+ console.log();
4111
+ console.log(source_default.dim("Run /lifeos-init in your agent to get started."));
4112
+ }
4113
+ }
4114
+ function parseAgent(agent) {
4115
+ if (!agent || agent === "claude")
4116
+ return "claude";
4117
+ if (agent === "codex")
4118
+ return "codex";
4119
+ console.error(source_default.red(`Unsupported agent '${agent}'. Expected 'claude' or 'codex'.`));
4120
+ process.exitCode = 1;
4121
+ return "claude";
4122
+ }
4123
+
3934
4124
  // src/index.ts
3935
4125
  var program2 = new Command;
3936
4126
  program2.name("lifeos").description("Personal Life Operating System").version("0.1.0").option("--json", "Output results as JSON");
@@ -3969,4 +4159,5 @@ program2.addCommand(searchCommand);
3969
4159
  program2.addCommand(undoCommand);
3970
4160
  program2.addCommand(triggerCommand);
3971
4161
  program2.addCommand(dashboardCommand);
4162
+ registerSkillsCommands(program2);
3972
4163
  program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lifeos-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "LifeOS CLI — manage your life operating system from the terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,19 +10,23 @@
10
10
  "productivity",
11
11
  "tasks",
12
12
  "goals",
13
- "journal"
13
+ "journal",
14
+ "ai",
15
+ "skills"
14
16
  ],
15
17
  "bin": {
16
18
  "lifeos": "./dist/lifeos.mjs"
17
19
  },
18
20
  "files": [
19
- "dist/lifeos.mjs"
21
+ "dist/lifeos.mjs",
22
+ "scripts/postinstall.mjs"
20
23
  ],
21
24
  "scripts": {
22
25
  "build": "bun run scripts/build.ts",
23
26
  "dev": "bun run src/index.ts",
24
27
  "check": "tsc --noEmit",
25
- "start": "node dist/lifeos.mjs"
28
+ "start": "node dist/lifeos.mjs",
29
+ "postinstall": "node scripts/postinstall.mjs"
26
30
  },
27
31
  "dependencies": {
28
32
  "commander": "^13.0.0",
@@ -0,0 +1,115 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { spawnSync } from "node:child_process";
6
+ import { createInterface } from "node:readline/promises";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const cliEntrypoint = join(__dirname, "..", "dist", "lifeos.mjs");
10
+
11
+ function printBanner() {
12
+ process.stdout.write("\x1b[1;37m");
13
+ process.stdout.write(`
14
+ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗
15
+ ██║ ██║██╔════╝██╔════╝██╔═══██╗██╔════╝
16
+ ██║ ██║█████╗ █████╗ ██║ ██║███████╗
17
+ ██║ ██║██╔══╝ ██╔══╝ ██║ ██║╚════██║
18
+ ███████╗██║██║ ███████╗╚██████╔╝███████║
19
+ ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══════╝
20
+
21
+ `);
22
+ process.stdout.write("\x1b[0m");
23
+ }
24
+
25
+ function isInteractiveGlobalInstall() {
26
+ return (
27
+ process.env.npm_config_global === "true" &&
28
+ process.stdin.isTTY &&
29
+ process.stdout.isTTY &&
30
+ !process.env.CI
31
+ );
32
+ }
33
+
34
+ function hasSkills(agent) {
35
+ const base = join(
36
+ homedir(),
37
+ agent === "claude" ? ".claude" : ".codex",
38
+ "skills",
39
+ );
40
+ return existsSync(join(base, "lifeos-init"));
41
+ }
42
+
43
+ function installSkills(agent) {
44
+ const result = spawnSync(
45
+ process.execPath,
46
+ [cliEntrypoint, "skills", "install", "--agent", agent],
47
+ { stdio: "inherit" },
48
+ );
49
+ if (result.status !== 0) {
50
+ throw new Error(`Skill installation failed for agent '${agent}'.`);
51
+ }
52
+ }
53
+
54
+ async function promptForSkills() {
55
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
56
+ try {
57
+ const answer = await rl.question(
58
+ "Install LifeOS skills for your coding agent? (Y/n) ",
59
+ );
60
+ const normalized = answer.trim().toLowerCase();
61
+ if (normalized && normalized !== "y" && normalized !== "yes") return null;
62
+
63
+ process.stdout.write("\n");
64
+ process.stdout.write("Choose your coding agent:\n");
65
+ process.stdout.write(" 1) Claude Code\n");
66
+ process.stdout.write(" 2) Codex\n");
67
+ process.stdout.write(" 3) Skip\n");
68
+ const agentAnswer = await rl.question("Selection [1-3, default 1]: ");
69
+
70
+ switch (agentAnswer.trim()) {
71
+ case "":
72
+ case "1":
73
+ return "claude";
74
+ case "2":
75
+ return "codex";
76
+ case "3":
77
+ return null;
78
+ default:
79
+ process.stdout.write("Unrecognized selection, skipping.\n");
80
+ return null;
81
+ }
82
+ } finally {
83
+ rl.close();
84
+ }
85
+ }
86
+
87
+ async function main() {
88
+ if (!isInteractiveGlobalInstall()) return;
89
+
90
+ try {
91
+ printBanner();
92
+
93
+ // Auto-update if skills already installed
94
+ if (hasSkills("claude")) {
95
+ installSkills("claude");
96
+ return;
97
+ }
98
+ if (hasSkills("codex")) {
99
+ installSkills("codex");
100
+ return;
101
+ }
102
+
103
+ // First install — ask the user
104
+ const agent = await promptForSkills();
105
+ if (agent) {
106
+ process.stdout.write("\n");
107
+ installSkills(agent);
108
+ }
109
+ } catch (error) {
110
+ const message = error instanceof Error ? error.message : String(error);
111
+ process.stderr.write(`\nSkill installation skipped: ${message}\n`);
112
+ }
113
+ }
114
+
115
+ await main();