@datacore-one/cli 1.0.6 → 1.0.8

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.
Files changed (2) hide show
  1. package/dist/index.js +2795 -1599
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6980,727 +6980,1162 @@ var require_dist = __commonJS((exports) => {
6980
6980
  exports.visitAsync = visit.visitAsync;
6981
6981
  });
6982
6982
 
6983
- // src/lib/agent.ts
6984
- var exports_agent = {};
6985
- __export(exports_agent, {
6986
- listAgents: () => listAgents,
6987
- invokeAgent: () => invokeAgent,
6988
- agentExists: () => agentExists
6983
+ // src/lib/space.ts
6984
+ var exports_space = {};
6985
+ __export(exports_space, {
6986
+ listSpaces: () => listSpaces,
6987
+ getSpace: () => getSpace,
6988
+ getNextSpaceNumber: () => getNextSpaceNumber,
6989
+ createSpace: () => createSpace,
6990
+ auditSpace: () => auditSpace
6989
6991
  });
6990
- import { spawn as spawn2 } from "child_process";
6991
- import { existsSync as existsSync9 } from "fs";
6992
- import { join as join9 } from "path";
6993
- function commandExists2(cmd) {
6994
- try {
6995
- const { execSync: execSync6 } = __require("child_process");
6996
- execSync6(`which ${cmd}`, { stdio: "pipe" });
6997
- return true;
6998
- } catch {
6999
- return false;
7000
- }
7001
- }
7002
- async function invokeAgent(invocation, options = {}) {
7003
- const { stream = false, cwd = DATA_DIR7, timeout = 300000 } = options;
7004
- if (!commandExists2("claude")) {
7005
- return {
7006
- success: false,
7007
- output: "",
7008
- error: "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
7009
- };
7010
- }
7011
- if (!existsSync9(cwd)) {
7012
- return {
7013
- success: false,
7014
- output: "",
7015
- error: `Working directory not found: ${cwd}`
7016
- };
6992
+ import { existsSync as existsSync3, readdirSync, statSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
6993
+ import { join as join3, basename } from "path";
6994
+ function listSpaces() {
6995
+ if (!existsSync3(DATA_DIR2)) {
6996
+ return [];
7017
6997
  }
7018
- const prompt2 = buildAgentPrompt(invocation);
7019
- return new Promise((resolve) => {
7020
- const chunks = [];
7021
- let errorChunks = [];
7022
- let timedOut = false;
7023
- const proc = spawn2("claude", ["--print", prompt2], {
7024
- cwd,
7025
- stdio: ["ignore", "pipe", "pipe"],
7026
- env: {
7027
- ...process.env,
7028
- CI: "true"
7029
- }
7030
- });
7031
- const timer = setTimeout(() => {
7032
- timedOut = true;
7033
- proc.kill("SIGTERM");
7034
- }, timeout);
7035
- proc.stdout?.on("data", (data) => {
7036
- const text = data.toString();
7037
- chunks.push(text);
7038
- if (stream) {
7039
- process.stdout.write(text);
7040
- }
7041
- });
7042
- proc.stderr?.on("data", (data) => {
7043
- errorChunks.push(data.toString());
7044
- });
7045
- proc.on("close", (code) => {
7046
- clearTimeout(timer);
7047
- const output2 = chunks.join("");
7048
- const stderr = errorChunks.join("");
7049
- if (timedOut) {
7050
- resolve({
7051
- success: false,
7052
- output: output2,
7053
- error: `Agent timed out after ${timeout}ms`
7054
- });
7055
- return;
7056
- }
7057
- if (code !== 0) {
7058
- resolve({
7059
- success: false,
7060
- output: output2,
7061
- error: stderr || `Agent exited with code ${code}`
7062
- });
7063
- return;
7064
- }
7065
- const artifacts = parseArtifacts(output2);
7066
- resolve({
7067
- success: true,
7068
- output: output2,
7069
- artifacts: Object.keys(artifacts).length > 0 ? artifacts : undefined
7070
- });
7071
- });
7072
- proc.on("error", (err) => {
7073
- clearTimeout(timer);
7074
- resolve({
7075
- success: false,
7076
- output: chunks.join(""),
7077
- error: err.message
7078
- });
6998
+ const entries = readdirSync(DATA_DIR2, { withFileTypes: true });
6999
+ const spaces = [];
7000
+ for (const entry of entries) {
7001
+ if (!entry.isDirectory())
7002
+ continue;
7003
+ const match = entry.name.match(/^(\d+)-(.+)$/);
7004
+ if (!match)
7005
+ continue;
7006
+ const [, numStr, name] = match;
7007
+ const number = parseInt(numStr, 10);
7008
+ const path = join3(DATA_DIR2, entry.name);
7009
+ spaces.push({
7010
+ name: entry.name,
7011
+ number,
7012
+ path,
7013
+ type: number === 0 ? "personal" : "team",
7014
+ hasGit: existsSync3(join3(path, ".git")),
7015
+ hasClaude: existsSync3(join3(path, "CLAUDE.md")) || existsSync3(join3(path, "CLAUDE.base.md"))
7079
7016
  });
7080
- });
7081
- }
7082
- function buildAgentPrompt(invocation) {
7083
- const { agent, params } = invocation;
7084
- const paramLines = Object.entries(params).map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`).join(`
7085
- `);
7086
- return `Use the Task tool to invoke the "${agent}" agent with the following parameters:
7087
-
7088
- ${paramLines || "(no parameters)"}
7089
-
7090
- Wait for the agent to complete and return its results.`;
7091
- }
7092
- function parseArtifacts(output2) {
7093
- const artifacts = {};
7094
- const regex = /<!-- ARTIFACT:([^:]+):([^ ]+) -->/g;
7095
- let match;
7096
- while ((match = regex.exec(output2)) !== null) {
7097
- const [, name, path] = match;
7098
- if (name && path) {
7099
- artifacts[name] = path;
7100
- }
7101
7017
  }
7102
- return artifacts;
7018
+ spaces.sort((a, b) => a.number - b.number);
7019
+ return spaces;
7103
7020
  }
7104
- function listAgents() {
7105
- const registryPath = join9(DATA_DIR7, ".datacore", "registry", "agents.yaml");
7106
- if (!existsSync9(registryPath)) {
7107
- return [];
7108
- }
7109
- try {
7110
- const { readFileSync: readFileSync5 } = __require("fs");
7111
- const { parse: parse3 } = require_dist();
7112
- const content = readFileSync5(registryPath, "utf-8");
7113
- const registry = parse3(content);
7114
- return registry.agents?.map((a) => a.name) || [];
7115
- } catch {
7116
- return [];
7117
- }
7021
+ function getSpace(nameOrNumber) {
7022
+ const spaces = listSpaces();
7023
+ if (typeof nameOrNumber === "number") {
7024
+ return spaces.find((s) => s.number === nameOrNumber) || null;
7025
+ }
7026
+ let space = spaces.find((s) => s.name === nameOrNumber);
7027
+ if (space)
7028
+ return space;
7029
+ space = spaces.find((s) => s.name.includes(nameOrNumber));
7030
+ return space || null;
7118
7031
  }
7119
- function agentExists(name) {
7120
- return listAgents().includes(name);
7032
+ function getNextSpaceNumber() {
7033
+ const spaces = listSpaces();
7034
+ if (spaces.length === 0)
7035
+ return 0;
7036
+ const maxNumber = Math.max(...spaces.map((s) => s.number));
7037
+ return maxNumber + 1;
7121
7038
  }
7122
- var DATA_DIR7;
7123
- var init_agent = __esm(() => {
7124
- DATA_DIR7 = join9(process.env.HOME || "~", "Data");
7125
- });
7126
-
7127
- // src/routing.ts
7128
- var RESOURCES = ["space", "module", "config", "nightshift", "cron", "snapshot"];
7129
- var META_COMMANDS = [
7130
- "init",
7131
- "doctor",
7132
- "ingest",
7133
- "sync",
7134
- "today",
7135
- "tomorrow",
7136
- "version"
7137
- ];
7138
- var ACTIONS = {
7139
- space: ["create", "list"],
7140
- module: ["install", "list", "update", "remove"],
7141
- config: ["show", "get", "set"],
7142
- nightshift: ["status", "trigger", "queue"],
7143
- cron: ["install", "status", "remove"],
7144
- snapshot: ["create", "restore", "diff", "show"]
7145
- };
7146
- function parseArgs(argv) {
7147
- if (argv.length === 0) {
7148
- return { type: "help", topic: undefined, subtopic: undefined };
7149
- }
7150
- const [first, ...rest] = argv;
7151
- if (!first) {
7152
- return { type: "help", topic: undefined, subtopic: undefined };
7153
- }
7154
- const helpIndex = argv.findIndex((a) => a === "--help" || a === "-h");
7155
- if (helpIndex !== -1) {
7156
- if (helpIndex === 0) {
7157
- return { type: "help", topic: undefined, subtopic: undefined };
7158
- }
7159
- if (helpIndex === 1 && RESOURCES.includes(first)) {
7160
- return { type: "help", topic: first, subtopic: undefined };
7161
- }
7162
- if (helpIndex === 2 && RESOURCES.includes(first)) {
7163
- return { type: "help", topic: first, subtopic: rest[0] };
7164
- }
7165
- return { type: "help", topic: first, subtopic: undefined };
7166
- }
7167
- if (first === "--version" || first === "-v") {
7168
- return { type: "meta", command: "version", args: [], flags: {} };
7039
+ function createSpace(name, type = "team") {
7040
+ const number = type === "personal" ? 0 : getNextSpaceNumber();
7041
+ const normalizedName = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
7042
+ const folderName = `${number}-${normalizedName}`;
7043
+ const spacePath = join3(DATA_DIR2, folderName);
7044
+ if (existsSync3(spacePath)) {
7045
+ throw new Error(`Space already exists: ${folderName}`);
7169
7046
  }
7170
- if (first === "help") {
7171
- return { type: "help", topic: rest[0], subtopic: rest[1] };
7047
+ mkdirSync2(spacePath, { recursive: true });
7048
+ const dirs = type === "personal" ? [
7049
+ ".datacore",
7050
+ ".datacore/commands",
7051
+ ".datacore/agents",
7052
+ ".datacore/learning",
7053
+ ".datacore/state",
7054
+ ".datacore/env",
7055
+ "org",
7056
+ "0-inbox",
7057
+ "notes",
7058
+ "notes/journals",
7059
+ "notes/pages",
7060
+ "notes/zettel",
7061
+ "journal",
7062
+ "3-knowledge",
7063
+ "3-knowledge/pages",
7064
+ "3-knowledge/zettel",
7065
+ "3-knowledge/literature",
7066
+ "3-knowledge/reference",
7067
+ "4-archive",
7068
+ "content"
7069
+ ] : [
7070
+ ".datacore",
7071
+ ".datacore/commands",
7072
+ ".datacore/agents",
7073
+ ".datacore/learning",
7074
+ ".datacore/state",
7075
+ ".datacore/env",
7076
+ "org",
7077
+ "0-inbox",
7078
+ "journal",
7079
+ "1-tracks",
7080
+ "1-tracks/ops",
7081
+ "1-tracks/product",
7082
+ "1-tracks/dev",
7083
+ "1-tracks/research",
7084
+ "1-tracks/comms",
7085
+ "2-projects",
7086
+ "3-knowledge",
7087
+ "3-knowledge/pages",
7088
+ "3-knowledge/zettel",
7089
+ "3-knowledge/literature",
7090
+ "3-knowledge/reference",
7091
+ "4-archive"
7092
+ ];
7093
+ for (const dir of dirs) {
7094
+ mkdirSync2(join3(spacePath, dir), { recursive: true });
7172
7095
  }
7173
- if (META_COMMANDS.includes(first)) {
7174
- const { args, flags } = parseRest(rest);
7175
- return { type: "meta", command: first, args, flags };
7176
- }
7177
- if (RESOURCES.includes(first)) {
7178
- const action = rest[0] || "list";
7179
- const { args, flags } = parseRest(rest.slice(1));
7180
- return { type: "resource", resource: first, action, args, flags };
7181
- }
7182
- return { type: "unknown", command: first };
7183
- }
7184
- function parseRest(argv) {
7185
- const args = [];
7186
- const flags = {};
7187
- for (let i = 0;i < argv.length; i++) {
7188
- const arg = argv[i];
7189
- if (!arg)
7190
- continue;
7191
- if (arg.startsWith("--")) {
7192
- if (arg.includes("=")) {
7193
- const [key, value] = arg.slice(2).split("=");
7194
- if (key)
7195
- flags[key] = value ?? true;
7196
- } else {
7197
- const key = arg.slice(2);
7198
- const next = argv[i + 1];
7199
- if (next && !next.startsWith("-")) {
7200
- flags[key] = next;
7201
- i++;
7202
- } else {
7203
- flags[key] = true;
7204
- }
7205
- }
7206
- } else if (arg.startsWith("-") && arg.length === 2) {
7207
- const key = arg.slice(1);
7208
- flags[key] = true;
7209
- } else {
7210
- args.push(arg);
7211
- }
7212
- }
7213
- return { args, flags };
7214
- }
7215
- function suggestCommand(unknown) {
7216
- const allCommands = [...META_COMMANDS, ...RESOURCES];
7217
- for (const cmd of allCommands) {
7218
- if (cmd.startsWith(unknown) || unknown.startsWith(cmd)) {
7219
- return cmd;
7220
- }
7221
- if (Math.abs(cmd.length - unknown.length) <= 1) {
7222
- let diff = 0;
7223
- for (let i = 0;i < Math.max(cmd.length, unknown.length); i++) {
7224
- if (cmd[i] !== unknown[i])
7225
- diff++;
7226
- }
7227
- if (diff <= 2)
7228
- return cmd;
7229
- }
7230
- }
7231
- return null;
7232
- }
7233
-
7234
- // src/help.ts
7235
- function showHelp() {
7236
- console.log(`datacore - AI Second Brain Setup & Management CLI
7096
+ if (type === "personal") {
7097
+ writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
7098
+ #+FILETAGS: :inbox:
7237
7099
 
7238
- Setup and admin tool for Datacore. For daily use, run: cd ~/Data && claude
7100
+ Capture everything here. Process daily to zero.
7239
7101
 
7240
- Usage:
7241
- datacore <command> [args] [options]
7102
+ * TODO Do more. With less.
7103
+ * Inbox
7104
+ `);
7105
+ writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
7106
+ #+TODO: TODO NEXT WAITING | DONE CANCELED
7107
+ #+FILETAGS: :tasks:
7242
7108
 
7243
- Setup:
7244
- init Set up a new Datacore installation
7245
- doctor Check dependencies and system status
7246
- ingest <path> Import files during setup
7109
+ Tasks organized by focus area. Tag with :AI: to delegate to agents.
7247
7110
 
7248
- Admin:
7249
- space Create and list spaces
7250
- module Install and manage modules
7251
- snapshot Create/restore installation snapshots
7252
- config View and modify settings
7111
+ * TIER 1: STRATEGIC FOUNDATION
7112
+ ** /Projects
7113
+ ** /Work
7114
+ * TIER 2: SUPPORTING WORK
7115
+ ** Admin
7116
+ ** Maintenance
7117
+ * PERSONAL: LIFE & DEVELOPMENT
7118
+ ** /Personal Development
7119
+ ** /Health & Longevity
7120
+ ** Home & Family
7121
+ ** Financial Management
7122
+ * RESEARCH & LEARNING
7123
+ ** Technology
7124
+ ** Skills
7125
+ `);
7126
+ writeFileSync2(join3(spacePath, "org", "nightshift.org"), `#+TITLE: Nightshift Queue
7127
+ #+TODO: QUEUED EXECUTING | DONE FAILED
7128
+ #+FILETAGS: :nightshift:
7253
7129
 
7254
- Automation (for cron jobs):
7255
- sync Sync all git repos
7256
- today Generate daily briefing
7257
- tomorrow End-of-day wrap-up
7258
- nightshift Queue and trigger AI tasks
7259
- cron Set up scheduled tasks
7130
+ AI tasks queued for overnight execution. Managed by /tomorrow command.
7260
7131
 
7261
- Meta:
7262
- version Show version
7263
- help [topic] Show help for a topic
7132
+ * Queue
7133
+ `);
7134
+ writeFileSync2(join3(spacePath, "org", "habits.org"), `#+TITLE: Habits
7135
+ #+FILETAGS: :habits:
7264
7136
 
7265
- Options:
7266
- --format json|human Output format (auto-detects TTY)
7267
- --help, -h Show help for any command
7268
- --yes, -y Skip confirmation prompts
7137
+ Recurring behaviors and routines. Track with org-habit.
7269
7138
 
7270
- Examples:
7271
- # Initial setup
7272
- datacore init
7273
- datacore ingest ~/Documents/chatgpt-export/
7139
+ * Daily
7140
+ * Weekly
7141
+ * Monthly
7142
+ `);
7143
+ writeFileSync2(join3(spacePath, "org", "someday.org"), `#+TITLE: Someday/Maybe
7144
+ #+FILETAGS: :someday:
7274
7145
 
7275
- # Admin
7276
- datacore snapshot create
7277
- datacore module install nightshift
7146
+ Ideas and projects for the future. Review monthly.
7278
7147
 
7279
- # Then use Claude Code for daily workflow
7280
- cd ~/Data && claude`);
7281
- }
7282
- function showResourceHelp(resource) {
7283
- if (resource === "init") {
7284
- showInitHelp();
7285
- return;
7286
- }
7287
- if (resource === "doctor") {
7288
- showDoctorHelp();
7289
- return;
7290
- }
7291
- if (resource === "ingest") {
7292
- showIngestHelp();
7293
- return;
7294
- }
7295
- if (resource === "sync") {
7296
- showSyncHelp();
7297
- return;
7298
- }
7299
- if (resource === "today" || resource === "tomorrow") {
7300
- showGtdMetaHelp(resource);
7301
- return;
7302
- }
7303
- if (!RESOURCES.includes(resource)) {
7304
- console.log(`Unknown command: ${resource}
7148
+ * Someday
7149
+ * Maybe
7150
+ `);
7151
+ writeFileSync2(join3(spacePath, "org", "archive.org"), `#+TITLE: Archive
7152
+ #+FILETAGS: :archive:
7305
7153
 
7306
- Available commands: init, doctor, ingest, sync, today, tomorrow, space, module, config, nightshift, cron, snapshot
7154
+ Completed and canceled tasks. Searchable history.
7307
7155
 
7308
- Run 'datacore help' for overview.`);
7309
- return;
7310
- }
7311
- const actions = ACTIONS[resource];
7312
- console.log(`datacore ${resource} - ${getResourceDescription(resource)}
7156
+ * Archived Tasks
7157
+ `);
7158
+ } else {
7159
+ writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
7160
+ #+FILETAGS: :inbox:
7313
7161
 
7314
- Usage:
7315
- datacore ${resource} <action> [args] [options]
7162
+ * Capture items here
7163
+ `);
7164
+ writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
7165
+ #+FILETAGS: :tasks:
7316
7166
 
7317
- Actions:`);
7318
- for (const action of actions) {
7319
- console.log(` ${action.padEnd(18)} ${getActionDescription(resource, action)}`);
7167
+ * Tasks
7168
+ ** TODO items go here
7169
+ `);
7320
7170
  }
7321
- console.log(`
7322
- For detailed help on an action:
7323
- datacore ${resource} <action> --help
7324
- datacore help ${resource} <action>`);
7325
- }
7326
- function showInitHelp() {
7327
- console.log(`datacore init - Set up a new Datacore installation
7171
+ if (type === "personal") {
7172
+ writeFileSync2(join3(spacePath, "_index.md"), `# Personal Space
7328
7173
 
7329
- Usage:
7330
- datacore init [options]
7174
+ Your personal knowledge base and GTD system.
7331
7175
 
7332
- Description:
7333
- Interactive wizard that sets up your Datacore second brain:
7334
- 1. Creates ~/Data directory structure
7335
- 2. Initializes git repositories
7336
- 3. Generates CLAUDE.md context files
7337
- 4. Creates personal space (0-personal)
7338
- 5. Optionally installs recommended modules
7176
+ ## GTD Workflow
7339
7177
 
7340
- Options:
7341
- --path <dir> Installation directory (default: ~/Data)
7342
- --yes, -y Use defaults, skip prompts
7343
- --no-claude Skip Claude Code check
7178
+ 1. **Capture** → \`org/inbox.org\` - dump everything here
7179
+ 2. **Clarify** Is it actionable? What's the next action?
7180
+ 3. **Organize** Move to \`next_actions.org\` by focus area
7181
+ 4. **Reflect** Weekly review, monthly strategic
7182
+ 5. **Engage** → Do the work, delegate with :AI: tags
7344
7183
 
7345
- Examples:
7346
- # Interactive setup
7347
- datacore init
7184
+ ## Org Files (GTD)
7348
7185
 
7349
- # Non-interactive with defaults
7350
- datacore init --yes
7186
+ | File | Purpose |
7187
+ |------|---------|
7188
+ | [inbox.org](org/inbox.org) | Single capture point - process to zero daily |
7189
+ | [next_actions.org](org/next_actions.org) | Active tasks by focus area |
7190
+ | [nightshift.org](org/nightshift.org) | AI task queue (overnight processing) |
7191
+ | [habits.org](org/habits.org) | Recurring behaviors |
7192
+ | [someday.org](org/someday.org) | Future possibilities |
7193
+ | [archive.org](org/archive.org) | Completed/canceled tasks |
7351
7194
 
7352
- # Custom location
7353
- datacore init --path ~/my-data`);
7354
- }
7355
- function showDoctorHelp() {
7356
- console.log(`datacore doctor - Check dependencies and system status
7195
+ ## Knowledge
7357
7196
 
7358
- Usage:
7359
- datacore doctor [options]
7197
+ | Folder | Purpose |
7198
+ |--------|---------|
7199
+ | [notes/](notes/) | Personal knowledge base (Obsidian) |
7200
+ | [notes/journals/](notes/journals/) | Daily personal journals |
7201
+ | [3-knowledge/](3-knowledge/) | Structured knowledge (zettelkasten) |
7360
7202
 
7361
- Description:
7362
- Verifies all required dependencies are installed and checks
7363
- the health of your Datacore installation.
7203
+ ## Other
7364
7204
 
7365
- Checks:
7366
- Required:
7367
- - git Version control
7368
- - git-lfs Large file support
7369
- - node >= 18 Node.js runtime
7370
- - python >= 3.9 Python runtime
7371
-
7372
- Recommended:
7373
- - claude Claude Code CLI for AI features
7205
+ - [journal/](journal/) - AI session journals (auto-generated)
7206
+ - [content/](content/) - Generated content (drafts, emails)
7207
+ - [0-inbox/](0-inbox/) - File inbox (process files here)
7208
+ - [4-archive/](4-archive/) - Historical content
7209
+ `);
7210
+ } else {
7211
+ writeFileSync2(join3(spacePath, "_index.md"), `# ${name}
7374
7212
 
7375
- Datacore:
7376
- - ~/Data exists
7377
- - .datacore/ configured
7378
- - Spaces detected
7213
+ Team space.
7379
7214
 
7380
- Options:
7381
- --format json Output as JSON
7215
+ ## Structure
7382
7216
 
7383
- Examples:
7384
- datacore doctor
7385
- datacore doctor --format json`);
7386
- }
7387
- function showIngestHelp() {
7388
- console.log(`datacore ingest - Import files with AI processing
7217
+ - \`org/\` - Task coordination
7218
+ - \`1-tracks/\` - Department work
7219
+ - \`2-projects/\` - Code repositories
7220
+ - \`3-knowledge/\` - Shared knowledge
7221
+ - \`4-archive/\` - Historical content
7389
7222
 
7390
- Usage:
7391
- datacore ingest <path> [options]
7223
+ ## Quick Links
7392
7224
 
7393
- Description:
7394
- Imports files into Datacore with deep knowledge extraction.
7395
- The CLI validates paths and invokes the ingest-coordinator agent
7396
- which handles the 6-phase semantic processing:
7225
+ - [Inbox](org/inbox.org)
7226
+ - [Tasks](org/next_actions.org)
7227
+ - [Journal](journal/)
7228
+ - [Knowledge](3-knowledge/)
7229
+ `);
7230
+ }
7231
+ if (type === "personal") {
7232
+ writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# Personal Space
7397
7233
 
7398
- 1. READ - Analyze content
7399
- 2. ASSESS - Determine destination
7400
- 3. EXTRACT - Pull out knowledge (zettels, insights)
7401
- 4. CAPTURE - Log tasks and TODOs
7402
- 5. FILE - Move to semantic location
7403
- 6. LINK - Connect to knowledge graph
7234
+ Personal GTD system and knowledge base (per DIP-0009).
7404
7235
 
7405
- Options:
7406
- --space <name> Target space (default: 0-personal)
7407
- --dry-run Show what would happen without doing it
7236
+ ## GTD Files
7408
7237
 
7409
- Examples:
7410
- # Import a file
7411
- datacore ingest ~/Documents/report.pdf
7238
+ | File | Purpose | Process |
7239
+ |------|---------|---------|
7240
+ | \`org/inbox.org\` | Single capture point | Process to zero daily |
7241
+ | \`org/next_actions.org\` | Active tasks by focus area | Work from here |
7242
+ | \`org/nightshift.org\` | AI task queue | Auto-managed by /tomorrow |
7243
+ | \`org/habits.org\` | Recurring behaviors | Track daily |
7244
+ | \`org/someday.org\` | Future possibilities | Review monthly |
7245
+ | \`org/archive.org\` | Completed tasks | Searchable history |
7412
7246
 
7413
- # Import a folder
7414
- datacore ingest ~/Documents/export/
7247
+ ## Task States
7415
7248
 
7416
- # Target specific space
7417
- datacore ingest ~/Documents/contracts/ --space 1-datafund`);
7418
- }
7419
- function showSyncHelp() {
7420
- console.log(`datacore sync - Git sync for all repos
7249
+ | State | Meaning |
7250
+ |-------|---------|
7251
+ | \`TODO\` | Standard next action |
7252
+ | \`NEXT\` | High priority, work today |
7253
+ | \`WAITING\` | Blocked on external |
7254
+ | \`DONE\` | Completed (terminal) |
7255
+ | \`CANCELED\` | Will not do (terminal) |
7421
7256
 
7422
- Usage:
7423
- datacore sync [action] [options]
7257
+ ## AI Delegation
7424
7258
 
7425
- Actions:
7426
- (none) Pull all repos (default)
7427
- push Commit and push changes
7428
- status Show git status overview
7259
+ Tag tasks with \`:AI:\` to delegate to agents:
7429
7260
 
7430
- Description:
7431
- Manages git synchronization for the root Datacore repo
7432
- and all space repositories.
7261
+ | Tag | Agent | Autonomous |
7262
+ |-----|-------|------------|
7263
+ | \`:AI:content:\` | gtd-content-writer | Yes |
7264
+ | \`:AI:research:\` | gtd-research-processor | Yes |
7265
+ | \`:AI:data:\` | gtd-data-analyzer | Yes |
7266
+ | \`:AI:pm:\` | gtd-project-manager | Yes |
7433
7267
 
7434
- Options:
7435
- --message <msg> Commit message (for push)
7268
+ ## Knowledge Structure
7436
7269
 
7437
- Examples:
7438
- # Pull latest changes
7439
- datacore sync
7270
+ | Location | Purpose |
7271
+ |----------|---------|
7272
+ | \`notes/\` | Personal knowledge base (Obsidian) |
7273
+ | \`notes/journals/\` | Daily personal reflections |
7274
+ | \`3-knowledge/zettel/\` | Atomic concept notes |
7275
+ | \`3-knowledge/literature/\` | Source summaries |
7440
7276
 
7441
- # Push changes
7442
- datacore sync push --message "Daily update"
7277
+ ## Daily Workflow
7443
7278
 
7444
- # Check status
7445
- datacore sync status`);
7446
- }
7447
- function showGtdMetaHelp(command) {
7448
- if (command === "today") {
7449
- console.log(`datacore today - Generate daily briefing
7279
+ 1. **Morning** - Run \`/today\` for briefing
7280
+ 2. **Capture** - Everything to inbox.org
7281
+ 3. **Process** - Clear inbox, route tasks
7282
+ 4. **Work** - Focus on NEXT items
7283
+ 5. **Evening** - Run \`/tomorrow\` for wrap-up
7450
7284
 
7451
- Usage:
7452
- datacore today [options]
7285
+ ## See Also
7453
7286
 
7454
- Description:
7455
- Generates your daily briefing by invoking the /today command.
7456
- Includes:
7457
- - Calendar events
7458
- - Priority tasks
7459
- - Nightshift results (completed AI tasks)
7460
- - Journal entry
7287
+ Parent: ~/Data/CLAUDE.md
7288
+ `);
7289
+ } else {
7290
+ writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# ${name} Space
7461
7291
 
7462
- Options:
7463
- --format json Output as JSON
7292
+ Team space for ${name}.
7464
7293
 
7465
- Examples:
7466
- datacore today`);
7467
- } else {
7468
- console.log(`datacore tomorrow - End-of-day wrap-up
7294
+ ## Structure
7469
7295
 
7470
- Usage:
7471
- datacore tomorrow [options]
7296
+ See parent CLAUDE.md for full documentation.
7297
+ `);
7298
+ }
7299
+ writeFileSync2(join3(spacePath, ".datacore", "config.yaml"), `# Space configuration
7300
+ name: ${normalizedName}
7301
+ type: ${type}
7302
+ `);
7303
+ writeFileSync2(join3(spacePath, ".datacore", "learning", "patterns.md"), `# Patterns
7472
7304
 
7473
- Description:
7474
- Wraps up your day by invoking the /tomorrow command.
7475
- Includes:
7476
- - Session summary
7477
- - Queuing AI tasks for overnight
7478
- - Learning extraction
7479
- - Journal update
7305
+ Successful approaches to remember.
7306
+ `);
7307
+ writeFileSync2(join3(spacePath, ".datacore", "learning", "corrections.md"), `# Corrections
7480
7308
 
7481
- Options:
7482
- --format json Output as JSON
7309
+ Human feedback log.
7310
+ `);
7311
+ writeFileSync2(join3(spacePath, ".datacore", "learning", "preferences.md"), `# Preferences
7483
7312
 
7484
- Examples:
7485
- datacore tomorrow`);
7486
- }
7313
+ Style and preference notes.
7314
+ `);
7315
+ writeFileSync2(join3(spacePath, ".gitignore"), type === "personal" ? `.datacore/state/
7316
+ .datacore/env/
7317
+ CLAUDE.md
7318
+ CLAUDE.local.md
7319
+ ` : `.datacore/state/
7320
+ .datacore/env/
7321
+ CLAUDE.md
7322
+ CLAUDE.local.md
7323
+ 2-projects/
7324
+ `);
7325
+ return {
7326
+ name: folderName,
7327
+ number,
7328
+ path: spacePath,
7329
+ type,
7330
+ hasGit: false,
7331
+ hasClaude: true
7332
+ };
7487
7333
  }
7488
- function showActionHelp(resource, action) {
7489
- if (!RESOURCES.includes(resource)) {
7490
- console.log(`Unknown resource: ${resource}`);
7491
- return;
7334
+ function auditSpace(nameOrPath) {
7335
+ let spacePath;
7336
+ let spaceName;
7337
+ if (existsSync3(nameOrPath) && statSync(nameOrPath).isDirectory()) {
7338
+ spacePath = nameOrPath;
7339
+ spaceName = basename(nameOrPath);
7340
+ } else {
7341
+ const space = getSpace(nameOrPath);
7342
+ if (!space) {
7343
+ throw new Error(`Space not found: ${nameOrPath}`);
7344
+ }
7345
+ spacePath = space.path;
7346
+ spaceName = space.name;
7492
7347
  }
7493
- const actions = ACTIONS[resource];
7494
- if (!actions.includes(action)) {
7495
- console.log(`Unknown action: ${action} for ${resource}
7496
-
7497
- Available actions: ${actions.join(", ")}`);
7498
- return;
7348
+ const issues = [];
7349
+ const requiredDirs = [
7350
+ ".datacore",
7351
+ "org",
7352
+ "0-inbox",
7353
+ "journal"
7354
+ ];
7355
+ for (const dir of requiredDirs) {
7356
+ if (!existsSync3(join3(spacePath, dir))) {
7357
+ issues.push({
7358
+ type: "missing",
7359
+ path: dir,
7360
+ message: `Required directory missing: ${dir}`
7361
+ });
7362
+ }
7499
7363
  }
7500
- const help = getDetailedHelp(resource, action);
7501
- console.log(help);
7502
- }
7503
- function getResourceDescription(resource) {
7504
- const descriptions = {
7505
- space: "Create and list spaces",
7506
- module: "Install and manage modules",
7507
- config: "View and modify settings",
7508
- nightshift: "Queue and trigger AI tasks",
7509
- cron: "Manage scheduled tasks",
7510
- snapshot: "Create and restore installation snapshots"
7364
+ const requiredFiles = [
7365
+ "org/inbox.org",
7366
+ "org/next_actions.org"
7367
+ ];
7368
+ for (const file of requiredFiles) {
7369
+ if (!existsSync3(join3(spacePath, file))) {
7370
+ issues.push({
7371
+ type: "missing",
7372
+ path: file,
7373
+ message: `Required file missing: ${file}`
7374
+ });
7375
+ }
7376
+ }
7377
+ const recommendedDirs = [
7378
+ "1-tracks",
7379
+ "2-projects",
7380
+ "3-knowledge",
7381
+ "4-archive",
7382
+ ".datacore/learning"
7383
+ ];
7384
+ for (const dir of recommendedDirs) {
7385
+ if (!existsSync3(join3(spacePath, dir))) {
7386
+ issues.push({
7387
+ type: "warning",
7388
+ path: dir,
7389
+ message: `Recommended directory missing: ${dir}`
7390
+ });
7391
+ }
7392
+ }
7393
+ if (!existsSync3(join3(spacePath, "CLAUDE.base.md")) && !existsSync3(join3(spacePath, "CLAUDE.md"))) {
7394
+ issues.push({
7395
+ type: "warning",
7396
+ path: "CLAUDE.base.md",
7397
+ message: "No CLAUDE context file found"
7398
+ });
7399
+ }
7400
+ let status = "healthy";
7401
+ if (issues.some((i) => i.type === "error" || i.type === "missing")) {
7402
+ status = "errors";
7403
+ } else if (issues.some((i) => i.type === "warning")) {
7404
+ status = "warnings";
7405
+ }
7406
+ return {
7407
+ space: spaceName,
7408
+ path: spacePath,
7409
+ issues,
7410
+ status
7511
7411
  };
7512
- return descriptions[resource];
7513
7412
  }
7514
- function getActionDescription(resource, action) {
7515
- const descriptions = {
7516
- "space create": "Create a new space",
7517
- "space list": "List all spaces",
7518
- "module install": "Install a module",
7519
- "module list": "List installed modules",
7520
- "module update": "Update all modules",
7521
- "module remove": "Remove a module",
7522
- "config show": "Show all settings",
7523
- "config get": "Get a setting value",
7524
- "config set": "Set a setting value",
7525
- "nightshift status": "Show queue and server status",
7526
- "nightshift trigger": "Execute queued AI tasks",
7527
- "nightshift queue": "Add task to AI queue",
7528
- "cron install": "Install scheduled jobs",
7529
- "cron status": "Show job status",
7530
- "cron remove": "Remove scheduled jobs",
7531
- "snapshot create": "Create installation snapshot",
7532
- "snapshot restore": "Restore from snapshot",
7533
- "snapshot diff": "Compare with snapshot",
7534
- "snapshot show": "Show snapshot contents"
7535
- };
7536
- return descriptions[`${resource} ${action}`] ?? action;
7413
+ var DATA_DIR2;
7414
+ var init_space = __esm(() => {
7415
+ DATA_DIR2 = join3(process.env.HOME || "~", "Data");
7416
+ });
7417
+
7418
+ // src/lib/agent.ts
7419
+ var exports_agent = {};
7420
+ __export(exports_agent, {
7421
+ listAgents: () => listAgents,
7422
+ invokeAgent: () => invokeAgent,
7423
+ agentExists: () => agentExists
7424
+ });
7425
+ import { spawn as spawn2 } from "child_process";
7426
+ import { existsSync as existsSync6 } from "fs";
7427
+ import { join as join6 } from "path";
7428
+ function commandExists2(cmd) {
7429
+ try {
7430
+ const { execSync: execSync4 } = __require("child_process");
7431
+ execSync4(`which ${cmd}`, { stdio: "pipe" });
7432
+ return true;
7433
+ } catch {
7434
+ return false;
7435
+ }
7537
7436
  }
7538
- function getDetailedHelp(resource, action) {
7539
- const key = `${resource} ${action}`;
7540
- const helpTexts = {
7541
- "space create": `datacore space create - Create a new space
7437
+ async function invokeAgent(invocation, options = {}) {
7438
+ const { stream = false, cwd = DATA_DIR5, timeout = 300000 } = options;
7439
+ if (!commandExists2("claude")) {
7440
+ return {
7441
+ success: false,
7442
+ output: "",
7443
+ error: "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
7444
+ };
7445
+ }
7446
+ if (!existsSync6(cwd)) {
7447
+ return {
7448
+ success: false,
7449
+ output: "",
7450
+ error: `Working directory not found: ${cwd}`
7451
+ };
7452
+ }
7453
+ const prompt = buildAgentPrompt(invocation);
7454
+ return new Promise((resolve) => {
7455
+ const chunks = [];
7456
+ let errorChunks = [];
7457
+ let timedOut = false;
7458
+ const proc = spawn2("claude", ["--print", prompt], {
7459
+ cwd,
7460
+ stdio: ["ignore", "pipe", "pipe"],
7461
+ env: {
7462
+ ...process.env,
7463
+ CI: "true"
7464
+ }
7465
+ });
7466
+ const timer = setTimeout(() => {
7467
+ timedOut = true;
7468
+ proc.kill("SIGTERM");
7469
+ }, timeout);
7470
+ proc.stdout?.on("data", (data) => {
7471
+ const text = data.toString();
7472
+ chunks.push(text);
7473
+ if (stream) {
7474
+ process.stdout.write(text);
7475
+ }
7476
+ });
7477
+ proc.stderr?.on("data", (data) => {
7478
+ errorChunks.push(data.toString());
7479
+ });
7480
+ proc.on("close", (code) => {
7481
+ clearTimeout(timer);
7482
+ const output2 = chunks.join("");
7483
+ const stderr = errorChunks.join("");
7484
+ if (timedOut) {
7485
+ resolve({
7486
+ success: false,
7487
+ output: output2,
7488
+ error: `Agent timed out after ${timeout}ms`
7489
+ });
7490
+ return;
7491
+ }
7492
+ if (code !== 0) {
7493
+ resolve({
7494
+ success: false,
7495
+ output: output2,
7496
+ error: stderr || `Agent exited with code ${code}`
7497
+ });
7498
+ return;
7499
+ }
7500
+ const artifacts = parseArtifacts(output2);
7501
+ resolve({
7502
+ success: true,
7503
+ output: output2,
7504
+ artifacts: Object.keys(artifacts).length > 0 ? artifacts : undefined
7505
+ });
7506
+ });
7507
+ proc.on("error", (err) => {
7508
+ clearTimeout(timer);
7509
+ resolve({
7510
+ success: false,
7511
+ output: chunks.join(""),
7512
+ error: err.message
7513
+ });
7514
+ });
7515
+ });
7516
+ }
7517
+ function buildAgentPrompt(invocation) {
7518
+ const { agent, params } = invocation;
7519
+ const paramLines = Object.entries(params).map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`).join(`
7520
+ `);
7521
+ return `Use the Task tool to invoke the "${agent}" agent with the following parameters:
7542
7522
 
7543
- Usage:
7544
- datacore space create [options]
7523
+ ${paramLines || "(no parameters)"}
7545
7524
 
7546
- Description:
7547
- Interactive wizard to create a new team or personal space.
7548
- Invokes the create-space agent for semantic decisions.
7525
+ Wait for the agent to complete and return its results.`;
7526
+ }
7527
+ function parseArtifacts(output2) {
7528
+ const artifacts = {};
7529
+ const regex = /<!-- ARTIFACT:([^:]+):([^ ]+) -->/g;
7530
+ let match;
7531
+ while ((match = regex.exec(output2)) !== null) {
7532
+ const [, name, path] = match;
7533
+ if (name && path) {
7534
+ artifacts[name] = path;
7535
+ }
7536
+ }
7537
+ return artifacts;
7538
+ }
7539
+ function listAgents() {
7540
+ const registryPath = join6(DATA_DIR5, ".datacore", "registry", "agents.yaml");
7541
+ if (!existsSync6(registryPath)) {
7542
+ return [];
7543
+ }
7544
+ try {
7545
+ const { readFileSync: readFileSync3 } = __require("fs");
7546
+ const { parse: parse2 } = require_dist();
7547
+ const content = readFileSync3(registryPath, "utf-8");
7548
+ const registry = parse2(content);
7549
+ return registry.agents?.map((a) => a.name) || [];
7550
+ } catch {
7551
+ return [];
7552
+ }
7553
+ }
7554
+ function agentExists(name) {
7555
+ return listAgents().includes(name);
7556
+ }
7557
+ var DATA_DIR5;
7558
+ var init_agent = __esm(() => {
7559
+ DATA_DIR5 = join6(process.env.HOME || "~", "Data");
7560
+ });
7549
7561
 
7550
- Options:
7551
- --name <name> Space name (lowercase, hyphenated)
7552
- --type <type> Space type: team or personal
7553
- --yes, -y Use defaults
7562
+ // src/routing.ts
7563
+ var RESOURCES = ["space", "module", "config", "nightshift", "cron", "snapshot"];
7564
+ var META_COMMANDS = [
7565
+ "init",
7566
+ "doctor",
7567
+ "ingest",
7568
+ "sync",
7569
+ "today",
7570
+ "tomorrow",
7571
+ "version"
7572
+ ];
7573
+ var ACTIONS = {
7574
+ space: ["create", "list"],
7575
+ module: ["install", "list", "update", "remove"],
7576
+ config: ["show", "get", "set"],
7577
+ nightshift: ["status", "trigger", "queue"],
7578
+ cron: ["install", "status", "remove"],
7579
+ snapshot: ["create", "restore", "diff", "show"]
7580
+ };
7581
+ function parseArgs(argv) {
7582
+ if (argv.length === 0) {
7583
+ return { type: "help", topic: undefined, subtopic: undefined };
7584
+ }
7585
+ const [first, ...rest] = argv;
7586
+ if (!first) {
7587
+ return { type: "help", topic: undefined, subtopic: undefined };
7588
+ }
7589
+ const helpIndex = argv.findIndex((a) => a === "--help" || a === "-h");
7590
+ if (helpIndex !== -1) {
7591
+ if (helpIndex === 0) {
7592
+ return { type: "help", topic: undefined, subtopic: undefined };
7593
+ }
7594
+ if (helpIndex === 1 && RESOURCES.includes(first)) {
7595
+ return { type: "help", topic: first, subtopic: undefined };
7596
+ }
7597
+ if (helpIndex === 2 && RESOURCES.includes(first)) {
7598
+ return { type: "help", topic: first, subtopic: rest[0] };
7599
+ }
7600
+ return { type: "help", topic: first, subtopic: undefined };
7601
+ }
7602
+ if (first === "--version" || first === "-v") {
7603
+ return { type: "meta", command: "version", args: [], flags: {} };
7604
+ }
7605
+ if (first === "help") {
7606
+ return { type: "help", topic: rest[0], subtopic: rest[1] };
7607
+ }
7608
+ if (META_COMMANDS.includes(first)) {
7609
+ const { args, flags } = parseRest(rest);
7610
+ return { type: "meta", command: first, args, flags };
7611
+ }
7612
+ if (RESOURCES.includes(first)) {
7613
+ const action = rest[0] || "list";
7614
+ const { args, flags } = parseRest(rest.slice(1));
7615
+ return { type: "resource", resource: first, action, args, flags };
7616
+ }
7617
+ return { type: "unknown", command: first };
7618
+ }
7619
+ function parseRest(argv) {
7620
+ const args = [];
7621
+ const flags = {};
7622
+ for (let i = 0;i < argv.length; i++) {
7623
+ const arg = argv[i];
7624
+ if (!arg)
7625
+ continue;
7626
+ if (arg.startsWith("--")) {
7627
+ if (arg.includes("=")) {
7628
+ const [key, value] = arg.slice(2).split("=");
7629
+ if (key)
7630
+ flags[key] = value ?? true;
7631
+ } else {
7632
+ const key = arg.slice(2);
7633
+ const next = argv[i + 1];
7634
+ if (next && !next.startsWith("-")) {
7635
+ flags[key] = next;
7636
+ i++;
7637
+ } else {
7638
+ flags[key] = true;
7639
+ }
7640
+ }
7641
+ } else if (arg.startsWith("-") && arg.length === 2) {
7642
+ const key = arg.slice(1);
7643
+ flags[key] = true;
7644
+ } else {
7645
+ args.push(arg);
7646
+ }
7647
+ }
7648
+ return { args, flags };
7649
+ }
7650
+ function suggestCommand(unknown) {
7651
+ const allCommands = [...META_COMMANDS, ...RESOURCES];
7652
+ for (const cmd of allCommands) {
7653
+ if (cmd.startsWith(unknown) || unknown.startsWith(cmd)) {
7654
+ return cmd;
7655
+ }
7656
+ if (Math.abs(cmd.length - unknown.length) <= 1) {
7657
+ let diff = 0;
7658
+ for (let i = 0;i < Math.max(cmd.length, unknown.length); i++) {
7659
+ if (cmd[i] !== unknown[i])
7660
+ diff++;
7661
+ }
7662
+ if (diff <= 2)
7663
+ return cmd;
7664
+ }
7665
+ }
7666
+ return null;
7667
+ }
7554
7668
 
7555
- Examples:
7556
- datacore space create
7557
- datacore space create --name fds --type team`,
7558
- "space list": `datacore space list - List all spaces
7669
+ // src/help.ts
7670
+ function showHelp() {
7671
+ console.log(`datacore - AI Second Brain Setup & Management CLI
7559
7672
 
7560
- Usage:
7561
- datacore space list [options]
7673
+ Setup and admin tool for Datacore. For daily use, run: cd ~/Data && claude
7562
7674
 
7563
- Description:
7564
- Lists all spaces in your Datacore installation.
7675
+ Usage:
7676
+ datacore <command> [args] [options]
7565
7677
 
7566
- Options:
7567
- --format json Output as JSON
7678
+ Setup:
7679
+ init Set up a new Datacore installation
7680
+ doctor Check dependencies and system status
7681
+ ingest <path> Import files during setup
7568
7682
 
7569
- Examples:
7570
- datacore space list`,
7571
- "module install": `datacore module install - Install a module
7683
+ Admin:
7684
+ space Create and list spaces
7685
+ module Install and manage modules
7686
+ snapshot Create/restore installation snapshots
7687
+ config View and modify settings
7572
7688
 
7573
- Usage:
7574
- datacore module install <name> [options]
7689
+ Automation (for cron jobs):
7690
+ sync Sync all git repos
7691
+ today Generate daily briefing
7692
+ tomorrow End-of-day wrap-up
7693
+ nightshift Queue and trigger AI tasks
7694
+ cron Set up scheduled tasks
7575
7695
 
7576
- Description:
7577
- Clones a module from the Datacore catalog and registers
7578
- its agents and commands.
7696
+ Meta:
7697
+ version Show version
7698
+ help [topic] Show help for a topic
7579
7699
 
7580
- Arguments:
7581
- <name> Module name (e.g., nightshift)
7700
+ Options:
7701
+ --format json|human Output format (auto-detects TTY)
7702
+ --help, -h Show help for any command
7703
+ --yes, -y Skip confirmation prompts
7582
7704
 
7583
7705
  Examples:
7706
+ # Initial setup
7707
+ datacore init
7708
+ datacore ingest ~/Documents/chatgpt-export/
7709
+
7710
+ # Admin
7711
+ datacore snapshot create
7584
7712
  datacore module install nightshift
7585
- datacore module install crm`,
7586
- "module list": `datacore module list - List modules
7587
7713
 
7588
- Usage:
7589
- datacore module list [options]
7714
+ # Then use Claude Code for daily workflow
7715
+ cd ~/Data && claude`);
7716
+ }
7717
+ function showResourceHelp(resource) {
7718
+ if (resource === "init") {
7719
+ showInitHelp();
7720
+ return;
7721
+ }
7722
+ if (resource === "doctor") {
7723
+ showDoctorHelp();
7724
+ return;
7725
+ }
7726
+ if (resource === "ingest") {
7727
+ showIngestHelp();
7728
+ return;
7729
+ }
7730
+ if (resource === "sync") {
7731
+ showSyncHelp();
7732
+ return;
7733
+ }
7734
+ if (resource === "today" || resource === "tomorrow") {
7735
+ showGtdMetaHelp(resource);
7736
+ return;
7737
+ }
7738
+ if (!RESOURCES.includes(resource)) {
7739
+ console.log(`Unknown command: ${resource}
7590
7740
 
7591
- Description:
7592
- Shows installed modules and available modules from catalog.
7741
+ Available commands: init, doctor, ingest, sync, today, tomorrow, space, module, config, nightshift, cron, snapshot
7593
7742
 
7594
- Options:
7595
- --available Show only available (not installed)
7596
- --format json Output as JSON
7743
+ Run 'datacore help' for overview.`);
7744
+ return;
7745
+ }
7746
+ const actions = ACTIONS[resource];
7747
+ console.log(`datacore ${resource} - ${getResourceDescription(resource)}
7597
7748
 
7598
- Examples:
7599
- datacore module list
7600
- datacore module list --available`,
7601
- "config show": `datacore config show - Show all settings
7749
+ Usage:
7750
+ datacore ${resource} <action> [args] [options]
7751
+
7752
+ Actions:`);
7753
+ for (const action of actions) {
7754
+ console.log(` ${action.padEnd(18)} ${getActionDescription(resource, action)}`);
7755
+ }
7756
+ console.log(`
7757
+ For detailed help on an action:
7758
+ datacore ${resource} <action> --help
7759
+ datacore help ${resource} <action>`);
7760
+ }
7761
+ function showInitHelp() {
7762
+ console.log(`datacore init - Set up a new Datacore installation
7602
7763
 
7603
7764
  Usage:
7604
- datacore config show [options]
7765
+ datacore init [options]
7605
7766
 
7606
7767
  Description:
7607
- Displays merged configuration from base and local settings.
7768
+ Interactive wizard that sets up your Datacore second brain:
7769
+ 1. Creates ~/Data directory structure
7770
+ 2. Initializes git repositories
7771
+ 3. Generates CLAUDE.md context files
7772
+ 4. Creates personal space (0-personal)
7773
+ 5. Optionally installs recommended modules
7608
7774
 
7609
7775
  Options:
7610
- --format json Output as JSON
7776
+ --path <dir> Installation directory (default: ~/Data)
7777
+ --yes, -y Use defaults, skip prompts
7778
+ --no-claude Skip Claude Code check
7611
7779
 
7612
7780
  Examples:
7613
- datacore config show`,
7614
- "config get": `datacore config get - Get a setting value
7615
-
7616
- Usage:
7617
- datacore config get <key>
7781
+ # Interactive setup
7782
+ datacore init
7618
7783
 
7619
- Arguments:
7620
- <key> Setting key (e.g., sync.pull_on_today)
7784
+ # Non-interactive with defaults
7785
+ datacore init --yes
7621
7786
 
7622
- Examples:
7623
- datacore config get sync.pull_on_today
7624
- datacore config get editor.open_command`,
7625
- "config set": `datacore config set - Set a setting value
7787
+ # Custom location
7788
+ datacore init --path ~/my-data`);
7789
+ }
7790
+ function showDoctorHelp() {
7791
+ console.log(`datacore doctor - Check dependencies and system status
7626
7792
 
7627
7793
  Usage:
7628
- datacore config set <key> <value>
7794
+ datacore doctor [options]
7629
7795
 
7630
7796
  Description:
7631
- Updates settings.local.yaml with the new value.
7632
-
7633
- Arguments:
7634
- <key> Setting key
7635
- <value> New value
7797
+ Verifies all required dependencies are installed and checks
7798
+ the health of your Datacore installation.
7636
7799
 
7637
- Examples:
7638
- datacore config set sync.pull_on_today false
7639
- datacore config set editor.open_command code`,
7640
- "nightshift status": `datacore nightshift status - Queue and server status
7800
+ Checks:
7801
+ Required:
7802
+ - git Version control
7803
+ - git-lfs Large file support
7804
+ - node >= 18 Node.js runtime
7805
+ - python >= 3.9 Python runtime
7641
7806
 
7642
- Usage:
7643
- datacore nightshift status [options]
7807
+ Recommended:
7808
+ - claude Claude Code CLI for AI features
7644
7809
 
7645
- Description:
7646
- Shows nightshift queue summary and server connection status.
7810
+ Datacore:
7811
+ - ~/Data exists
7812
+ - .datacore/ configured
7813
+ - Spaces detected
7647
7814
 
7648
7815
  Options:
7649
7816
  --format json Output as JSON
7650
7817
 
7651
7818
  Examples:
7652
- datacore nightshift status`,
7653
- "nightshift trigger": `datacore nightshift trigger - Execute queued AI tasks
7819
+ datacore doctor
7820
+ datacore doctor --format json`);
7821
+ }
7822
+ function showIngestHelp() {
7823
+ console.log(`datacore ingest - Import files with AI processing
7654
7824
 
7655
7825
  Usage:
7656
- datacore nightshift trigger
7826
+ datacore ingest <path> [options]
7657
7827
 
7658
7828
  Description:
7659
- Manually triggers execution of queued AI tasks.
7660
- Normally called by cron job at night.
7829
+ Imports files into Datacore with deep knowledge extraction.
7830
+ The CLI validates paths and invokes the ingest-coordinator agent
7831
+ which handles the 6-phase semantic processing:
7661
7832
 
7662
- Examples:
7663
- datacore nightshift trigger`,
7664
- "nightshift queue": `datacore nightshift queue - Add task to AI queue
7833
+ 1. READ - Analyze content
7834
+ 2. ASSESS - Determine destination
7835
+ 3. EXTRACT - Pull out knowledge (zettels, insights)
7836
+ 4. CAPTURE - Log tasks and TODOs
7837
+ 5. FILE - Move to semantic location
7838
+ 6. LINK - Connect to knowledge graph
7665
7839
 
7666
- Usage:
7667
- datacore nightshift queue "<task description>"
7840
+ Options:
7841
+ --space <name> Target space (default: 0-personal)
7842
+ --dry-run Show what would happen without doing it
7668
7843
 
7669
- Description:
7670
- Quick way to add an AI task without editing org files.
7671
- Adds entry to inbox.org with :AI: tag.
7844
+ Examples:
7845
+ # Import a file
7846
+ datacore ingest ~/Documents/report.pdf
7672
7847
 
7673
- Arguments:
7674
- <task> Task description
7848
+ # Import a folder
7849
+ datacore ingest ~/Documents/export/
7675
7850
 
7676
- Examples:
7677
- datacore nightshift queue "Research competitor analysis"
7678
- datacore nightshift queue "Generate weekly metrics report"`,
7679
- "cron install": `datacore cron install - Install scheduled jobs
7851
+ # Target specific space
7852
+ datacore ingest ~/Documents/contracts/ --space 1-datafund`);
7853
+ }
7854
+ function showSyncHelp() {
7855
+ console.log(`datacore sync - Git sync for all repos
7680
7856
 
7681
7857
  Usage:
7682
- datacore cron install [options]
7858
+ datacore sync [action] [options]
7859
+
7860
+ Actions:
7861
+ (none) Pull all repos (default)
7862
+ push Commit and push changes
7863
+ status Show git status overview
7683
7864
 
7684
7865
  Description:
7685
- Sets up platform-specific scheduled tasks:
7686
- - macOS: launchd plist
7687
- - Linux: crontab entry
7866
+ Manages git synchronization for the root Datacore repo
7867
+ and all space repositories.
7688
7868
 
7689
7869
  Options:
7690
- --schedule <cron> Custom schedule (default: nightly)
7870
+ --message <msg> Commit message (for push)
7691
7871
 
7692
7872
  Examples:
7693
- datacore cron install
7694
- datacore cron install --schedule "0 2 * * *"`,
7695
- "snapshot create": `datacore snapshot create - Create installation snapshot
7873
+ # Pull latest changes
7874
+ datacore sync
7875
+
7876
+ # Push changes
7877
+ datacore sync push --message "Daily update"
7878
+
7879
+ # Check status
7880
+ datacore sync status`);
7881
+ }
7882
+ function showGtdMetaHelp(command) {
7883
+ if (command === "today") {
7884
+ console.log(`datacore today - Generate daily briefing
7696
7885
 
7697
7886
  Usage:
7698
- datacore snapshot create [path] [options]
7887
+ datacore today [options]
7699
7888
 
7700
7889
  Description:
7701
- Creates a snapshot of your Datacore installation including:
7702
- - Installed modules (with git commit hashes)
7703
- - Spaces (with git sources)
7890
+ Generates your daily briefing by invoking the /today command.
7891
+ Includes:
7892
+ - Calendar events
7893
+ - Priority tasks
7894
+ - Nightshift results (completed AI tasks)
7895
+ - Journal entry
7896
+
7897
+ Options:
7898
+ --format json Output as JSON
7899
+
7900
+ Examples:
7901
+ datacore today`);
7902
+ } else {
7903
+ console.log(`datacore tomorrow - End-of-day wrap-up
7904
+
7905
+ Usage:
7906
+ datacore tomorrow [options]
7907
+
7908
+ Description:
7909
+ Wraps up your day by invoking the /tomorrow command.
7910
+ Includes:
7911
+ - Session summary
7912
+ - Queuing AI tasks for overnight
7913
+ - Learning extraction
7914
+ - Journal update
7915
+
7916
+ Options:
7917
+ --format json Output as JSON
7918
+
7919
+ Examples:
7920
+ datacore tomorrow`);
7921
+ }
7922
+ }
7923
+ function showActionHelp(resource, action) {
7924
+ if (!RESOURCES.includes(resource)) {
7925
+ console.log(`Unknown resource: ${resource}`);
7926
+ return;
7927
+ }
7928
+ const actions = ACTIONS[resource];
7929
+ if (!actions.includes(action)) {
7930
+ console.log(`Unknown action: ${action} for ${resource}
7931
+
7932
+ Available actions: ${actions.join(", ")}`);
7933
+ return;
7934
+ }
7935
+ const help = getDetailedHelp(resource, action);
7936
+ console.log(help);
7937
+ }
7938
+ function getResourceDescription(resource) {
7939
+ const descriptions = {
7940
+ space: "Create and list spaces",
7941
+ module: "Install and manage modules",
7942
+ config: "View and modify settings",
7943
+ nightshift: "Queue and trigger AI tasks",
7944
+ cron: "Manage scheduled tasks",
7945
+ snapshot: "Create and restore installation snapshots"
7946
+ };
7947
+ return descriptions[resource];
7948
+ }
7949
+ function getActionDescription(resource, action) {
7950
+ const descriptions = {
7951
+ "space create": "Create a new space",
7952
+ "space list": "List all spaces",
7953
+ "module install": "Install a module",
7954
+ "module list": "List installed modules",
7955
+ "module update": "Update all modules",
7956
+ "module remove": "Remove a module",
7957
+ "config show": "Show all settings",
7958
+ "config get": "Get a setting value",
7959
+ "config set": "Set a setting value",
7960
+ "nightshift status": "Show queue and server status",
7961
+ "nightshift trigger": "Execute queued AI tasks",
7962
+ "nightshift queue": "Add task to AI queue",
7963
+ "cron install": "Install scheduled jobs",
7964
+ "cron status": "Show job status",
7965
+ "cron remove": "Remove scheduled jobs",
7966
+ "snapshot create": "Create installation snapshot",
7967
+ "snapshot restore": "Restore from snapshot",
7968
+ "snapshot diff": "Compare with snapshot",
7969
+ "snapshot show": "Show snapshot contents"
7970
+ };
7971
+ return descriptions[`${resource} ${action}`] ?? action;
7972
+ }
7973
+ function getDetailedHelp(resource, action) {
7974
+ const key = `${resource} ${action}`;
7975
+ const helpTexts = {
7976
+ "space create": `datacore space create - Create a new space
7977
+
7978
+ Usage:
7979
+ datacore space create [options]
7980
+
7981
+ Description:
7982
+ Interactive wizard to create a new team or personal space.
7983
+ Invokes the create-space agent for semantic decisions.
7984
+
7985
+ Options:
7986
+ --name <name> Space name (lowercase, hyphenated)
7987
+ --type <type> Space type: team or personal
7988
+ --yes, -y Use defaults
7989
+
7990
+ Examples:
7991
+ datacore space create
7992
+ datacore space create --name fds --type team`,
7993
+ "space list": `datacore space list - List all spaces
7994
+
7995
+ Usage:
7996
+ datacore space list [options]
7997
+
7998
+ Description:
7999
+ Lists all spaces in your Datacore installation.
8000
+
8001
+ Options:
8002
+ --format json Output as JSON
8003
+
8004
+ Examples:
8005
+ datacore space list`,
8006
+ "module install": `datacore module install - Install a module
8007
+
8008
+ Usage:
8009
+ datacore module install <name> [options]
8010
+
8011
+ Description:
8012
+ Clones a module from the Datacore catalog and registers
8013
+ its agents and commands.
8014
+
8015
+ Arguments:
8016
+ <name> Module name (e.g., nightshift)
8017
+
8018
+ Examples:
8019
+ datacore module install nightshift
8020
+ datacore module install crm`,
8021
+ "module list": `datacore module list - List modules
8022
+
8023
+ Usage:
8024
+ datacore module list [options]
8025
+
8026
+ Description:
8027
+ Shows installed modules and available modules from catalog.
8028
+
8029
+ Options:
8030
+ --available Show only available (not installed)
8031
+ --format json Output as JSON
8032
+
8033
+ Examples:
8034
+ datacore module list
8035
+ datacore module list --available`,
8036
+ "config show": `datacore config show - Show all settings
8037
+
8038
+ Usage:
8039
+ datacore config show [options]
8040
+
8041
+ Description:
8042
+ Displays merged configuration from base and local settings.
8043
+
8044
+ Options:
8045
+ --format json Output as JSON
8046
+
8047
+ Examples:
8048
+ datacore config show`,
8049
+ "config get": `datacore config get - Get a setting value
8050
+
8051
+ Usage:
8052
+ datacore config get <key>
8053
+
8054
+ Arguments:
8055
+ <key> Setting key (e.g., sync.pull_on_today)
8056
+
8057
+ Examples:
8058
+ datacore config get sync.pull_on_today
8059
+ datacore config get editor.open_command`,
8060
+ "config set": `datacore config set - Set a setting value
8061
+
8062
+ Usage:
8063
+ datacore config set <key> <value>
8064
+
8065
+ Description:
8066
+ Updates settings.local.yaml with the new value.
8067
+
8068
+ Arguments:
8069
+ <key> Setting key
8070
+ <value> New value
8071
+
8072
+ Examples:
8073
+ datacore config set sync.pull_on_today false
8074
+ datacore config set editor.open_command code`,
8075
+ "nightshift status": `datacore nightshift status - Queue and server status
8076
+
8077
+ Usage:
8078
+ datacore nightshift status [options]
8079
+
8080
+ Description:
8081
+ Shows nightshift queue summary and server connection status.
8082
+
8083
+ Options:
8084
+ --format json Output as JSON
8085
+
8086
+ Examples:
8087
+ datacore nightshift status`,
8088
+ "nightshift trigger": `datacore nightshift trigger - Execute queued AI tasks
8089
+
8090
+ Usage:
8091
+ datacore nightshift trigger
8092
+
8093
+ Description:
8094
+ Manually triggers execution of queued AI tasks.
8095
+ Normally called by cron job at night.
8096
+
8097
+ Examples:
8098
+ datacore nightshift trigger`,
8099
+ "nightshift queue": `datacore nightshift queue - Add task to AI queue
8100
+
8101
+ Usage:
8102
+ datacore nightshift queue "<task description>"
8103
+
8104
+ Description:
8105
+ Quick way to add an AI task without editing org files.
8106
+ Adds entry to inbox.org with :AI: tag.
8107
+
8108
+ Arguments:
8109
+ <task> Task description
8110
+
8111
+ Examples:
8112
+ datacore nightshift queue "Research competitor analysis"
8113
+ datacore nightshift queue "Generate weekly metrics report"`,
8114
+ "cron install": `datacore cron install - Install scheduled jobs
8115
+
8116
+ Usage:
8117
+ datacore cron install [options]
8118
+
8119
+ Description:
8120
+ Sets up platform-specific scheduled tasks:
8121
+ - macOS: launchd plist
8122
+ - Linux: crontab entry
8123
+
8124
+ Options:
8125
+ --schedule <cron> Custom schedule (default: nightly)
8126
+
8127
+ Examples:
8128
+ datacore cron install
8129
+ datacore cron install --schedule "0 2 * * *"`,
8130
+ "snapshot create": `datacore snapshot create - Create installation snapshot
8131
+
8132
+ Usage:
8133
+ datacore snapshot create [path] [options]
8134
+
8135
+ Description:
8136
+ Creates a snapshot of your Datacore installation including:
8137
+ - Installed modules (with git commit hashes)
8138
+ - Spaces (with git sources)
7704
8139
  - Dependency versions
7705
8140
  - Optionally: base settings
7706
8141
 
@@ -7961,6 +8396,13 @@ function getInstallCommand(pkg, platform2) {
7961
8396
  windows: "winget install Python.Python.3.11",
7962
8397
  unknown: null
7963
8398
  },
8399
+ gh: {
8400
+ macos: "brew install gh",
8401
+ linux: "sudo apt-get install gh",
8402
+ wsl: "sudo apt-get install gh",
8403
+ windows: "winget install GitHub.cli",
8404
+ unknown: null
8405
+ },
7964
8406
  claude: {
7965
8407
  macos: "npm install -g @anthropic-ai/claude-code",
7966
8408
  linux: "npm install -g @anthropic-ai/claude-code",
@@ -8096,11 +8538,28 @@ function checkPython(platform2) {
8096
8538
  installCommand: !installed || !meetsMin ? getInstallCommand("python", platform2) ?? undefined : undefined
8097
8539
  };
8098
8540
  }
8099
- function checkClaude(platform2) {
8100
- const installed = commandExists("claude");
8101
- return {
8102
- name: "claude",
8103
- required: false,
8541
+ function checkGh(platform2) {
8542
+ const installed = commandExists("gh");
8543
+ let authenticated = false;
8544
+ if (installed) {
8545
+ try {
8546
+ execSync2("gh auth status", { stdio: "pipe" });
8547
+ authenticated = true;
8548
+ } catch {}
8549
+ }
8550
+ return {
8551
+ name: "gh",
8552
+ required: false,
8553
+ installed: installed && authenticated,
8554
+ version: installed ? getVersion("gh") : undefined,
8555
+ installCommand: installed ? authenticated ? undefined : "gh auth login" : getInstallCommand("gh", platform2) ?? undefined
8556
+ };
8557
+ }
8558
+ function checkClaude(platform2) {
8559
+ const installed = commandExists("claude");
8560
+ return {
8561
+ name: "claude",
8562
+ required: false,
8104
8563
  installed,
8105
8564
  version: installed ? getVersion("claude", "--version") : undefined,
8106
8565
  installCommand: installed ? undefined : getInstallCommand("claude", platform2) ?? undefined
@@ -8113,6 +8572,7 @@ function checkDependencies() {
8113
8572
  checkGitLfs(platform2),
8114
8573
  checkNode(platform2),
8115
8574
  checkPython(platform2),
8575
+ checkGh(platform2),
8116
8576
  checkClaude(platform2)
8117
8577
  ];
8118
8578
  }
@@ -8305,351 +8765,21 @@ function getAllConfig() {
8305
8765
  }
8306
8766
  if (check !== undefined) {
8307
8767
  sources[pathStr] = "base";
8308
- continue;
8309
- }
8310
- sources[pathStr] = "default";
8311
- }
8312
- }
8313
- }
8314
- traceSources(merged);
8315
- return { merged, sources };
8316
- }
8317
-
8318
- // src/lib/space.ts
8319
- import { existsSync as existsSync3, readdirSync, statSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
8320
- import { join as join3, basename } from "path";
8321
- var DATA_DIR2 = join3(process.env.HOME || "~", "Data");
8322
- function listSpaces() {
8323
- if (!existsSync3(DATA_DIR2)) {
8324
- return [];
8325
- }
8326
- const entries = readdirSync(DATA_DIR2, { withFileTypes: true });
8327
- const spaces = [];
8328
- for (const entry of entries) {
8329
- if (!entry.isDirectory())
8330
- continue;
8331
- const match = entry.name.match(/^(\d+)-(.+)$/);
8332
- if (!match)
8333
- continue;
8334
- const [, numStr, name] = match;
8335
- const number = parseInt(numStr, 10);
8336
- const path = join3(DATA_DIR2, entry.name);
8337
- spaces.push({
8338
- name: entry.name,
8339
- number,
8340
- path,
8341
- type: number === 0 ? "personal" : "team",
8342
- hasGit: existsSync3(join3(path, ".git")),
8343
- hasClaude: existsSync3(join3(path, "CLAUDE.md")) || existsSync3(join3(path, "CLAUDE.base.md"))
8344
- });
8345
- }
8346
- spaces.sort((a, b) => a.number - b.number);
8347
- return spaces;
8348
- }
8349
- function getNextSpaceNumber() {
8350
- const spaces = listSpaces();
8351
- if (spaces.length === 0)
8352
- return 0;
8353
- const maxNumber = Math.max(...spaces.map((s) => s.number));
8354
- return maxNumber + 1;
8355
- }
8356
- function createSpace(name, type = "team") {
8357
- const number = type === "personal" ? 0 : getNextSpaceNumber();
8358
- const normalizedName = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
8359
- const folderName = `${number}-${normalizedName}`;
8360
- const spacePath = join3(DATA_DIR2, folderName);
8361
- if (existsSync3(spacePath)) {
8362
- throw new Error(`Space already exists: ${folderName}`);
8363
- }
8364
- mkdirSync2(spacePath, { recursive: true });
8365
- const dirs = type === "personal" ? [
8366
- ".datacore",
8367
- ".datacore/commands",
8368
- ".datacore/agents",
8369
- ".datacore/learning",
8370
- ".datacore/state",
8371
- ".datacore/env",
8372
- "org",
8373
- "0-inbox",
8374
- "notes",
8375
- "notes/journals",
8376
- "notes/pages",
8377
- "notes/zettel",
8378
- "journal",
8379
- "3-knowledge",
8380
- "3-knowledge/pages",
8381
- "3-knowledge/zettel",
8382
- "3-knowledge/literature",
8383
- "3-knowledge/reference",
8384
- "4-archive",
8385
- "content"
8386
- ] : [
8387
- ".datacore",
8388
- ".datacore/commands",
8389
- ".datacore/agents",
8390
- ".datacore/learning",
8391
- ".datacore/state",
8392
- ".datacore/env",
8393
- "org",
8394
- "0-inbox",
8395
- "journal",
8396
- "1-tracks",
8397
- "1-tracks/ops",
8398
- "1-tracks/product",
8399
- "1-tracks/dev",
8400
- "1-tracks/research",
8401
- "1-tracks/comms",
8402
- "2-projects",
8403
- "3-knowledge",
8404
- "3-knowledge/pages",
8405
- "3-knowledge/zettel",
8406
- "3-knowledge/literature",
8407
- "3-knowledge/reference",
8408
- "4-archive"
8409
- ];
8410
- for (const dir of dirs) {
8411
- mkdirSync2(join3(spacePath, dir), { recursive: true });
8412
- }
8413
- if (type === "personal") {
8414
- writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
8415
- #+FILETAGS: :inbox:
8416
-
8417
- Capture everything here. Process daily to zero.
8418
-
8419
- * TODO Do more. With less.
8420
- * Inbox
8421
- `);
8422
- writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
8423
- #+TODO: TODO NEXT WAITING | DONE CANCELED
8424
- #+FILETAGS: :tasks:
8425
-
8426
- Tasks organized by focus area. Tag with :AI: to delegate to agents.
8427
-
8428
- * TIER 1: STRATEGIC FOUNDATION
8429
- ** /Projects
8430
- ** /Work
8431
- * TIER 2: SUPPORTING WORK
8432
- ** Admin
8433
- ** Maintenance
8434
- * PERSONAL: LIFE & DEVELOPMENT
8435
- ** /Personal Development
8436
- ** /Health & Longevity
8437
- ** Home & Family
8438
- ** Financial Management
8439
- * RESEARCH & LEARNING
8440
- ** Technology
8441
- ** Skills
8442
- `);
8443
- writeFileSync2(join3(spacePath, "org", "nightshift.org"), `#+TITLE: Nightshift Queue
8444
- #+TODO: QUEUED EXECUTING | DONE FAILED
8445
- #+FILETAGS: :nightshift:
8446
-
8447
- AI tasks queued for overnight execution. Managed by /tomorrow command.
8448
-
8449
- * Queue
8450
- `);
8451
- writeFileSync2(join3(spacePath, "org", "habits.org"), `#+TITLE: Habits
8452
- #+FILETAGS: :habits:
8453
-
8454
- Recurring behaviors and routines. Track with org-habit.
8455
-
8456
- * Daily
8457
- * Weekly
8458
- * Monthly
8459
- `);
8460
- writeFileSync2(join3(spacePath, "org", "someday.org"), `#+TITLE: Someday/Maybe
8461
- #+FILETAGS: :someday:
8462
-
8463
- Ideas and projects for the future. Review monthly.
8464
-
8465
- * Someday
8466
- * Maybe
8467
- `);
8468
- writeFileSync2(join3(spacePath, "org", "archive.org"), `#+TITLE: Archive
8469
- #+FILETAGS: :archive:
8470
-
8471
- Completed and canceled tasks. Searchable history.
8472
-
8473
- * Archived Tasks
8474
- `);
8475
- } else {
8476
- writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
8477
- #+FILETAGS: :inbox:
8478
-
8479
- * Capture items here
8480
- `);
8481
- writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
8482
- #+FILETAGS: :tasks:
8483
-
8484
- * Tasks
8485
- ** TODO items go here
8486
- `);
8487
- }
8488
- if (type === "personal") {
8489
- writeFileSync2(join3(spacePath, "_index.md"), `# Personal Space
8490
-
8491
- Your personal knowledge base and GTD system.
8492
-
8493
- ## GTD Workflow
8494
-
8495
- 1. **Capture** → \`org/inbox.org\` - dump everything here
8496
- 2. **Clarify** → Is it actionable? What's the next action?
8497
- 3. **Organize** → Move to \`next_actions.org\` by focus area
8498
- 4. **Reflect** → Weekly review, monthly strategic
8499
- 5. **Engage** → Do the work, delegate with :AI: tags
8500
-
8501
- ## Org Files (GTD)
8502
-
8503
- | File | Purpose |
8504
- |------|---------|
8505
- | [inbox.org](org/inbox.org) | Single capture point - process to zero daily |
8506
- | [next_actions.org](org/next_actions.org) | Active tasks by focus area |
8507
- | [nightshift.org](org/nightshift.org) | AI task queue (overnight processing) |
8508
- | [habits.org](org/habits.org) | Recurring behaviors |
8509
- | [someday.org](org/someday.org) | Future possibilities |
8510
- | [archive.org](org/archive.org) | Completed/canceled tasks |
8511
-
8512
- ## Knowledge
8513
-
8514
- | Folder | Purpose |
8515
- |--------|---------|
8516
- | [notes/](notes/) | Personal knowledge base (Obsidian) |
8517
- | [notes/journals/](notes/journals/) | Daily personal journals |
8518
- | [3-knowledge/](3-knowledge/) | Structured knowledge (zettelkasten) |
8519
-
8520
- ## Other
8521
-
8522
- - [journal/](journal/) - AI session journals (auto-generated)
8523
- - [content/](content/) - Generated content (drafts, emails)
8524
- - [0-inbox/](0-inbox/) - File inbox (process files here)
8525
- - [4-archive/](4-archive/) - Historical content
8526
- `);
8527
- } else {
8528
- writeFileSync2(join3(spacePath, "_index.md"), `# ${name}
8529
-
8530
- Team space.
8531
-
8532
- ## Structure
8533
-
8534
- - \`org/\` - Task coordination
8535
- - \`1-tracks/\` - Department work
8536
- - \`2-projects/\` - Code repositories
8537
- - \`3-knowledge/\` - Shared knowledge
8538
- - \`4-archive/\` - Historical content
8539
-
8540
- ## Quick Links
8541
-
8542
- - [Inbox](org/inbox.org)
8543
- - [Tasks](org/next_actions.org)
8544
- - [Journal](journal/)
8545
- - [Knowledge](3-knowledge/)
8546
- `);
8547
- }
8548
- if (type === "personal") {
8549
- writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# Personal Space
8550
-
8551
- Personal GTD system and knowledge base (per DIP-0009).
8552
-
8553
- ## GTD Files
8554
-
8555
- | File | Purpose | Process |
8556
- |------|---------|---------|
8557
- | \`org/inbox.org\` | Single capture point | Process to zero daily |
8558
- | \`org/next_actions.org\` | Active tasks by focus area | Work from here |
8559
- | \`org/nightshift.org\` | AI task queue | Auto-managed by /tomorrow |
8560
- | \`org/habits.org\` | Recurring behaviors | Track daily |
8561
- | \`org/someday.org\` | Future possibilities | Review monthly |
8562
- | \`org/archive.org\` | Completed tasks | Searchable history |
8563
-
8564
- ## Task States
8565
-
8566
- | State | Meaning |
8567
- |-------|---------|
8568
- | \`TODO\` | Standard next action |
8569
- | \`NEXT\` | High priority, work today |
8570
- | \`WAITING\` | Blocked on external |
8571
- | \`DONE\` | Completed (terminal) |
8572
- | \`CANCELED\` | Will not do (terminal) |
8573
-
8574
- ## AI Delegation
8575
-
8576
- Tag tasks with \`:AI:\` to delegate to agents:
8577
-
8578
- | Tag | Agent | Autonomous |
8579
- |-----|-------|------------|
8580
- | \`:AI:content:\` | gtd-content-writer | Yes |
8581
- | \`:AI:research:\` | gtd-research-processor | Yes |
8582
- | \`:AI:data:\` | gtd-data-analyzer | Yes |
8583
- | \`:AI:pm:\` | gtd-project-manager | Yes |
8584
-
8585
- ## Knowledge Structure
8586
-
8587
- | Location | Purpose |
8588
- |----------|---------|
8589
- | \`notes/\` | Personal knowledge base (Obsidian) |
8590
- | \`notes/journals/\` | Daily personal reflections |
8591
- | \`3-knowledge/zettel/\` | Atomic concept notes |
8592
- | \`3-knowledge/literature/\` | Source summaries |
8593
-
8594
- ## Daily Workflow
8595
-
8596
- 1. **Morning** - Run \`/today\` for briefing
8597
- 2. **Capture** - Everything to inbox.org
8598
- 3. **Process** - Clear inbox, route tasks
8599
- 4. **Work** - Focus on NEXT items
8600
- 5. **Evening** - Run \`/tomorrow\` for wrap-up
8601
-
8602
- ## See Also
8603
-
8604
- Parent: ~/Data/CLAUDE.md
8605
- `);
8606
- } else {
8607
- writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# ${name} Space
8608
-
8609
- Team space for ${name}.
8610
-
8611
- ## Structure
8612
-
8613
- See parent CLAUDE.md for full documentation.
8614
- `);
8768
+ continue;
8769
+ }
8770
+ sources[pathStr] = "default";
8771
+ }
8772
+ }
8615
8773
  }
8616
- writeFileSync2(join3(spacePath, ".datacore", "config.yaml"), `# Space configuration
8617
- name: ${normalizedName}
8618
- type: ${type}
8619
- `);
8620
- writeFileSync2(join3(spacePath, ".datacore", "learning", "patterns.md"), `# Patterns
8621
-
8622
- Successful approaches to remember.
8623
- `);
8624
- writeFileSync2(join3(spacePath, ".datacore", "learning", "corrections.md"), `# Corrections
8625
-
8626
- Human feedback log.
8627
- `);
8628
- writeFileSync2(join3(spacePath, ".datacore", "learning", "preferences.md"), `# Preferences
8629
-
8630
- Style and preference notes.
8631
- `);
8632
- writeFileSync2(join3(spacePath, ".gitignore"), type === "personal" ? `.datacore/state/
8633
- .datacore/env/
8634
- CLAUDE.md
8635
- CLAUDE.local.md
8636
- ` : `.datacore/state/
8637
- .datacore/env/
8638
- CLAUDE.md
8639
- CLAUDE.local.md
8640
- 2-projects/
8641
- `);
8642
- return {
8643
- name: folderName,
8644
- number,
8645
- path: spacePath,
8646
- type,
8647
- hasGit: false,
8648
- hasClaude: true
8649
- };
8774
+ traceSources(merged);
8775
+ return { merged, sources };
8650
8776
  }
8651
8777
 
8778
+ // src/index.ts
8779
+ init_space();
8780
+
8652
8781
  // src/lib/sync.ts
8782
+ init_space();
8653
8783
  import { execSync as execSync3 } from "child_process";
8654
8784
  import { existsSync as existsSync4 } from "fs";
8655
8785
  import { join as join4, basename as basename2 } from "path";
@@ -8784,46 +8914,89 @@ function getGitRepos() {
8784
8914
  }
8785
8915
 
8786
8916
  // src/lib/init.ts
8787
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, symlinkSync } from "fs";
8788
- import { join as join7 } from "path";
8917
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, symlinkSync, copyFileSync, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2, cpSync } from "fs";
8918
+ import { join as join10, basename as basename5 } from "path";
8919
+ import { execFileSync as execFileSync2 } from "child_process";
8789
8920
  import { createInterface } from "readline";
8790
8921
 
8791
8922
  // src/lib/module.ts
8792
8923
  import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
8793
8924
  import { join as join5, basename as basename3 } from "path";
8794
- import { execSync as execSync4 } from "child_process";
8795
- var DATA_DIR4 = join5(process.env.HOME || "~", "Data");
8925
+ import { execFileSync } from "child_process";
8926
+ var DATA_DIR4 = join5(process.env.HOME || "", "Data");
8796
8927
  var MODULES_DIR = join5(DATA_DIR4, ".datacore", "modules");
8797
8928
  var AVAILABLE_MODULES = [
8798
8929
  {
8799
8930
  name: "nightshift",
8800
- description: "Overnight AI task execution",
8801
- repo: "https://github.com/datacore-one/module-nightshift",
8802
- features: ["Queues :AI: tasks for overnight processing", "Morning briefing with results", "Remote server support"]
8931
+ description: "Autonomous AI task execution (local mode)",
8932
+ repo: "https://github.com/datacore-one/datacore-nightshift",
8933
+ features: ["Queues :AI: tasks for background processing", "Morning briefing with results", "Multi-persona evaluation"],
8934
+ core: true
8935
+ },
8936
+ {
8937
+ name: "health",
8938
+ description: "Health and wellness tracking - sleep, exercise, habits",
8939
+ repo: "https://github.com/datacore-one/datacore-health",
8940
+ features: ["Daily check-ins", "Habit tracking and streaks", "Health reports and correlations"],
8941
+ core: false
8803
8942
  },
8804
8943
  {
8805
8944
  name: "crm",
8806
- description: "Contact relationship management",
8807
- repo: "https://github.com/datacore-one/module-crm",
8808
- features: ["Contact profiles", "Interaction history", "Relationship tracking"]
8945
+ description: "Network intelligence and contact management",
8946
+ repo: "https://github.com/datacore-one/datacore-crm",
8947
+ features: ["Contact profiles and interaction history", "Relationship tracking", "Industry landscape mapping"],
8948
+ core: false
8809
8949
  },
8810
8950
  {
8811
8951
  name: "meetings",
8812
- description: "Meeting preparation and follow-up",
8813
- repo: "https://github.com/datacore-one/module-meetings",
8814
- features: ["Pre-meeting briefs", "Transcript processing", "Action item extraction"]
8952
+ description: "Meeting lifecycle automation",
8953
+ repo: "https://github.com/datacore-one/datacore-meetings",
8954
+ features: ["Pre-meeting briefs", "Transcript processing", "Action item extraction"],
8955
+ core: false
8815
8956
  },
8816
8957
  {
8817
- name: "health",
8818
- description: "Personal health and wellness tracking",
8819
- repo: "https://github.com/datacore-one/module-health",
8820
- features: ["Health metrics", "Habit tracking", "Wellness insights"]
8958
+ name: "mail",
8959
+ description: "Email integration and processing",
8960
+ repo: "https://github.com/datacore-one/datacore-mail",
8961
+ features: ["Gmail adapter", "AI classification and routing", "Automated processing"],
8962
+ core: false
8963
+ },
8964
+ {
8965
+ name: "news",
8966
+ description: "Automated news aggregation with AI-scored relevance",
8967
+ repo: "https://github.com/datacore-one/datacore-news",
8968
+ features: ["Multi-source aggregation", "AI relevance scoring", "Tiered processing"],
8969
+ core: false
8970
+ },
8971
+ {
8972
+ name: "slides",
8973
+ description: "Presentation generation via Gamma.app",
8974
+ repo: "https://github.com/datacore-one/datacore-slides",
8975
+ features: ["Presentations via Gamma.app", "AI-powered backgrounds", "Template support"],
8976
+ core: false
8977
+ },
8978
+ {
8979
+ name: "trading",
8980
+ description: "Position management and trading workflows",
8981
+ repo: "https://github.com/datacore-one/datacore-trading",
8982
+ features: ["Position tracking", "Performance analytics", "Risk management"],
8983
+ core: false
8984
+ },
8985
+ {
8986
+ name: "telegram",
8987
+ description: "Mobile access to Claude Code via Telegram",
8988
+ repo: "https://github.com/datacore-one/datacore-telegram",
8989
+ features: ["Message relay", "Command execution", "File sharing"],
8990
+ core: false
8991
+ },
8992
+ {
8993
+ name: "campaigns",
8994
+ description: "Landing pages, deployment, and A/B testing",
8995
+ repo: "https://github.com/datacore-one/datacore-campaigns",
8996
+ features: ["Landing page generation", "PostHog analytics", "A/B testing"],
8997
+ core: false
8821
8998
  }
8822
8999
  ];
8823
- function getAvailableModules() {
8824
- const installed = listModules().map((m) => m.name);
8825
- return AVAILABLE_MODULES.filter((m) => !installed.includes(m.name));
8826
- }
8827
9000
  function listModules() {
8828
9001
  if (!existsSync5(MODULES_DIR)) {
8829
9002
  return [];
@@ -8869,108 +9042,362 @@ function getModuleInfo(modulePath) {
8869
9042
  }
8870
9043
  }
8871
9044
  }
8872
- const commandsDir = join5(modulePath, "commands");
8873
- const commands = [];
8874
- if (existsSync5(commandsDir)) {
8875
- const cmdFiles = readdirSync2(commandsDir);
8876
- for (const file of cmdFiles) {
8877
- if (file.endsWith(".md")) {
8878
- commands.push(file.replace(".md", ""));
8879
- }
9045
+ const commandsDir = join5(modulePath, "commands");
9046
+ const commands = [];
9047
+ if (existsSync5(commandsDir)) {
9048
+ const cmdFiles = readdirSync2(commandsDir);
9049
+ for (const file of cmdFiles) {
9050
+ if (file.endsWith(".md")) {
9051
+ commands.push(file.replace(".md", ""));
9052
+ }
9053
+ }
9054
+ }
9055
+ return {
9056
+ name,
9057
+ path: modulePath,
9058
+ version,
9059
+ description,
9060
+ agents,
9061
+ commands
9062
+ };
9063
+ }
9064
+ function installModule(source) {
9065
+ if (!existsSync5(MODULES_DIR)) {
9066
+ const { mkdirSync: mkdirSync3 } = __require("fs");
9067
+ mkdirSync3(MODULES_DIR, { recursive: true });
9068
+ }
9069
+ let name;
9070
+ let isGit = false;
9071
+ if (source.includes("github.com") || source.startsWith("git@") || source.endsWith(".git")) {
9072
+ isGit = true;
9073
+ const match = source.match(/([^/]+?)(?:\.git)?$/);
9074
+ name = match?.[1] ?? source.split("/").pop() ?? "unknown";
9075
+ name = name.replace(/^module-/, "").replace(/^datacore-/, "");
9076
+ } else if (source.startsWith("@")) {
9077
+ name = source.split("/").pop()?.replace(/^datacore-/, "") ?? "unknown";
9078
+ } else {
9079
+ name = source.replace(/^datacore-/, "").replace(/^module-/, "");
9080
+ }
9081
+ const modulePath = join5(MODULES_DIR, name);
9082
+ if (existsSync5(modulePath)) {
9083
+ throw new Error(`Module already installed: ${name}`);
9084
+ }
9085
+ if (isGit) {
9086
+ try {
9087
+ execFileSync("git", ["clone", source, modulePath], { stdio: "pipe", timeout: 300000 });
9088
+ } catch {
9089
+ const sshUrl = source.replace("https://github.com/", "git@github.com:");
9090
+ try {
9091
+ execFileSync("git", ["clone", sshUrl, modulePath], { stdio: "pipe", timeout: 300000 });
9092
+ } catch {
9093
+ throw new Error(`Could not clone module: ${source} (tried HTTPS and SSH)`);
9094
+ }
9095
+ }
9096
+ } else {
9097
+ const catalogEntry = AVAILABLE_MODULES.find((m) => m.name === name);
9098
+ if (catalogEntry) {
9099
+ return installModule(catalogEntry.repo);
9100
+ }
9101
+ const urls = [
9102
+ `https://github.com/datacore-one/datacore-${name}`,
9103
+ `https://github.com/datacore-one/module-${name}`
9104
+ ];
9105
+ let installed = false;
9106
+ for (const url of urls) {
9107
+ try {
9108
+ execFileSync("git", ["clone", url, modulePath], { stdio: "pipe", timeout: 300000 });
9109
+ installed = true;
9110
+ break;
9111
+ } catch {
9112
+ continue;
9113
+ }
9114
+ }
9115
+ if (!installed) {
9116
+ throw new Error(`Could not install module "${source}". Try providing a full git URL.`);
9117
+ }
9118
+ }
9119
+ const info2 = getModuleInfo(modulePath);
9120
+ if (!info2) {
9121
+ rmSync(modulePath, { recursive: true, force: true });
9122
+ throw new Error("Invalid module: missing module.yaml");
9123
+ }
9124
+ return info2;
9125
+ }
9126
+ function updateModules(name) {
9127
+ const modules = listModules();
9128
+ const results = [];
9129
+ const toUpdate = name ? modules.filter((m) => m.name === name) : modules;
9130
+ for (const mod of toUpdate) {
9131
+ try {
9132
+ const gitDir = join5(mod.path, ".git");
9133
+ if (existsSync5(gitDir)) {
9134
+ execFileSync("git", ["pull"], { cwd: mod.path, stdio: "pipe", timeout: 60000 });
9135
+ results.push({ name: mod.name, updated: true });
9136
+ } else {
9137
+ results.push({ name: mod.name, updated: false, error: "Not a git repository" });
9138
+ }
9139
+ } catch (err) {
9140
+ results.push({ name: mod.name, updated: false, error: err.message });
9141
+ }
9142
+ }
9143
+ return results;
9144
+ }
9145
+ function removeModule(name) {
9146
+ const modulePath = join5(MODULES_DIR, name);
9147
+ if (!existsSync5(modulePath)) {
9148
+ return false;
9149
+ }
9150
+ rmSync(modulePath, { recursive: true, force: true });
9151
+ return true;
9152
+ }
9153
+
9154
+ // src/lib/init.ts
9155
+ init_space();
9156
+ init_agent();
9157
+
9158
+ // src/lib/snapshot.ts
9159
+ init_space();
9160
+ var import_yaml2 = __toESM(require_dist(), 1);
9161
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
9162
+ import { execSync as execSync4 } from "child_process";
9163
+ import { join as join7 } from "path";
9164
+ var DATA_DIR6 = join7(process.env.HOME || "~", "Data");
9165
+ var LOCK_FILE = join7(DATA_DIR6, "datacore.lock.yaml");
9166
+ var CLI_VERSION = "1.0.6";
9167
+ function getGitInfo(path) {
9168
+ if (!existsSync7(join7(path, ".git"))) {
9169
+ return {};
9170
+ }
9171
+ try {
9172
+ const remote = execSync4("git remote get-url origin 2>/dev/null || true", {
9173
+ cwd: path,
9174
+ encoding: "utf-8"
9175
+ }).trim() || undefined;
9176
+ const commit = execSync4("git rev-parse HEAD 2>/dev/null || true", {
9177
+ cwd: path,
9178
+ encoding: "utf-8"
9179
+ }).trim() || undefined;
9180
+ const branch = execSync4("git branch --show-current 2>/dev/null || true", {
9181
+ cwd: path,
9182
+ encoding: "utf-8"
9183
+ }).trim() || undefined;
9184
+ return { remote, commit, branch };
9185
+ } catch {
9186
+ return {};
9187
+ }
9188
+ }
9189
+ function createSnapshot(options = {}) {
9190
+ const { includeSettings = false } = options;
9191
+ const modules = [];
9192
+ for (const mod of listModules()) {
9193
+ const gitInfo = getGitInfo(mod.path);
9194
+ modules.push({
9195
+ name: mod.name,
9196
+ source: gitInfo.remote || "local",
9197
+ version: mod.version,
9198
+ commit: gitInfo.commit,
9199
+ branch: gitInfo.branch
9200
+ });
9201
+ }
9202
+ const spaces = [];
9203
+ for (const space of listSpaces()) {
9204
+ const gitInfo = getGitInfo(space.path);
9205
+ spaces.push({
9206
+ name: space.name,
9207
+ number: space.number,
9208
+ type: space.type,
9209
+ source: gitInfo.remote,
9210
+ commit: gitInfo.commit
9211
+ });
9212
+ }
9213
+ const deps = checkDependencies();
9214
+ const dependencies = deps.filter((d) => d.installed && d.version).map((d) => ({
9215
+ name: d.name,
9216
+ version: d.version,
9217
+ required: d.required
9218
+ }));
9219
+ const snapshot = {
9220
+ version: "1.0",
9221
+ created: new Date().toISOString(),
9222
+ cliVersion: CLI_VERSION,
9223
+ platform: `${process.platform}-${process.arch}`,
9224
+ modules,
9225
+ spaces,
9226
+ dependencies
9227
+ };
9228
+ if (includeSettings) {
9229
+ const settingsPath = join7(DATA_DIR6, ".datacore", "settings.yaml");
9230
+ if (existsSync7(settingsPath)) {
9231
+ try {
9232
+ const content = readFileSync3(settingsPath, "utf-8");
9233
+ snapshot.settings = import_yaml2.parse(content);
9234
+ } catch {}
9235
+ }
9236
+ }
9237
+ return snapshot;
9238
+ }
9239
+ function saveSnapshot(snapshot, path) {
9240
+ const lockPath = path || LOCK_FILE;
9241
+ const content = import_yaml2.stringify(snapshot, {
9242
+ lineWidth: 0
9243
+ });
9244
+ writeFileSync3(lockPath, content);
9245
+ return lockPath;
9246
+ }
9247
+ function loadSnapshot(path) {
9248
+ const lockPath = path || LOCK_FILE;
9249
+ if (!existsSync7(lockPath)) {
9250
+ return null;
9251
+ }
9252
+ try {
9253
+ const content = readFileSync3(lockPath, "utf-8");
9254
+ return import_yaml2.parse(content);
9255
+ } catch {
9256
+ return null;
9257
+ }
9258
+ }
9259
+ function diffSnapshot(snapshot) {
9260
+ const current = createSnapshot();
9261
+ const diff = {
9262
+ modules: { added: [], removed: [], changed: [] },
9263
+ spaces: { added: [], removed: [] },
9264
+ dependencies: { changed: [] }
9265
+ };
9266
+ const currentModules = new Map(current.modules.map((m) => [m.name, m]));
9267
+ const snapshotModules = new Map(snapshot.modules.map((m) => [m.name, m]));
9268
+ for (const [name, mod] of currentModules) {
9269
+ if (!snapshotModules.has(name)) {
9270
+ diff.modules.added.push(name);
9271
+ } else {
9272
+ const expected = snapshotModules.get(name);
9273
+ if (expected.commit && mod.commit && expected.commit !== mod.commit) {
9274
+ diff.modules.changed.push({
9275
+ name,
9276
+ from: expected.commit.slice(0, 7),
9277
+ to: mod.commit.slice(0, 7)
9278
+ });
9279
+ }
9280
+ }
9281
+ }
9282
+ for (const name of snapshotModules.keys()) {
9283
+ if (!currentModules.has(name)) {
9284
+ diff.modules.removed.push(name);
8880
9285
  }
8881
9286
  }
8882
- return {
8883
- name,
8884
- path: modulePath,
8885
- version,
8886
- description,
8887
- agents,
8888
- commands
8889
- };
8890
- }
8891
- function installModule(source) {
8892
- if (!existsSync5(MODULES_DIR)) {
8893
- const { mkdirSync: mkdirSync3 } = __require("fs");
8894
- mkdirSync3(MODULES_DIR, { recursive: true });
8895
- }
8896
- let name;
8897
- let isGit = false;
8898
- if (source.includes("github.com") || source.startsWith("git@") || source.endsWith(".git")) {
8899
- isGit = true;
8900
- const match = source.match(/([^/]+?)(?:\.git)?$/);
8901
- name = match?.[1] ?? source.split("/").pop() ?? "unknown";
8902
- name = name.replace(/^module-/, "");
8903
- } else if (source.startsWith("@")) {
8904
- name = source.split("/").pop()?.replace(/^datacore-/, "") ?? "unknown";
8905
- } else {
8906
- name = source.replace(/^datacore-/, "").replace(/^module-/, "");
8907
- }
8908
- const modulePath = join5(MODULES_DIR, name);
8909
- if (existsSync5(modulePath)) {
8910
- throw new Error(`Module already installed: ${name}`);
9287
+ const currentSpaces = new Set(current.spaces.map((s) => s.name));
9288
+ const snapshotSpaces = new Set(snapshot.spaces.map((s) => s.name));
9289
+ for (const name of currentSpaces) {
9290
+ if (!snapshotSpaces.has(name)) {
9291
+ diff.spaces.added.push(name);
9292
+ }
8911
9293
  }
8912
- if (isGit) {
8913
- execSync4(`git clone ${source} "${modulePath}"`, { stdio: "pipe" });
8914
- } else {
8915
- const gitUrl = `https://github.com/datacore/module-${name}.git`;
8916
- try {
8917
- execSync4(`git clone ${gitUrl} "${modulePath}"`, { stdio: "pipe" });
8918
- } catch {
8919
- throw new Error(`Could not install module: ${source}`);
9294
+ for (const name of snapshotSpaces) {
9295
+ if (!currentSpaces.has(name)) {
9296
+ diff.spaces.removed.push(name);
8920
9297
  }
8921
9298
  }
8922
- const info2 = getModuleInfo(modulePath);
8923
- if (!info2) {
8924
- rmSync(modulePath, { recursive: true, force: true });
8925
- throw new Error("Invalid module: missing module.yaml");
9299
+ const currentDeps = new Map(current.dependencies.map((d) => [d.name, d.version]));
9300
+ for (const dep of snapshot.dependencies) {
9301
+ const actualVersion = currentDeps.get(dep.name);
9302
+ if (actualVersion && actualVersion !== dep.version) {
9303
+ diff.dependencies.changed.push({
9304
+ name: dep.name,
9305
+ expected: dep.version,
9306
+ actual: actualVersion
9307
+ });
9308
+ }
8926
9309
  }
8927
- return info2;
9310
+ return diff;
8928
9311
  }
8929
- function updateModules(name) {
8930
- const modules = listModules();
8931
- const results = [];
8932
- const toUpdate = name ? modules.filter((m) => m.name === name) : modules;
8933
- for (const mod of toUpdate) {
8934
- try {
8935
- const gitDir = join5(mod.path, ".git");
8936
- if (existsSync5(gitDir)) {
8937
- execSync4("git pull", { cwd: mod.path, stdio: "pipe" });
8938
- results.push({ name: mod.name, updated: true });
8939
- } else {
8940
- results.push({ name: mod.name, updated: false, error: "Not a git repository" });
9312
+ function restoreFromSnapshot(snapshot, options = {}) {
9313
+ const { modules = true, spaces = true, dryRun = false } = options;
9314
+ const result = {
9315
+ modulesInstalled: [],
9316
+ modulesFailed: [],
9317
+ spacesCreated: [],
9318
+ warnings: []
9319
+ };
9320
+ if (modules) {
9321
+ const currentModules = new Set(listModules().map((m) => m.name));
9322
+ for (const mod of snapshot.modules) {
9323
+ if (currentModules.has(mod.name)) {
9324
+ continue;
9325
+ }
9326
+ if (mod.source === "local") {
9327
+ result.warnings.push(`Module ${mod.name} was local, cannot restore`);
9328
+ continue;
9329
+ }
9330
+ if (dryRun) {
9331
+ result.modulesInstalled.push(mod.name);
9332
+ continue;
9333
+ }
9334
+ try {
9335
+ const modulesDir = join7(DATA_DIR6, ".datacore", "modules");
9336
+ if (!existsSync7(modulesDir)) {
9337
+ mkdirSync3(modulesDir, { recursive: true });
9338
+ }
9339
+ const modulePath = join7(modulesDir, mod.name);
9340
+ execSync4(`git clone ${mod.source} "${modulePath}"`, { stdio: "pipe" });
9341
+ if (mod.commit) {
9342
+ execSync4(`git checkout ${mod.commit}`, { cwd: modulePath, stdio: "pipe" });
9343
+ } else if (mod.branch) {
9344
+ execSync4(`git checkout ${mod.branch}`, { cwd: modulePath, stdio: "pipe" });
9345
+ }
9346
+ result.modulesInstalled.push(mod.name);
9347
+ } catch (err) {
9348
+ result.modulesFailed.push({
9349
+ name: mod.name,
9350
+ error: err.message
9351
+ });
8941
9352
  }
8942
- } catch (err) {
8943
- results.push({ name: mod.name, updated: false, error: err.message });
8944
9353
  }
8945
9354
  }
8946
- return results;
8947
- }
8948
- function removeModule(name) {
8949
- const modulePath = join5(MODULES_DIR, name);
8950
- if (!existsSync5(modulePath)) {
8951
- return false;
9355
+ if (spaces) {
9356
+ const currentSpaces = new Set(listSpaces().map((s) => s.name));
9357
+ for (const space of snapshot.spaces) {
9358
+ if (currentSpaces.has(space.name)) {
9359
+ continue;
9360
+ }
9361
+ if (dryRun) {
9362
+ result.spacesCreated.push(space.name);
9363
+ continue;
9364
+ }
9365
+ if (space.source) {
9366
+ try {
9367
+ const spacePath = join7(DATA_DIR6, space.name);
9368
+ execSync4(`git clone ${space.source} "${spacePath}"`, { stdio: "pipe" });
9369
+ if (space.commit) {
9370
+ execSync4(`git checkout ${space.commit}`, { cwd: spacePath, stdio: "pipe" });
9371
+ }
9372
+ result.spacesCreated.push(space.name);
9373
+ } catch (err) {
9374
+ result.warnings.push(`Could not clone space ${space.name}: ${err.message}`);
9375
+ }
9376
+ } else {
9377
+ result.warnings.push(`Space ${space.name} has no git source, skipping`);
9378
+ }
9379
+ }
8952
9380
  }
8953
- rmSync(modulePath, { recursive: true, force: true });
8954
- return true;
9381
+ return result;
8955
9382
  }
8956
9383
 
8957
9384
  // src/state.ts
8958
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
8959
- import { join as join6 } from "path";
8960
- var STATE_DIR = join6(process.env.HOME || "~", "Data", ".datacore", "state");
8961
- var STATE_FILE = join6(STATE_DIR, "operations.json");
9385
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
9386
+ import { join as join8 } from "path";
9387
+ var STATE_DIR = join8(process.env.HOME || "~", "Data", ".datacore", "state");
9388
+ var STATE_FILE = join8(STATE_DIR, "operations.json");
8962
9389
  function ensureStateDir() {
8963
- if (!existsSync6(STATE_DIR)) {
8964
- mkdirSync3(STATE_DIR, { recursive: true });
9390
+ if (!existsSync8(STATE_DIR)) {
9391
+ mkdirSync4(STATE_DIR, { recursive: true });
8965
9392
  }
8966
9393
  }
8967
9394
  function loadState() {
8968
9395
  ensureStateDir();
8969
- if (!existsSync6(STATE_FILE)) {
9396
+ if (!existsSync8(STATE_FILE)) {
8970
9397
  return { operations: [] };
8971
9398
  }
8972
9399
  try {
8973
- const content = readFileSync3(STATE_FILE, "utf-8");
9400
+ const content = readFileSync4(STATE_FILE, "utf-8");
8974
9401
  return JSON.parse(content);
8975
9402
  } catch {
8976
9403
  return { operations: [] };
@@ -8978,7 +9405,7 @@ function loadState() {
8978
9405
  }
8979
9406
  function saveState(state) {
8980
9407
  ensureStateDir();
8981
- writeFileSync3(STATE_FILE, JSON.stringify(state, null, 2));
9408
+ writeFileSync4(STATE_FILE, JSON.stringify(state, null, 2));
8982
9409
  }
8983
9410
  function generateId() {
8984
9411
  return `op_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
@@ -8995,7 +9422,8 @@ class Operation {
8995
9422
  status: "pending",
8996
9423
  startedAt: new Date().toISOString(),
8997
9424
  steps: [],
8998
- rollback: []
9425
+ rollback: [],
9426
+ meta: Object.keys(params).length > 0 ? params : undefined
8999
9427
  };
9000
9428
  this.store.operations.push(this.state);
9001
9429
  saveState(this.store);
@@ -9087,16 +9515,12 @@ ${c.reset}${c.dim} AI-Powered Second Brain ${c.reset}
9087
9515
  `;
9088
9516
  var INIT_COMPLETE = `
9089
9517
  ${c.green}${c.bright}
9090
- ╔═══════════════════════════════════════════════════════════════╗
9091
-
9092
- ║ ${c.reset}${c.green}█████╗ ██████╗████████╗██╗██╗ ██╗███████╗${c.bright}
9093
- ║ ${c.reset}${c.green}██╔══██╗██╔════╝╚══██╔══╝██║██║ ██║██╔════╝${c.bright}
9094
- ${c.reset}${c.green}███████║██║ ██║ ██║██║ ██║█████╗${c.bright}
9095
- ║ ${c.reset}${c.green}██╔══██║██║ ██║ ██║╚██╗ ██╔╝██╔══╝${c.bright} ║
9096
- ║ ${c.reset}${c.green}██║ ██║╚██████╗ ██║ ██║ ╚████╔╝ ███████╗${c.bright} ║
9097
- ║ ${c.reset}${c.green}╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝${c.bright} ║
9098
- ║ ║
9099
- ╚═══════════════════════════════════════════════════════════════╝
9518
+ ╔══════════════════════════════════════════════════════════╗
9519
+
9520
+ ║ ${c.reset}${c.green}██ D A T A C O R E S Y S T E M O N L I N E ██${c.bright}
9521
+ ║ ${c.reset}${c.dim} ▸ All systems nominal. Ready to engage. ◂${c.green}${c.bright}
9522
+
9523
+ ╚══════════════════════════════════════════════════════════╝
9100
9524
  ${c.reset}
9101
9525
  `;
9102
9526
  function sleep(ms) {
@@ -9143,9 +9567,76 @@ function section(title) {
9143
9567
  console.log(`${c.dim}${"─".repeat(50)}${c.reset}`);
9144
9568
  }
9145
9569
 
9570
+ // src/lib/background.ts
9571
+ import { spawn as spawn3 } from "child_process";
9572
+ import { openSync, closeSync, mkdirSync as mkdirSync5 } from "fs";
9573
+ import { join as join9 } from "path";
9574
+ var DATA_DIR7 = join9(process.env.HOME || "", "Data");
9575
+ var STATE_DIR2 = join9(DATA_DIR7, ".datacore", "state");
9576
+ function spawnBackground(command, args, label) {
9577
+ mkdirSync5(STATE_DIR2, { recursive: true });
9578
+ const timestamp = Date.now();
9579
+ const logFile = join9(STATE_DIR2, `bg-${label}-${timestamp}.log`);
9580
+ let outFd;
9581
+ try {
9582
+ outFd = openSync(logFile, "a");
9583
+ const child = spawn3(command, args, {
9584
+ detached: true,
9585
+ stdio: ["ignore", outFd, outFd],
9586
+ cwd: DATA_DIR7,
9587
+ env: { ...process.env }
9588
+ });
9589
+ child.unref();
9590
+ try {
9591
+ closeSync(outFd);
9592
+ } catch {}
9593
+ return {
9594
+ pid: child.pid ?? -1,
9595
+ logFile,
9596
+ label,
9597
+ startedAt: new Date().toISOString()
9598
+ };
9599
+ } catch {
9600
+ if (outFd !== undefined) {
9601
+ try {
9602
+ closeSync(outFd);
9603
+ } catch {}
9604
+ }
9605
+ return null;
9606
+ }
9607
+ }
9608
+
9146
9609
  // src/lib/init.ts
9147
- var DATA_DIR5 = join7(process.env.HOME || "~", "Data");
9148
- var DATACORE_DIR = join7(DATA_DIR5, ".datacore");
9610
+ var DATA_DIR8 = join10(process.env.HOME || "", "Data");
9611
+ var DATACORE_DIR = join10(DATA_DIR8, ".datacore");
9612
+ var UPSTREAM_REPO = "datacore-one/datacore";
9613
+ var TOTAL_STEPS = 9;
9614
+ var KNOWN_SPACES = [
9615
+ {
9616
+ name: "datafund",
9617
+ displayName: "Datafund",
9618
+ description: "Datafund organization - strategy, operations, investor relations",
9619
+ repo: "https://github.com/datacore-one/datafund-space.git",
9620
+ org: "datacore-one",
9621
+ private: true
9622
+ },
9623
+ {
9624
+ name: "fds",
9625
+ displayName: "Fair Data Society",
9626
+ description: "FDS projects - Fairdrive, Fairdrop, fairOS, identity",
9627
+ repo: "https://github.com/fairDataSociety/fds-space.git",
9628
+ org: "fairDataSociety",
9629
+ private: false
9630
+ },
9631
+ {
9632
+ name: "datacore",
9633
+ displayName: "Datacore",
9634
+ description: "Datacore system development - specs, CLI, modules",
9635
+ repo: "https://github.com/datacore-one/datacore-space.git",
9636
+ org: "datacore-one",
9637
+ private: false
9638
+ }
9639
+ ];
9149
9640
  var c2 = {
9150
9641
  reset: "\x1B[0m",
9151
9642
  bold: "\x1B[1m",
@@ -9176,13 +9667,245 @@ async function confirm(question, defaultYes = true) {
9176
9667
  return defaultYes;
9177
9668
  return answer.toLowerCase().startsWith("y");
9178
9669
  }
9670
+ async function choose(question, options, defaultIndex = 0) {
9671
+ for (let i = 0;i < options.length; i++) {
9672
+ const marker = i === defaultIndex ? `${c2.cyan}>${c2.reset}` : " ";
9673
+ console.log(` ${marker} ${c2.cyan}${i + 1}${c2.reset}) ${options[i]}`);
9674
+ }
9675
+ console.log();
9676
+ const answer = await prompt(question, String(defaultIndex + 1));
9677
+ const num = parseInt(answer, 10);
9678
+ if (num >= 1 && num <= options.length)
9679
+ return num - 1;
9680
+ return defaultIndex;
9681
+ }
9682
+ function runArgs(cmd, args, opts) {
9683
+ try {
9684
+ execFileSync2(cmd, args, { stdio: "pipe", cwd: opts?.cwd, timeout: opts?.timeout });
9685
+ return true;
9686
+ } catch {
9687
+ return false;
9688
+ }
9689
+ }
9690
+ function runArgsWithError(cmd, args, opts) {
9691
+ try {
9692
+ execFileSync2(cmd, args, { stdio: "pipe", cwd: opts?.cwd, timeout: opts?.timeout });
9693
+ return { ok: true };
9694
+ } catch (err) {
9695
+ const stderr = err?.stderr?.toString().trim() || "";
9696
+ return { ok: false, stderr };
9697
+ }
9698
+ }
9699
+ function initGitInDir(repoUrl, dir, timeout = 300000) {
9700
+ let r = runArgsWithError("git", ["init"], { cwd: dir, timeout: 30000 });
9701
+ if (!r.ok)
9702
+ return r;
9703
+ r = runArgsWithError("git", ["remote", "add", "origin", repoUrl], { cwd: dir, timeout: 5000 });
9704
+ if (!r.ok) {
9705
+ r = runArgsWithError("git", ["remote", "set-url", "origin", repoUrl], { cwd: dir, timeout: 5000 });
9706
+ if (!r.ok)
9707
+ return r;
9708
+ }
9709
+ r = runArgsWithError("git", ["fetch", "origin"], { cwd: dir, timeout });
9710
+ if (!r.ok)
9711
+ return r;
9712
+ const headRef = runArgsOutput("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { timeout: 5000 });
9713
+ const branch = headRef?.replace("refs/remotes/origin/", "") || "main";
9714
+ r = runArgsWithError("git", ["reset", `origin/${branch}`], { cwd: dir, timeout: 30000 });
9715
+ if (!r.ok)
9716
+ return r;
9717
+ runArgs("git", ["branch", "-M", branch], { cwd: dir });
9718
+ runArgs("git", ["branch", "--set-upstream-to", `origin/${branch}`], { cwd: dir });
9719
+ runArgs("git", ["checkout", "--", "."], { cwd: dir, timeout: 60000 });
9720
+ return { ok: true };
9721
+ }
9722
+ function runArgsOutput(cmd, args, opts) {
9723
+ try {
9724
+ return execFileSync2(cmd, args, { encoding: "utf-8", stdio: "pipe", timeout: opts?.timeout }).trim();
9725
+ } catch {
9726
+ return "";
9727
+ }
9728
+ }
9729
+ function commandExists3(cmd) {
9730
+ try {
9731
+ execFileSync2("which", [cmd], { stdio: "pipe" });
9732
+ return true;
9733
+ } catch {
9734
+ return false;
9735
+ }
9736
+ }
9737
+ function getVersionString(cmd, flag = "--version") {
9738
+ try {
9739
+ const output2 = execFileSync2(cmd, [flag], { encoding: "utf-8", stdio: "pipe" });
9740
+ return output2.match(/(\d+\.\d+(\.\d+)?)/)?.[0] ?? output2.trim().split(`
9741
+ `)[0]?.slice(0, 30);
9742
+ } catch {
9743
+ return;
9744
+ }
9745
+ }
9746
+ function checkGhAuth() {
9747
+ if (!commandExists3("gh"))
9748
+ return { available: false };
9749
+ try {
9750
+ const output2 = execFileSync2("gh", ["auth", "status"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 });
9751
+ const combined = output2;
9752
+ const userMatch = combined.match(/Logged in to github\.com.*account (\S+)/i);
9753
+ return { available: true, user: userMatch?.[1] };
9754
+ } catch (err) {
9755
+ const stderr = err?.stderr?.toString() || "";
9756
+ const userMatch = stderr.match(/Logged in to github\.com.*account (\S+)/i);
9757
+ if (userMatch)
9758
+ return { available: true, user: userMatch[1] };
9759
+ return { available: false };
9760
+ }
9761
+ }
9762
+ function isGitConfigured() {
9763
+ const name = runArgsOutput("git", ["config", "--global", "user.name"]);
9764
+ const email = runArgsOutput("git", ["config", "--global", "user.email"]);
9765
+ return { name: name || undefined, email: email || undefined, configured: !!(name && email) };
9766
+ }
9767
+ async function ensureDependency(name, checkCmd, platform2, isTTY, versionFlag = "--version") {
9768
+ if (commandExists3(checkCmd)) {
9769
+ const version = getVersionString(checkCmd, versionFlag);
9770
+ return { available: true, version, wasInstalled: false };
9771
+ }
9772
+ const installCmd = getInstallCommand(name, platform2);
9773
+ if (!installCmd) {
9774
+ return { available: false, wasInstalled: false };
9775
+ }
9776
+ const spinner = isTTY ? new Spinner(`Installing ${name}...`) : null;
9777
+ spinner?.start();
9778
+ try {
9779
+ execFileSync2("/bin/bash", ["-c", installCmd], { stdio: "pipe", timeout: 300000 });
9780
+ if (commandExists3(checkCmd)) {
9781
+ const version = getVersionString(checkCmd, versionFlag);
9782
+ spinner?.succeed(`${name} installed${version ? ` (${version})` : ""}`);
9783
+ return { available: true, version, wasInstalled: true };
9784
+ }
9785
+ spinner?.fail(`${name} install completed but command not found in PATH`);
9786
+ if (isTTY)
9787
+ console.log(` ${c2.dim}Try manually: ${installCmd}${c2.reset}`);
9788
+ return { available: false, wasInstalled: false };
9789
+ } catch {
9790
+ spinner?.fail(`Failed to install ${name}`);
9791
+ if (isTTY)
9792
+ console.log(` ${c2.dim}Try manually: ${installCmd}${c2.reset}`);
9793
+ return { available: false, wasInstalled: false };
9794
+ }
9795
+ }
9796
+ async function ensureHomebrew(isTTY) {
9797
+ if (commandExists3("brew"))
9798
+ return true;
9799
+ const brewPaths = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"];
9800
+ for (const p of brewPaths) {
9801
+ if (existsSync9(p)) {
9802
+ const dir = join10(p, "..");
9803
+ process.env.PATH = `${dir}:${process.env.PATH}`;
9804
+ return true;
9805
+ }
9806
+ }
9807
+ const spinner = isTTY ? new Spinner("Installing Homebrew...") : null;
9808
+ spinner?.start();
9809
+ try {
9810
+ execFileSync2("/bin/bash", ["-c", 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'], {
9811
+ stdio: "pipe",
9812
+ timeout: 600000
9813
+ });
9814
+ for (const p of brewPaths) {
9815
+ if (existsSync9(p)) {
9816
+ const dir = join10(p, "..");
9817
+ process.env.PATH = `${dir}:${process.env.PATH}`;
9818
+ break;
9819
+ }
9820
+ }
9821
+ if (commandExists3("brew")) {
9822
+ spinner?.succeed("Homebrew installed");
9823
+ return true;
9824
+ }
9825
+ spinner?.fail("Homebrew installed but not found in PATH");
9826
+ return false;
9827
+ } catch {
9828
+ spinner?.fail("Failed to install Homebrew");
9829
+ if (isTTY) {
9830
+ console.log(` ${c2.dim}Install manually: https://brew.sh${c2.reset}`);
9831
+ }
9832
+ return false;
9833
+ }
9834
+ }
9835
+ function runModulePostInstall(modulePath) {
9836
+ const reqTxt = join10(modulePath, "requirements.txt");
9837
+ if (existsSync9(reqTxt)) {
9838
+ const ok = runArgs("python3", ["-m", "pip", "install", "-r", reqTxt, "--quiet"], { cwd: modulePath, timeout: 120000 });
9839
+ return { ran: true, success: ok, type: "pip" };
9840
+ }
9841
+ const pkgJson = join10(modulePath, "package.json");
9842
+ if (existsSync9(pkgJson)) {
9843
+ try {
9844
+ const pkg = JSON.parse(readFileSync5(pkgJson, "utf-8"));
9845
+ if (pkg.dependencies || pkg.devDependencies) {
9846
+ const ok = runArgs("npm", ["install", "--silent"], { cwd: modulePath, timeout: 120000 });
9847
+ return { ran: true, success: ok, type: "npm" };
9848
+ }
9849
+ } catch {}
9850
+ }
9851
+ const installSh = join10(modulePath, "install.sh");
9852
+ if (existsSync9(installSh)) {
9853
+ const ok = runArgs("bash", [installSh], { cwd: modulePath });
9854
+ return { ran: true, success: ok, type: "script" };
9855
+ }
9856
+ try {
9857
+ const entries = readdirSync3(modulePath, { withFileTypes: true });
9858
+ for (const entry of entries) {
9859
+ if (!entry.isDirectory() || entry.name.startsWith("."))
9860
+ continue;
9861
+ const subPkg = join10(modulePath, entry.name, "package.json");
9862
+ if (existsSync9(subPkg)) {
9863
+ try {
9864
+ const pkg = JSON.parse(readFileSync5(subPkg, "utf-8"));
9865
+ if (pkg.dependencies || pkg.devDependencies) {
9866
+ const ok = runArgs("npm", ["install", "--silent"], { cwd: join10(modulePath, entry.name), timeout: 120000 });
9867
+ return { ran: true, success: ok, type: "npm" };
9868
+ }
9869
+ } catch {}
9870
+ }
9871
+ }
9872
+ } catch {}
9873
+ return { ran: false, success: true };
9874
+ }
9875
+ function countFiles(dir) {
9876
+ let total = 0;
9877
+ const byExt = {};
9878
+ function walk(d) {
9879
+ try {
9880
+ for (const entry of readdirSync3(d, { withFileTypes: true })) {
9881
+ if (entry.name.startsWith("."))
9882
+ continue;
9883
+ const fullPath = join10(d, entry.name);
9884
+ if (entry.isFile()) {
9885
+ total++;
9886
+ const ext = entry.name.includes(".") ? entry.name.split(".").pop().toLowerCase() : "other";
9887
+ byExt[ext] = (byExt[ext] || 0) + 1;
9888
+ } else if (entry.isDirectory()) {
9889
+ walk(fullPath);
9890
+ }
9891
+ }
9892
+ } catch {}
9893
+ }
9894
+ walk(dir);
9895
+ return { total, byExt };
9896
+ }
9179
9897
  function isInitialized() {
9180
- return existsSync7(DATACORE_DIR);
9898
+ try {
9899
+ return existsSync9(DATACORE_DIR) && existsSync9(join10(DATACORE_DIR, "agents")) && readdirSync3(join10(DATACORE_DIR, "agents")).length > 0 && existsSync9(join10(DATA_DIR8, "0-personal")) && existsSync9(join10(DATA_DIR8, ".git"));
9900
+ } catch {
9901
+ return false;
9902
+ }
9181
9903
  }
9182
9904
  async function initDatacore(options = {}) {
9183
- const { nonInteractive = false, skipChecks = false, stream = false } = options;
9905
+ const { nonInteractive = false, skipChecks = false, stream = false, verbose = false, force = false } = options;
9184
9906
  const isTTY = stream && process.stdout.isTTY;
9185
9907
  const interactive = isTTY && !nonInteractive;
9908
+ const platform2 = detectPlatform();
9186
9909
  const result = {
9187
9910
  success: false,
9188
9911
  created: [],
@@ -9194,6 +9917,7 @@ async function initDatacore(options = {}) {
9194
9917
  };
9195
9918
  const op = startOperation("init", { options });
9196
9919
  op.start();
9920
+ let profile = { name: "", email: "", useCase: "both", role: "" };
9197
9921
  try {
9198
9922
  if (isTTY) {
9199
9923
  console.clear();
@@ -9202,388 +9926,1081 @@ async function initDatacore(options = {}) {
9202
9926
  console.log();
9203
9927
  await sleep(300);
9204
9928
  }
9205
- op.addStep("check_dependencies");
9206
- op.startStep("check_dependencies");
9929
+ op.addStep("about_you");
9930
+ op.startStep("about_you");
9931
+ if (interactive) {
9932
+ section(`Step 1/${TOTAL_STEPS}: About You`);
9933
+ console.log();
9934
+ console.log(` ${c2.dim}Let's personalize your setup. This configures your identity,${c2.reset}`);
9935
+ console.log(` ${c2.dim}AI context layer, and tailors the experience to your needs.${c2.reset}`);
9936
+ console.log();
9937
+ const gitConfig = isGitConfigured();
9938
+ if (gitConfig.name)
9939
+ profile.name = gitConfig.name;
9940
+ if (gitConfig.email)
9941
+ profile.email = gitConfig.email;
9942
+ profile.name = await prompt(` Your name`, profile.name || undefined);
9943
+ profile.email = await prompt(` Your email`, profile.email || undefined);
9944
+ console.log();
9945
+ console.log(` ${c2.bold}How will you use Datacore?${c2.reset}`);
9946
+ const useCaseIdx = await choose(" Choose", [
9947
+ "Personal productivity (GTD, knowledge management, AI delegation)",
9948
+ "Team management (projects, collaboration, shared knowledge)",
9949
+ "Both personal and team use"
9950
+ ], 2);
9951
+ profile.useCase = ["personal", "team", "both"][useCaseIdx] ?? "both";
9952
+ console.log();
9953
+ profile.role = await prompt(` Your role (e.g., developer, founder, researcher)`, "");
9954
+ console.log();
9955
+ console.log(` ${c2.green}✓${c2.reset} Welcome, ${c2.bold}${profile.name || "friend"}${c2.reset}!`);
9956
+ console.log();
9957
+ } else {
9958
+ const gitConfig = isGitConfigured();
9959
+ if (gitConfig.name)
9960
+ profile.name = gitConfig.name;
9961
+ if (gitConfig.email)
9962
+ profile.email = gitConfig.email;
9963
+ }
9964
+ op.completeStep("about_you");
9965
+ op.addStep("system_setup");
9966
+ op.startStep("system_setup");
9207
9967
  if (!skipChecks) {
9208
9968
  if (isTTY) {
9209
- section("Step 1: Checking Your System");
9969
+ section(`Step 2/${TOTAL_STEPS}: System Setup`);
9970
+ console.log();
9971
+ console.log(` ${c2.dim}Ensuring all tools are installed and configured.${c2.reset}`);
9972
+ console.log(` ${c2.dim}Datacore will install anything that's missing.${c2.reset}`);
9210
9973
  console.log();
9211
9974
  }
9212
- const doctor = runDoctor();
9213
- const gitStatus = checkGitDetailed();
9214
- if (isTTY) {
9215
- if (gitStatus.installed) {
9216
- console.log(` ${c2.green}✓${c2.reset} git ${c2.dim}(${gitStatus.version})${c2.reset}`);
9217
- if (gitStatus.configured) {
9218
- console.log(` ${c2.dim}Configured as: ${gitStatus.userName} <${gitStatus.userEmail}>${c2.reset}`);
9219
- } else {
9220
- console.log(` ${c2.yellow}⚠${c2.reset} ${c2.yellow}Not configured. Run:${c2.reset}`);
9221
- console.log(` ${c2.dim}git config --global user.name "Your Name"${c2.reset}`);
9222
- console.log(` ${c2.dim}git config --global user.email "you@example.com"${c2.reset}`);
9223
- result.warnings.push("git not configured with user.name and user.email");
9975
+ if (platform2 === "macos") {
9976
+ if (!commandExists3("brew")) {
9977
+ if (isTTY) {
9978
+ console.log(` ${c2.dim}Homebrew is needed to install system packages on macOS.${c2.reset}`);
9979
+ console.log();
9224
9980
  }
9225
- } else {
9226
- console.log(` ${c2.red}✗${c2.reset} git ${c2.red}(required)${c2.reset}`);
9227
- console.log(` ${c2.dim}Version control for your knowledge system${c2.reset}`);
9228
- result.errors.push("git is required but not installed");
9981
+ await ensureHomebrew(!!isTTY);
9229
9982
  }
9230
- if (gitStatus.githubAuth) {
9231
- console.log(` ${c2.green}✓${c2.reset} GitHub ${c2.dim}(authenticated as ${gitStatus.githubUser})${c2.reset}`);
9232
- } else {
9233
- console.log(` ${c2.yellow}○${c2.reset} GitHub ${c2.dim}(not authenticated)${c2.reset}`);
9234
- console.log(` ${c2.dim}Optional: Install gh CLI and run 'gh auth login'${c2.reset}`);
9983
+ }
9984
+ const git = await ensureDependency("git", "git", platform2, !!isTTY);
9985
+ if (git.available) {
9986
+ if (!git.wasInstalled && isTTY) {
9987
+ console.log(` ${c2.green}✓${c2.reset} git ${c2.dim}(${git.version})${c2.reset}`);
9235
9988
  }
9236
- for (const dep of doctor.dependencies) {
9237
- if (dep.name === "git")
9238
- continue;
9239
- const info2 = {
9240
- "git-lfs": "Large file support (PDFs, images, videos)",
9241
- node: "Runtime for automation scripts",
9242
- python: "Powers AI agents and data processing",
9243
- claude: "Claude Code AI assistant"
9244
- };
9245
- if (dep.installed) {
9246
- console.log(` ${c2.green}✓${c2.reset} ${dep.name} ${dep.version ? `${c2.dim}(${dep.version})${c2.reset}` : ""}`);
9247
- } else if (dep.required) {
9248
- console.log(` ${c2.red}✗${c2.reset} ${dep.name} ${c2.red}(required)${c2.reset}`);
9249
- console.log(` ${c2.dim}${info2[dep.name] || ""}${c2.reset}`);
9250
- if (dep.installCommand) {
9251
- console.log(` ${c2.yellow}Install: ${dep.installCommand}${c2.reset}`);
9252
- }
9989
+ const gitConfig = isGitConfigured();
9990
+ if (!gitConfig.configured && profile.name && profile.email) {
9991
+ if (isTTY) {
9992
+ const spinner = new Spinner("Configuring git identity...");
9993
+ spinner.start();
9994
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
9995
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
9996
+ spinner.succeed(`Git configured as: ${profile.name} <${profile.email}>`);
9253
9997
  } else {
9254
- console.log(` ${c2.yellow}○${c2.reset} ${dep.name} ${c2.dim}(recommended)${c2.reset}`);
9255
- console.log(` ${c2.dim}${info2[dep.name] || ""}${c2.reset}`);
9256
- if (dep.installCommand) {
9257
- console.log(` ${c2.dim}Install: ${dep.installCommand}${c2.reset}`);
9258
- }
9998
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
9999
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
9259
10000
  }
10001
+ } else if (gitConfig.configured && interactive && profile.name && profile.email && (gitConfig.name !== profile.name || gitConfig.email !== profile.email)) {
10002
+ console.log(` ${c2.dim}Git configured as: ${gitConfig.name} <${gitConfig.email}>${c2.reset}`);
10003
+ const override = await confirm(` Update to ${profile.name} <${profile.email}>?`, false);
10004
+ if (override) {
10005
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
10006
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
10007
+ console.log(` ${c2.green}✓${c2.reset} Git updated to: ${profile.name} <${profile.email}>`);
10008
+ }
10009
+ } else if (gitConfig.configured && isTTY && !git.wasInstalled) {
10010
+ console.log(` ${c2.dim}Configured as: ${gitConfig.name} <${gitConfig.email}>${c2.reset}`);
10011
+ }
10012
+ } else {
10013
+ result.errors.push("git is required but could not be installed");
10014
+ }
10015
+ const node = await ensureDependency("node", "node", platform2, !!isTTY, "-v");
10016
+ if (node.available && !node.wasInstalled && isTTY) {
10017
+ console.log(` ${c2.green}✓${c2.reset} node ${c2.dim}(${node.version})${c2.reset}`);
10018
+ }
10019
+ if (node.available && node.version) {
10020
+ const major = parseInt(node.version.split(".")[0] || "0", 10);
10021
+ if (major < 20) {
10022
+ if (isTTY)
10023
+ console.log(` ${c2.yellow}⚠${c2.reset} ${c2.dim}Node ${node.version} is outdated. Consider updating to Node 20+${c2.reset}`);
10024
+ result.warnings.push(`Node.js ${node.version} is outdated - recommend Node 20+`);
9260
10025
  }
9261
- console.log();
9262
10026
  }
9263
- if (doctor.status === "missing_required") {
9264
- const missing = doctor.dependencies.filter((d) => d.required && !d.installed);
9265
- for (const dep of missing) {
9266
- result.errors.push(`Missing required: ${dep.name}`);
10027
+ const claude = await ensureDependency("claude", "claude", platform2, !!isTTY);
10028
+ if (claude.available && !claude.wasInstalled && isTTY) {
10029
+ console.log(` ${c2.green}✓${c2.reset} Claude Code ${c2.dim}(${claude.version})${c2.reset}`);
10030
+ }
10031
+ if (!claude.available) {
10032
+ result.warnings.push("Claude Code not installed - install with: npm install -g @anthropic-ai/claude-code");
10033
+ }
10034
+ const python = await ensureDependency("python", "python3", platform2, !!isTTY);
10035
+ if (python.available && !python.wasInstalled && isTTY) {
10036
+ console.log(` ${c2.green}✓${c2.reset} python ${c2.dim}(${python.version})${c2.reset}`);
10037
+ }
10038
+ if (!python.available) {
10039
+ result.warnings.push("Python not installed - some agents may not work");
10040
+ }
10041
+ const gh = await ensureDependency("gh", "gh", platform2, !!isTTY);
10042
+ if (gh.available && !gh.wasInstalled && isTTY) {
10043
+ const ghAuth2 = checkGhAuth();
10044
+ if (ghAuth2.available) {
10045
+ console.log(` ${c2.green}✓${c2.reset} GitHub CLI ${c2.dim}(authenticated as ${ghAuth2.user})${c2.reset}`);
10046
+ } else {
10047
+ console.log(` ${c2.green}✓${c2.reset} GitHub CLI ${c2.dim}(installed, not authenticated)${c2.reset}`);
9267
10048
  }
9268
- if (isTTY) {
9269
- console.log(` ${c2.red}${c2.bold}Cannot continue - install required dependencies first.${c2.reset}`);
10049
+ }
10050
+ if (commandExists3("gh")) {
10051
+ const ghAuth2 = checkGhAuth();
10052
+ if (!ghAuth2.available && interactive) {
9270
10053
  console.log();
10054
+ console.log(` ${c2.dim}To connect your GitHub account, a browser window will open.${c2.reset}`);
10055
+ const doAuth = await confirm(" Authenticate with GitHub now?", true);
10056
+ if (doAuth) {
10057
+ try {
10058
+ execFileSync2("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
10059
+ stdio: "inherit",
10060
+ timeout: 120000
10061
+ });
10062
+ const newAuth = checkGhAuth();
10063
+ if (newAuth.available) {
10064
+ console.log(` ${c2.green}✓${c2.reset} GitHub authenticated (${newAuth.user})`);
10065
+ }
10066
+ } catch {
10067
+ console.log(` ${c2.yellow}⚠${c2.reset} GitHub authentication skipped`);
10068
+ result.warnings.push("GitHub CLI not authenticated - fork workflow may not work");
10069
+ }
10070
+ } else {
10071
+ result.warnings.push("GitHub CLI not authenticated - run: gh auth login");
10072
+ }
9271
10073
  }
9272
- op.failStep("check_dependencies", "Missing required dependencies");
9273
- op.fail("Missing required dependencies");
9274
- return result;
9275
10074
  }
9276
- if (interactive && !gitStatus.configured) {
9277
- const proceed = await confirm("Git is not configured. Continue anyway?", false);
9278
- if (!proceed) {
9279
- result.errors.push("User cancelled: git not configured");
9280
- op.fail("User cancelled");
9281
- return result;
10075
+ const gitlfs = await ensureDependency("git-lfs", "git-lfs", platform2, !!isTTY);
10076
+ if (gitlfs.available && !gitlfs.wasInstalled && isTTY) {
10077
+ console.log(` ${c2.green}✓${c2.reset} git-lfs ${c2.dim}(${gitlfs.version})${c2.reset}`);
10078
+ }
10079
+ if (isTTY) {
10080
+ console.log();
10081
+ if (git.available && node.available) {
10082
+ console.log(` ${c2.green}All systems ready.${c2.reset}`);
10083
+ } else {
10084
+ console.log(` ${c2.yellow}Some tools could not be installed. Continuing with what's available.${c2.reset}`);
9282
10085
  }
10086
+ console.log();
9283
10087
  }
9284
10088
  }
9285
- op.completeStep("check_dependencies");
9286
- op.addStep("create_data_dir");
9287
- op.startStep("create_data_dir");
10089
+ op.completeStep("system_setup");
10090
+ op.addStep("clone_repo");
10091
+ op.startStep("clone_repo");
9288
10092
  if (isTTY) {
9289
- section("Step 2: Creating ~/Data");
9290
- console.log(` ${c2.dim}Your central knowledge directory. Everything lives here.${c2.reset}`);
10093
+ section(`Step 3/${TOTAL_STEPS}: Setting Up Repository`);
10094
+ console.log();
10095
+ console.log(` ${c2.dim}Datacore lives in ~/Data. We'll fork the main repository to your${c2.reset}`);
10096
+ console.log(` ${c2.dim}GitHub account so you can customize freely and pull updates.${c2.reset}`);
10097
+ console.log();
10098
+ }
10099
+ const ghAuth = checkGhAuth();
10100
+ const upstreamUrl = `https://github.com/${UPSTREAM_REPO}.git`;
10101
+ let ghUser;
10102
+ if (ghAuth.available) {
10103
+ ghUser = ghAuth.user || runArgsOutput("gh", ["api", "user", "-q", ".login"], { timeout: 15000 });
10104
+ const forkSpinner = isTTY ? new Spinner("Checking fork...") : null;
10105
+ forkSpinner?.start();
10106
+ const forkExists = runArgs("gh", ["repo", "view", `${ghUser}/datacore`], { cwd: process.env.HOME, timeout: 30000 });
10107
+ if (!forkExists) {
10108
+ forkSpinner?.update("Forking repository...");
10109
+ const forked = runArgs("gh", ["repo", "fork", UPSTREAM_REPO, "--clone=false"], { timeout: 30000 });
10110
+ if (forked) {
10111
+ forkSpinner?.succeed(`Forked to ${ghUser}/datacore`);
10112
+ } else {
10113
+ forkSpinner?.fail("Fork failed (will clone upstream directly)");
10114
+ result.warnings.push("Could not fork repository - will clone directly");
10115
+ ghUser = undefined;
10116
+ }
10117
+ } else {
10118
+ forkSpinner?.succeed(`Fork exists: ${ghUser}/datacore`);
10119
+ }
10120
+ }
10121
+ const cloneUrls = [];
10122
+ if (ghUser) {
10123
+ cloneUrls.push(`https://github.com/${ghUser}/datacore.git`);
10124
+ cloneUrls.push(`git@github.com:${ghUser}/datacore.git`);
10125
+ }
10126
+ if (!ghAuth.available && interactive) {
10127
+ const customUrl = await prompt(" Repository URL", upstreamUrl);
10128
+ cloneUrls.push(customUrl);
10129
+ const sshUrl = customUrl.replace("https://github.com/", "git@github.com:");
10130
+ if (sshUrl !== customUrl)
10131
+ cloneUrls.push(sshUrl);
10132
+ } else {
10133
+ cloneUrls.push(upstreamUrl);
10134
+ cloneUrls.push(`git@github.com:${UPSTREAM_REPO}.git`);
10135
+ }
10136
+ if (!ghAuth.available && interactive) {
10137
+ console.log(` ${c2.dim}GitHub CLI not authenticated. Cloning upstream directly.${c2.reset}`);
10138
+ console.log(` ${c2.dim}You can fork later with: gh repo fork --remote${c2.reset}`);
9291
10139
  console.log();
9292
10140
  }
9293
- if (!existsSync7(DATA_DIR5)) {
9294
- mkdirSync4(DATA_DIR5, { recursive: true });
9295
- result.created.push(DATA_DIR5);
10141
+ const hasGitDir = existsSync9(join10(DATA_DIR8, ".git"));
10142
+ const hasValidGit = hasGitDir && runArgs("git", ["rev-parse", "HEAD"], { cwd: DATA_DIR8, timeout: 5000 });
10143
+ if (hasValidGit) {
9296
10144
  if (isTTY)
9297
- console.log(` ${c2.green}✓${c2.reset} Created ${DATA_DIR5}`);
10145
+ console.log(` ${c2.green}✓${c2.reset} Found existing repository at ~/Data`);
10146
+ const pullSpinner = isTTY ? new Spinner("Pulling latest changes...") : null;
10147
+ pullSpinner?.start();
10148
+ if (runArgs("git", ["pull", "--rebase", "--autostash"], { cwd: DATA_DIR8, timeout: 60000 })) {
10149
+ pullSpinner?.succeed("Repository up to date");
10150
+ } else {
10151
+ pullSpinner?.fail("Pull failed (non-fatal, continuing)");
10152
+ result.warnings.push("Could not pull latest changes");
10153
+ }
10154
+ const currentUpstream = runArgsOutput("git", ["remote", "get-url", "upstream"]);
10155
+ if (!currentUpstream) {
10156
+ runArgs("git", ["remote", "add", "upstream", upstreamUrl], { cwd: DATA_DIR8 });
10157
+ }
9298
10158
  } else {
10159
+ mkdirSync6(DATA_DIR8, { recursive: true });
10160
+ const cloneSpinner = isTTY ? new Spinner("Cloning into ~/Data...") : null;
10161
+ cloneSpinner?.start();
10162
+ let lastCloneErr = "";
10163
+ let cloned = false;
10164
+ for (const url of cloneUrls) {
10165
+ const r = runArgsWithError("git", ["clone", url, "."], { cwd: DATA_DIR8, timeout: 300000 });
10166
+ if (r.ok) {
10167
+ cloned = true;
10168
+ break;
10169
+ }
10170
+ lastCloneErr = r.stderr || "";
10171
+ if (lastCloneErr.includes("already exists and is not an empty directory"))
10172
+ break;
10173
+ }
10174
+ if (cloned) {
10175
+ cloneSpinner?.succeed("Cloned into ~/Data");
10176
+ result.created.push(DATA_DIR8);
10177
+ if (ghUser) {
10178
+ runArgs("git", ["remote", "add", "upstream", upstreamUrl], { cwd: DATA_DIR8 });
10179
+ }
10180
+ } else if (lastCloneErr.includes("already exists and is not an empty directory")) {
10181
+ if (!force) {
10182
+ cloneSpinner?.fail("~/Data is not empty");
10183
+ const entries = readdirSync3(DATA_DIR8);
10184
+ if (isTTY) {
10185
+ console.log(` ${c2.dim}~/Data contains ${entries.length} items but is not a git repository.${c2.reset}`);
10186
+ console.log(` ${c2.dim}Use --force to initialize git in the existing directory.${c2.reset}`);
10187
+ console.log(` ${c2.dim}Or remove/rename ~/Data for a fresh install.${c2.reset}`);
10188
+ }
10189
+ result.errors.push("~/Data exists and is not empty. Use --force to re-initialize.");
10190
+ op.failStep("clone_repo", "Directory not empty");
10191
+ op.fail("~/Data is not empty");
10192
+ return result;
10193
+ }
10194
+ cloneSpinner?.update("Initializing git in existing ~/Data...");
10195
+ let initOk = false;
10196
+ let lastInitErr = "";
10197
+ for (const url of cloneUrls) {
10198
+ const r = initGitInDir(url, DATA_DIR8);
10199
+ if (r.ok) {
10200
+ initOk = true;
10201
+ break;
10202
+ }
10203
+ lastInitErr = r.stderr || "";
10204
+ }
10205
+ if (initOk) {
10206
+ cloneSpinner?.succeed("Git initialized in existing ~/Data");
10207
+ if (ghUser) {
10208
+ runArgs("git", ["remote", "add", "upstream", upstreamUrl], { cwd: DATA_DIR8 });
10209
+ }
10210
+ } else {
10211
+ cloneSpinner?.fail("Could not initialize git");
10212
+ result.errors.push(`Git init failed: ${lastInitErr}`);
10213
+ if (lastInitErr.includes("Authentication") || lastInitErr.includes("403") || lastInitErr.includes("401")) {
10214
+ if (isTTY)
10215
+ console.log(` ${c2.dim}Check your GitHub access: gh auth status${c2.reset}`);
10216
+ }
10217
+ op.failStep("clone_repo", "Git init failed");
10218
+ op.fail("Git init failed");
10219
+ return result;
10220
+ }
10221
+ } else {
10222
+ cloneSpinner?.fail("Clone failed");
10223
+ if (lastCloneErr.includes("not found") || lastCloneErr.includes("not exist") || lastCloneErr.includes("does not appear to be a git repository")) {
10224
+ result.errors.push("Repository not found");
10225
+ if (isTTY) {
10226
+ if (ghUser) {
10227
+ console.log(` ${c2.dim}Neither ${ghUser}/datacore nor ${UPSTREAM_REPO} could be cloned.${c2.reset}`);
10228
+ }
10229
+ console.log(` ${c2.dim}Ensure you have access to ${UPSTREAM_REPO} and try again.${c2.reset}`);
10230
+ console.log(` ${c2.dim}Check access: gh repo view ${UPSTREAM_REPO}${c2.reset}`);
10231
+ }
10232
+ } else if (lastCloneErr.includes("Authentication") || lastCloneErr.includes("Permission") || lastCloneErr.includes("403") || lastCloneErr.includes("401")) {
10233
+ result.errors.push("Authentication failed");
10234
+ if (isTTY) {
10235
+ console.log(` ${c2.dim}Could not authenticate with GitHub.${c2.reset}`);
10236
+ console.log(` ${c2.dim}Try: gh auth login${c2.reset}`);
10237
+ }
10238
+ } else {
10239
+ result.errors.push(`Clone failed: ${lastCloneErr}`);
10240
+ }
10241
+ op.failStep("clone_repo", "Clone failed");
10242
+ op.fail("Clone failed");
10243
+ return result;
10244
+ }
10245
+ }
10246
+ const dipsDir = join10(DATACORE_DIR, "dips");
10247
+ if (existsSync9(DATACORE_DIR) && !existsSync9(join10(dipsDir, ".git"))) {
10248
+ const spinner = isTTY ? new Spinner("Fetching specifications...") : null;
10249
+ spinner?.start();
10250
+ const dipsRepo = "https://github.com/datacore-one/datacore-dips.git";
10251
+ let clonedDips = runArgs("git", ["clone", dipsRepo, dipsDir], { timeout: 300000 });
10252
+ if (!clonedDips) {
10253
+ clonedDips = runArgs("git", ["clone", "git@github.com:datacore-one/datacore-dips.git", dipsDir], { timeout: 300000 });
10254
+ }
10255
+ if (clonedDips) {
10256
+ spinner?.succeed("Specifications installed");
10257
+ } else {
10258
+ spinner?.fail("Could not fetch specifications (non-fatal)");
10259
+ result.warnings.push("Specifications repo not cloned");
10260
+ }
10261
+ } else if (existsSync9(join10(dipsDir, ".git"))) {
9299
10262
  if (isTTY)
9300
- console.log(` ${c2.green}✓${c2.reset} Found ${DATA_DIR5}`);
10263
+ console.log(` ${c2.green}✓${c2.reset} Specifications present`);
10264
+ }
10265
+ if (commandExists3("git-lfs") && existsSync9(join10(DATA_DIR8, ".git"))) {
10266
+ const spinner = isTTY ? new Spinner("Initializing Git LFS...") : null;
10267
+ spinner?.start();
10268
+ if (runArgs("git", ["lfs", "install"], { cwd: DATA_DIR8, timeout: 30000 })) {
10269
+ runArgs("git", ["lfs", "pull"], { cwd: DATA_DIR8, timeout: 120000 });
10270
+ spinner?.succeed("Git LFS initialized");
10271
+ } else {
10272
+ spinner?.fail("Git LFS init failed (non-fatal)");
10273
+ }
9301
10274
  }
9302
10275
  if (isTTY)
9303
10276
  console.log();
9304
- op.completeStep("create_data_dir");
9305
- op.addStep("create_datacore_dir");
9306
- op.startStep("create_datacore_dir");
10277
+ op.completeStep("clone_repo");
10278
+ op.addStep("team_spaces");
10279
+ op.startStep("team_spaces");
10280
+ if (interactive) {
10281
+ section(`Step 4/${TOTAL_STEPS}: Team Spaces`);
10282
+ console.log();
10283
+ console.log(` ${c2.dim}Spaces separate different areas of your life. Your personal space${c2.reset}`);
10284
+ console.log(` ${c2.dim}(0-personal) is created automatically. Team spaces are separate${c2.reset}`);
10285
+ console.log(` ${c2.dim}git repos for organizations you work with.${c2.reset}`);
10286
+ console.log();
10287
+ if (profile.useCase === "personal") {
10288
+ console.log(` ${c2.dim}You selected personal use. You can add team spaces anytime later:${c2.reset}`);
10289
+ console.log(` ${c2.dim}datacore space create <name>${c2.reset}`);
10290
+ } else {
10291
+ console.log(` ${c2.dim}Each team space gets its own:${c2.reset}`);
10292
+ console.log(` ${c2.dim}• GTD task system and AI agents${c2.reset}`);
10293
+ console.log(` ${c2.dim}• Knowledge base (wiki, notes, research)${c2.reset}`);
10294
+ console.log(` ${c2.dim}• Project tracking via GitHub Issues${c2.reset}`);
10295
+ console.log();
10296
+ const existingSpaces = listSpaces();
10297
+ const existingNames = existingSpaces.map((s) => s.name.replace(/^\d+-/, ""));
10298
+ if (existingSpaces.length > 1) {
10299
+ console.log(` ${c2.dim}Existing spaces:${c2.reset}`);
10300
+ for (const s of existingSpaces) {
10301
+ console.log(` ${s.type === "personal" ? "\uD83D\uDC64" : "\uD83D\uDC65"} ${s.name}`);
10302
+ }
10303
+ console.log();
10304
+ }
10305
+ const wantSpace = await confirm(" Would you like to add a team space?", false);
10306
+ if (wantSpace) {
10307
+ let adding = true;
10308
+ while (adding) {
10309
+ const spaceName = await prompt(' Space name (e.g., "datafund", "acme-corp")');
10310
+ if (!spaceName) {
10311
+ adding = false;
10312
+ continue;
10313
+ }
10314
+ const normalized = spaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
10315
+ const knownMatch = KNOWN_SPACES.find((ks) => ks.name === normalized || ks.displayName.toLowerCase() === spaceName.toLowerCase() || normalized.length >= 3 && ks.name.startsWith(normalized) || normalized.length >= 3 && ks.displayName.toLowerCase().startsWith(normalized.toLowerCase()));
10316
+ if (existingNames.includes(normalized) || existingNames.includes(knownMatch?.name ?? "")) {
10317
+ console.log(` ${c2.green}✓${c2.reset} ${spaceName} is already installed`);
10318
+ adding = await confirm(" Add another?", false);
10319
+ continue;
10320
+ }
10321
+ if (knownMatch) {
10322
+ console.log(` ${c2.green}✓${c2.reset} Found registered space: ${c2.bold}${knownMatch.displayName}${c2.reset}`);
10323
+ console.log(` ${c2.dim}${knownMatch.description}${c2.reset}`);
10324
+ const currentSpaces = listSpaces();
10325
+ const nextNum = currentSpaces.length > 0 ? Math.max(...currentSpaces.map((s) => s.number)) + 1 : 1;
10326
+ const spacePath = join10(DATA_DIR8, `${nextNum}-${knownMatch.name}`);
10327
+ const spinner = new Spinner(`Cloning ${knownMatch.displayName}...`);
10328
+ spinner.start();
10329
+ let cloned = runArgs("git", ["clone", knownMatch.repo, spacePath], { timeout: 300000 });
10330
+ if (!cloned) {
10331
+ const sshUrl = knownMatch.repo.replace("https://github.com/", "git@github.com:");
10332
+ cloned = runArgs("git", ["clone", sshUrl, spacePath], { timeout: 300000 });
10333
+ }
10334
+ if (cloned) {
10335
+ spinner.succeed(`Added space: ${nextNum}-${knownMatch.name}`);
10336
+ result.spacesCreated.push(`${nextNum}-${knownMatch.name}`);
10337
+ } else {
10338
+ spinner.fail(`Could not clone ${knownMatch.displayName}`);
10339
+ if (knownMatch.private) {
10340
+ console.log(` ${c2.dim}This is a private repo. Make sure you have access to ${knownMatch.org}.${c2.reset}`);
10341
+ console.log(` ${c2.dim}Request access or try: gh auth refresh -s read:org${c2.reset}`);
10342
+ }
10343
+ result.warnings.push(`Failed to add space: ${knownMatch.name}`);
10344
+ }
10345
+ } else {
10346
+ const repoUrl = await prompt(" Git repo URL (or Enter to create local)", "");
10347
+ if (repoUrl) {
10348
+ const currentSpaces = listSpaces();
10349
+ const nextNum = currentSpaces.length > 0 ? Math.max(...currentSpaces.map((s) => s.number)) + 1 : 1;
10350
+ const spacePath = join10(DATA_DIR8, `${nextNum}-${normalized}`);
10351
+ const spinner = new Spinner(`Cloning ${spaceName}...`);
10352
+ spinner.start();
10353
+ let cloned = runArgs("git", ["clone", repoUrl, spacePath], { timeout: 300000 });
10354
+ if (!cloned) {
10355
+ const sshUrl = repoUrl.replace("https://github.com/", "git@github.com:");
10356
+ cloned = runArgs("git", ["clone", sshUrl, spacePath], { timeout: 300000 });
10357
+ }
10358
+ if (cloned) {
10359
+ spinner.succeed(`Added space: ${nextNum}-${normalized}`);
10360
+ result.spacesCreated.push(`${nextNum}-${normalized}`);
10361
+ } else {
10362
+ spinner.fail(`Could not clone ${repoUrl}`);
10363
+ result.warnings.push(`Failed to add space: ${spaceName}`);
10364
+ }
10365
+ } else {
10366
+ try {
10367
+ const { createSpace: createSpace2 } = await Promise.resolve().then(() => (init_space(), exports_space));
10368
+ const space = createSpace2(spaceName, "team");
10369
+ result.created.push(space.path);
10370
+ result.spacesCreated.push(space.name);
10371
+ console.log(` ${c2.green}✓${c2.reset} Created ${space.name}/`);
10372
+ } catch (err) {
10373
+ console.log(` ${c2.red}✗${c2.reset} ${err.message}`);
10374
+ }
10375
+ }
10376
+ }
10377
+ adding = await confirm(" Add another?", false);
10378
+ }
10379
+ }
10380
+ }
10381
+ console.log();
10382
+ }
10383
+ op.completeStep("team_spaces");
10384
+ op.addStep("second_brain");
10385
+ op.startStep("second_brain");
9307
10386
  if (isTTY) {
9308
- section("Step 3: Creating .datacore");
9309
- console.log(` ${c2.dim}System configuration, agents, commands, and modules.${c2.reset}`);
10387
+ section(`Step 5/${TOTAL_STEPS}: Your Second Brain`);
10388
+ console.log();
10389
+ console.log(` ${c2.dim}Your personal space is your AI-powered second brain. Here's how${c2.reset}`);
10390
+ console.log(` ${c2.dim}it's organized:${c2.reset}`);
10391
+ console.log();
10392
+ console.log(` ${c2.bold}Getting Things Done (GTD)${c2.reset}`);
10393
+ console.log(` ${c2.dim}A trusted system where you capture everything into an inbox, then${c2.reset}`);
10394
+ console.log(` ${c2.dim}process it into actionable next steps. Nothing stays in your head.${c2.reset}`);
10395
+ console.log(` ${c2.dim}• org/inbox.org → Capture anything, anytime${c2.reset}`);
10396
+ console.log(` ${c2.dim}• org/next_actions.org → What you're actually doing${c2.reset}`);
10397
+ console.log(` ${c2.dim}• org/someday.org → Ideas for later${c2.reset}`);
10398
+ console.log();
10399
+ console.log(` ${c2.bold}AI Delegation${c2.reset}`);
10400
+ console.log(` ${c2.dim}Tag tasks with :AI: and agents handle them overnight:${c2.reset}`);
10401
+ console.log(` ${c2.dim}• :AI:research: → Deep research on any topic${c2.reset}`);
10402
+ console.log(` ${c2.dim}• :AI:content: → Draft emails, blog posts, docs${c2.reset}`);
10403
+ console.log(` ${c2.dim}• :AI:data: → Analyze data, generate reports${c2.reset}`);
10404
+ console.log(` ${c2.dim}• :AI:pm: → Track projects, flag blockers${c2.reset}`);
10405
+ console.log();
10406
+ console.log(` ${c2.bold}Knowledge Management${c2.reset}`);
10407
+ console.log(` ${c2.dim}Your knowledge compounds over time. Every note, insight, and${c2.reset}`);
10408
+ console.log(` ${c2.dim}conversation gets woven into a personal knowledge base:${c2.reset}`);
10409
+ console.log(` ${c2.dim}• notes/ → Daily journals, quick captures${c2.reset}`);
10410
+ console.log(` ${c2.dim}• 3-knowledge/ → Permanent knowledge (Zettelkasten)${c2.reset}`);
10411
+ console.log(` ${c2.dim}├── zettel/ → Atomic ideas, one concept per note${c2.reset}`);
10412
+ console.log(` ${c2.dim}├── pages/ → Longer topic pages and guides${c2.reset}`);
10413
+ console.log(` ${c2.dim}├── literature/ → Summaries of things you've read${c2.reset}`);
10414
+ console.log(` ${c2.dim}└── reference/ → People, companies, glossary${c2.reset}`);
10415
+ console.log();
10416
+ console.log(` ${c2.dim}The more you capture, the smarter your system becomes. Agents${c2.reset}`);
10417
+ console.log(` ${c2.dim}cross-reference your knowledge to give better answers over time.${c2.reset}`);
9310
10418
  console.log();
9311
10419
  }
9312
- if (!existsSync7(DATACORE_DIR)) {
9313
- const dirs = [
9314
- { path: "", desc: "" },
9315
- { path: "commands", desc: "Slash commands (/today, /sync, etc.)" },
9316
- { path: "agents", desc: "AI agents for task automation" },
9317
- { path: "modules", desc: "Optional extensions" },
9318
- { path: "specs", desc: "System documentation" },
9319
- { path: "lib", desc: "Shared utilities" },
9320
- { path: "env", desc: "API keys and secrets" },
9321
- { path: "state", desc: "Runtime state" },
9322
- { path: "registry", desc: "Agent/command discovery" }
9323
- ];
9324
- for (const { path: dir, desc } of dirs) {
9325
- const path = join7(DATACORE_DIR, dir);
9326
- mkdirSync4(path, { recursive: true });
9327
- result.created.push(path);
9328
- if (isTTY && dir) {
9329
- console.log(` ${c2.green}✓${c2.reset} ${dir}/ ${c2.dim}- ${desc}${c2.reset}`);
10420
+ const personalPath = join10(DATA_DIR8, "0-personal");
10421
+ if (existsSync9(personalPath)) {
10422
+ if (isTTY)
10423
+ console.log(` ${c2.green}✓${c2.reset} Personal space ready (0-personal/)`);
10424
+ result.spacesCreated.push("0-personal");
10425
+ } else {
10426
+ if (isTTY)
10427
+ console.log(` ${c2.dim}Creating personal space...${c2.reset}`);
10428
+ try {
10429
+ const { createSpace: createSpace2 } = await Promise.resolve().then(() => (init_space(), exports_space));
10430
+ const space = createSpace2("personal", "personal");
10431
+ result.created.push(space.path);
10432
+ result.spacesCreated.push("0-personal");
10433
+ if (isTTY)
10434
+ console.log(` ${c2.green}✓${c2.reset} Personal space created (0-personal/)`);
10435
+ } catch {
10436
+ const dirs = ["org", "notes", "notes/journals", "notes/pages", "journal", "3-knowledge", "content", "0-inbox"];
10437
+ for (const dir of dirs) {
10438
+ mkdirSync6(join10(personalPath, dir), { recursive: true });
9330
10439
  }
10440
+ result.created.push(personalPath);
10441
+ result.spacesCreated.push("0-personal");
10442
+ if (isTTY)
10443
+ console.log(` ${c2.green}✓${c2.reset} Personal space initialized`);
9331
10444
  }
9332
- writeFileSync4(join7(DATACORE_DIR, "settings.yaml"), `# Datacore Settings
9333
- # Override in settings.local.yaml (gitignored)
9334
-
9335
- editor:
9336
- open_markdown_on_generate: true
9337
- open_command: ""
9338
-
9339
- sync:
9340
- pull_on_today: true
9341
- push_on_wrap_up: true
9342
-
9343
- journal:
9344
- open_after_update: false
9345
-
9346
- nightshift:
9347
- server: ""
9348
- auto_trigger: false
9349
- `);
9350
- result.created.push(join7(DATACORE_DIR, "settings.yaml"));
9351
- writeFileSync4(join7(DATACORE_DIR, "registry", "agents.yaml"), `# Agent Registry
9352
- agents: []
10445
+ }
10446
+ const gtdFiles = ["inbox.org", "next_actions.org", "someday.org"];
10447
+ const orgDir = join10(personalPath, "org");
10448
+ let gtdReady = true;
10449
+ for (const file of gtdFiles) {
10450
+ if (!existsSync9(join10(orgDir, file)))
10451
+ gtdReady = false;
10452
+ }
10453
+ if (gtdReady) {
10454
+ if (isTTY)
10455
+ console.log(` ${c2.green}✓${c2.reset} GTD system initialized`);
10456
+ }
10457
+ if (existsSync9(join10(personalPath, "3-knowledge"))) {
10458
+ if (isTTY)
10459
+ console.log(` ${c2.green}✓${c2.reset} Knowledge base ready`);
10460
+ }
10461
+ const templates = [
10462
+ { src: "install.yaml.example", dst: "install.yaml", label: "Installation manifest" },
10463
+ { src: "0-personal/org/inbox.org.example", dst: "0-personal/org/inbox.org", label: "GTD inbox" },
10464
+ { src: "0-personal/org/next_actions.org.example", dst: "0-personal/org/next_actions.org", label: "GTD next actions" },
10465
+ { src: "0-personal/org/someday.org.example", dst: "0-personal/org/someday.org", label: "GTD someday" },
10466
+ { src: "0-personal/org/habits.org.example", dst: "0-personal/org/habits.org", label: "GTD habits" }
10467
+ ];
10468
+ for (const { src, dst } of templates) {
10469
+ const srcPath = join10(DATA_DIR8, src);
10470
+ const dstPath = join10(DATA_DIR8, dst);
10471
+ if (existsSync9(srcPath) && !existsSync9(dstPath)) {
10472
+ copyFileSync(srcPath, dstPath);
10473
+ result.created.push(dstPath);
10474
+ }
10475
+ }
10476
+ if (isTTY)
10477
+ console.log(` ${c2.green}✓${c2.reset} Templates activated`);
10478
+ const claudeLocalPath = join10(DATA_DIR8, "CLAUDE.local.md");
10479
+ if (!existsSync9(claudeLocalPath)) {
10480
+ const localContent = [
10481
+ `<!-- PRIVATE LAYER - This file is gitignored and never shared -->`,
10482
+ ``,
10483
+ `# ${profile.name || "My"}'s Datacore`,
10484
+ ``,
10485
+ profile.role ? `Role: ${profile.role}` : "",
10486
+ ``,
10487
+ `## My Workflow`,
10488
+ ``,
10489
+ `<!-- Add your personal workflow notes, preferences, and shortcuts here. -->`,
10490
+ `<!-- This file is gitignored and only visible to your local Claude Code. -->`,
10491
+ ``,
10492
+ `## Custom Context`,
10493
+ ``,
10494
+ `<!-- Any private context that helps Claude assist you better: -->`,
10495
+ `<!-- - Project abbreviations and shorthand -->`,
10496
+ `<!-- - Personal communication preferences -->`,
10497
+ `<!-- - Domain expertise and background -->`,
10498
+ ``
10499
+ ].filter(Boolean).join(`
9353
10500
  `);
9354
- writeFileSync4(join7(DATACORE_DIR, "registry", "commands.yaml"), `# Command Registry
9355
- commands: []
10501
+ writeFileSync5(claudeLocalPath, localContent);
10502
+ result.created.push(claudeLocalPath);
10503
+ if (isTTY)
10504
+ console.log(` ${c2.green}✓${c2.reset} CLAUDE.local.md created (your private AI context)`);
10505
+ }
10506
+ const settingsLocalPath = join10(DATACORE_DIR, "settings.local.yaml");
10507
+ if (existsSync9(DATACORE_DIR) && !existsSync9(settingsLocalPath)) {
10508
+ const settingsContent = [
10509
+ `# Personal Settings Overrides (gitignored)`,
10510
+ `# See settings.yaml for all available options`,
10511
+ ``,
10512
+ `editor:`,
10513
+ ` open_markdown_on_generate: false`,
10514
+ ``,
10515
+ `sync:`,
10516
+ ` pull_on_today: true`,
10517
+ ` push_on_wrap_up: true`,
10518
+ ``
10519
+ ].join(`
9356
10520
  `);
9357
- if (isTTY) {
9358
- console.log();
9359
- console.log(` ${c2.green}✓${c2.reset} Created settings.yaml`);
9360
- console.log(` ${c2.dim}Customize in settings.local.yaml${c2.reset}`);
9361
- }
9362
- } else {
10521
+ writeFileSync5(settingsLocalPath, settingsContent);
10522
+ result.created.push(settingsLocalPath);
9363
10523
  if (isTTY)
9364
- console.log(` ${c2.green}✓${c2.reset} Found existing .datacore/`);
10524
+ console.log(` ${c2.green}✓${c2.reset} settings.local.yaml created (your preferences)`);
9365
10525
  }
9366
10526
  if (isTTY)
9367
10527
  console.log();
9368
- op.completeStep("create_datacore_dir");
9369
- op.addStep("create_personal_space");
9370
- op.startStep("create_personal_space");
10528
+ op.completeStep("second_brain");
10529
+ op.addStep("modules");
10530
+ op.startStep("modules");
9371
10531
  if (isTTY) {
9372
- section("Step 4: Creating Personal Space");
9373
- console.log(` ${c2.dim}Your personal GTD system and knowledge base.${c2.reset}`);
10532
+ section(`Step 6/${TOTAL_STEPS}: Modules`);
10533
+ console.log();
10534
+ console.log(` ${c2.dim}Modules extend Datacore with specialized capabilities. Each adds${c2.reset}`);
10535
+ console.log(` ${c2.dim}new AI agents and /commands you can use in Claude Code.${c2.reset}`);
9374
10536
  console.log();
9375
10537
  }
9376
- const existingSpaces = listSpaces();
9377
- const hasPersonal = existingSpaces.some((s) => s.number === 0);
9378
- if (!hasPersonal) {
9379
- const space = createSpace("personal", "personal");
9380
- result.created.push(space.path);
9381
- result.spacesCreated.push(space.name);
9382
- if (isTTY) {
9383
- console.log(` ${c2.green}✓${c2.reset} Created 0-personal/`);
9384
- console.log();
9385
- console.log(` ${c2.bold}GTD System (org/):${c2.reset}`);
9386
- console.log(` ${c2.cyan}inbox.org${c2.reset} ${c2.dim}- Capture everything here${c2.reset}`);
9387
- console.log(` ${c2.cyan}next_actions.org${c2.reset} ${c2.dim}- Tasks by focus area${c2.reset}`);
9388
- console.log(` ${c2.cyan}nightshift.org${c2.reset} ${c2.dim}- AI task queue (overnight)${c2.reset}`);
9389
- console.log(` ${c2.cyan}habits.org${c2.reset} ${c2.dim}- Daily/weekly routines${c2.reset}`);
9390
- console.log(` ${c2.cyan}someday.org${c2.reset} ${c2.dim}- Future ideas${c2.reset}`);
9391
- console.log();
9392
- console.log(` ${c2.bold}Knowledge:${c2.reset}`);
9393
- console.log(` ${c2.cyan}notes/${c2.reset} ${c2.dim}- Personal wiki (Obsidian)${c2.reset}`);
9394
- console.log(` ${c2.cyan}3-knowledge/${c2.reset} ${c2.dim}- Zettelkasten structure${c2.reset}`);
9395
- console.log();
9396
- console.log(` ${c2.bold}AI Delegation:${c2.reset}`);
9397
- console.log(` ${c2.dim}Tag tasks with :AI: to delegate:${c2.reset}`);
9398
- console.log(` ${c2.dim}:AI:research: :AI:content: :AI:data: :AI:pm:${c2.reset}`);
10538
+ const modulesDir = join10(DATACORE_DIR, "modules");
10539
+ if (existsSync9(DATACORE_DIR)) {
10540
+ mkdirSync6(modulesDir, { recursive: true });
10541
+ }
10542
+ const installedNames = listModules().map((m) => m.name);
10543
+ const allModules = AVAILABLE_MODULES;
10544
+ let modulesToInstall = [];
10545
+ if (interactive) {
10546
+ console.log(` ${c2.dim}All modules are selected by default. Deselect any you don't need:${c2.reset}`);
10547
+ console.log();
10548
+ const selected = new Set(allModules.map((_, i) => i));
10549
+ for (let i = 0;i < allModules.length; i++) {
10550
+ const mod = allModules[i];
10551
+ const isInstalled = installedNames.includes(mod.name);
10552
+ const coreLabel = mod.core ? ` ${c2.dim}(core)${c2.reset}` : "";
10553
+ const installedLabel = isInstalled ? ` ${c2.dim}(installed)${c2.reset}` : "";
10554
+ const num = String(i + 1).padStart(2, " ");
10555
+ console.log(` [x] ${c2.cyan}${num}${c2.reset}. ${c2.bold}${mod.name.padEnd(12)}${c2.reset} ${mod.description}${coreLabel}${installedLabel}`);
10556
+ }
10557
+ console.log();
10558
+ const removeInput = await prompt(" Enter numbers to REMOVE, or press Enter to install all", "");
10559
+ if (removeInput) {
10560
+ const nums = removeInput.split(/[,\s]+/).map((s) => parseInt(s.trim(), 10));
10561
+ for (const n of nums) {
10562
+ if (n >= 1 && n <= allModules.length) {
10563
+ const mod = allModules[n - 1];
10564
+ if (!mod.core) {
10565
+ selected.delete(n - 1);
10566
+ }
10567
+ }
10568
+ }
9399
10569
  }
10570
+ modulesToInstall = allModules.filter((_, i) => selected.has(i));
9400
10571
  } else {
9401
- if (isTTY) {
9402
- console.log(` ${c2.green}✓${c2.reset} Found existing personal space`);
10572
+ modulesToInstall = [...allModules];
10573
+ }
10574
+ let installCount = 0;
10575
+ for (const mod of modulesToInstall) {
10576
+ if (installedNames.includes(mod.name)) {
10577
+ installCount++;
10578
+ continue;
10579
+ }
10580
+ const spinner = isTTY ? new Spinner(`Installing ${mod.name}...`) : null;
10581
+ spinner?.start();
10582
+ try {
10583
+ await sleep(100);
10584
+ const info2 = installModule(mod.repo);
10585
+ result.modulesInstalled.push(mod.name);
10586
+ installCount++;
10587
+ const postInstall = runModulePostInstall(info2.path);
10588
+ if (postInstall.ran && postInstall.success) {
10589
+ spinner?.succeed(`${mod.name} - dependencies installed (${postInstall.type})`);
10590
+ } else if (postInstall.ran && !postInstall.success) {
10591
+ spinner?.succeed(`${mod.name}`);
10592
+ if (isTTY)
10593
+ console.log(` ${c2.yellow}⚠${c2.reset} ${c2.dim}Dependency install failed (${postInstall.type})${c2.reset}`);
10594
+ } else {
10595
+ spinner?.succeed(mod.name);
10596
+ }
10597
+ } catch (err) {
10598
+ spinner?.fail(`${mod.name} - ${err.message}`);
10599
+ result.warnings.push(`Module ${mod.name} failed to install: ${err.message}`);
10600
+ }
10601
+ }
10602
+ if (isTTY) {
10603
+ console.log();
10604
+ console.log(` ${c2.green}${installCount} modules installed.${c2.reset}`);
10605
+ console.log();
10606
+ }
10607
+ op.completeStep("modules");
10608
+ op.addStep("finalize");
10609
+ op.startStep("finalize");
10610
+ if (isTTY) {
10611
+ section(`Step 7/${TOTAL_STEPS}: Finalize`);
10612
+ console.log();
10613
+ }
10614
+ const contextMerge = join10(DATACORE_DIR, "lib", "context_merge.py");
10615
+ if (existsSync9(contextMerge)) {
10616
+ const spinner = isTTY ? new Spinner("Building CLAUDE.md from layers...") : null;
10617
+ spinner?.start();
10618
+ if (runArgs("python3", [contextMerge, "rebuild", "--path", DATA_DIR8, "--all"])) {
10619
+ spinner?.succeed("CLAUDE.md built from layers (all spaces)");
10620
+ } else {
10621
+ spinner?.fail("CLAUDE.md build failed (can rebuild later)");
10622
+ result.warnings.push("Could not build CLAUDE.md");
10623
+ }
10624
+ }
10625
+ const zettelDb = join10(DATACORE_DIR, "lib", "zettel_db.py");
10626
+ if (existsSync9(zettelDb)) {
10627
+ const spinner = isTTY ? new Spinner("Initializing knowledge database...") : null;
10628
+ spinner?.start();
10629
+ if (runArgs("python3", [zettelDb, "init-all"], { cwd: DATA_DIR8 })) {
10630
+ spinner?.succeed("Knowledge database initialized");
10631
+ } else {
10632
+ spinner?.fail("Database init failed (non-fatal)");
10633
+ result.warnings.push("Database initialization failed");
10634
+ }
10635
+ }
10636
+ if (existsSync9(DATACORE_DIR)) {
10637
+ const stateDir = join10(DATACORE_DIR, "state");
10638
+ const envDir = join10(DATACORE_DIR, "env");
10639
+ mkdirSync6(stateDir, { recursive: true });
10640
+ mkdirSync6(envDir, { recursive: true });
10641
+ if (!existsSync9(join10(stateDir, ".gitkeep")))
10642
+ writeFileSync5(join10(stateDir, ".gitkeep"), "");
10643
+ if (!existsSync9(join10(envDir, ".gitkeep")))
10644
+ writeFileSync5(join10(envDir, ".gitkeep"), "");
10645
+ if (isTTY)
10646
+ console.log(` ${c2.green}✓${c2.reset} Runtime directories ready`);
10647
+ }
10648
+ const claudeDir = join10(DATA_DIR8, ".claude");
10649
+ if (!existsSync9(claudeDir) && existsSync9(DATACORE_DIR)) {
10650
+ try {
10651
+ symlinkSync(DATACORE_DIR, claudeDir);
10652
+ result.created.push(claudeDir);
10653
+ } catch {}
10654
+ }
10655
+ const syncScript = join10(DATA_DIR8, "sync");
10656
+ if (existsSync9(syncScript)) {
10657
+ if (platform2 !== "windows") {
10658
+ runArgs("chmod", ["+x", syncScript]);
10659
+ }
10660
+ if (isTTY)
10661
+ console.log(` ${c2.green}✓${c2.reset} Sync script configured`);
10662
+ }
10663
+ const installYaml = join10(DATA_DIR8, "install.yaml");
10664
+ if (existsSync9(installYaml) || existsSync9(DATACORE_DIR)) {
10665
+ try {
10666
+ const allSpaces = listSpaces();
10667
+ const allInstalledModules = listModules();
10668
+ const spacesYaml = allSpaces.filter((s) => s.type !== "personal").map((s) => ` ${s.name}:
10669
+ path: ${s.name}`).join(`
10670
+ `);
10671
+ const modulesYaml = allInstalledModules.map((m) => ` - ${m.name}`).join(`
10672
+ `);
10673
+ const yamlContent = [
10674
+ `# Datacore Installation Manifest`,
10675
+ `# Generated by: datacore init`,
10676
+ `# Date: ${new Date().toISOString().split("T")[0]}`,
10677
+ ``,
10678
+ `meta:`,
10679
+ ` name: "${profile.name ? `${profile.name}'s Datacore` : "My Datacore"}"`,
10680
+ ` root: "${DATA_DIR8}"`,
10681
+ ` version: 1.0.0`,
10682
+ profile.role ? ` role: "${profile.role}"` : null,
10683
+ ` use_case: ${profile.useCase}`,
10684
+ ``,
10685
+ `modules:`,
10686
+ modulesYaml || " []",
10687
+ ``,
10688
+ `personal:`,
10689
+ ` path: 0-personal`,
10690
+ ``,
10691
+ `spaces:`,
10692
+ spacesYaml || " {}",
10693
+ ``
10694
+ ].filter((line) => line !== null).join(`
10695
+ `);
10696
+ writeFileSync5(installYaml, yamlContent);
10697
+ if (isTTY)
10698
+ console.log(` ${c2.green}✓${c2.reset} install.yaml saved`);
10699
+ } catch {
10700
+ result.warnings.push("Could not update install.yaml");
9403
10701
  }
9404
10702
  }
10703
+ try {
10704
+ const snapshot = createSnapshot();
10705
+ saveSnapshot(snapshot);
10706
+ if (isTTY)
10707
+ console.log(` ${c2.green}✓${c2.reset} Snapshot created (datacore.lock.yaml)`);
10708
+ } catch {
10709
+ result.warnings.push("Could not create snapshot");
10710
+ }
9405
10711
  if (isTTY)
9406
10712
  console.log();
9407
- op.completeStep("create_personal_space");
10713
+ op.completeStep("finalize");
10714
+ op.addStep("import_data");
10715
+ op.startStep("import_data");
10716
+ const backgroundJobs = [];
10717
+ const canBackgroundIngest = commandExists3("datacore") && commandExists3("claude");
9408
10718
  if (interactive) {
9409
- section("Step 5: Additional Spaces");
9410
- console.log(` ${c2.dim}Spaces separate different areas of your life (work, projects, teams).${c2.reset}`);
9411
- console.log(` ${c2.dim}Each space is a separate git repo with its own GTD system.${c2.reset}`);
10719
+ section(`Step 8/${TOTAL_STEPS}: Import Your Data`);
9412
10720
  console.log();
9413
- const wantMoreSpaces = await confirm("Would you like to create additional spaces?", false);
9414
- if (wantMoreSpaces) {
9415
- let creating = true;
9416
- while (creating) {
9417
- const name = await prompt('Space name (e.g., "work", "acme-corp")');
9418
- if (!name) {
9419
- creating = false;
9420
- continue;
10721
+ console.log(` ${c2.dim}Your second brain works best when it has your existing knowledge.${c2.reset}`);
10722
+ console.log(` ${c2.dim}You can import data now or do it later with 'datacore ingest'.${c2.reset}`);
10723
+ console.log();
10724
+ let importing = true;
10725
+ let totalImported = 0;
10726
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10727
+ mkdirSync6(inboxDir, { recursive: true });
10728
+ const IMPORT_SKIP = 4;
10729
+ while (importing) {
10730
+ console.log(` ${c2.dim}Common sources to import:${c2.reset}`);
10731
+ console.log(` ${c2.cyan}1${c2.reset}) ChatGPT conversation exports (JSON)`);
10732
+ console.log(` ${c2.cyan}2${c2.reset}) Documents folder (PDFs, Word docs, markdown)`);
10733
+ console.log(` ${c2.cyan}3${c2.reset}) Existing notes (Obsidian, Notion export, etc.)`);
10734
+ console.log(` ${c2.cyan}4${c2.reset}) Skip for now`);
10735
+ console.log();
10736
+ const choice = await prompt(" What would you like to import?", "4");
10737
+ const choiceNum = parseInt(choice, 10);
10738
+ if (choiceNum === IMPORT_SKIP || !choice) {
10739
+ if (totalImported === 0) {
10740
+ console.log();
10741
+ console.log(` ${c2.dim}No problem! You can import data anytime:${c2.reset}`);
10742
+ console.log(` ${c2.dim}datacore ingest ~/path/to/files${c2.reset}`);
10743
+ console.log(` ${c2.dim}datacore ingest ~/Downloads/chatgpt-export.json${c2.reset}`);
9421
10744
  }
9422
- try {
9423
- const space = createSpace(name, "team");
9424
- result.created.push(space.path);
9425
- result.spacesCreated.push(space.name);
9426
- console.log(` ${c2.green}✓${c2.reset} Created ${space.name}/`);
9427
- } catch (err) {
9428
- console.log(` ${c2.red}✗${c2.reset} ${err.message}`);
10745
+ importing = false;
10746
+ continue;
10747
+ }
10748
+ if (choiceNum === 1) {
10749
+ const chatPath = await prompt(" Path to ChatGPT export");
10750
+ const chatResolved = chatPath?.startsWith("~") ? join10(process.env.HOME || "", chatPath.slice(1)) : chatPath;
10751
+ if (chatResolved && existsSync9(chatResolved)) {
10752
+ const resolvedPath = chatResolved;
10753
+ const spinner = new Spinner("Processing ChatGPT export...");
10754
+ spinner.start();
10755
+ try {
10756
+ const content = readFileSync5(resolvedPath, "utf-8");
10757
+ const data = JSON.parse(content);
10758
+ const count = Array.isArray(data) ? data.length : 1;
10759
+ const destName = `chatgpt-export-${Date.now()}.json`;
10760
+ const destPath = join10(inboxDir, destName);
10761
+ copyFileSync(resolvedPath, destPath);
10762
+ if (canBackgroundIngest) {
10763
+ const job = spawnBackground("datacore", ["ingest", destPath], "ingest");
10764
+ if (job) {
10765
+ backgroundJobs.push(job);
10766
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: destPath });
10767
+ bgOp.start();
10768
+ spinner.succeed(`Found ${count} conversations - queued for background processing`);
10769
+ } else {
10770
+ spinner.succeed(`Found ${count} conversations - copied to inbox`);
10771
+ console.log(` ${c2.dim}Process with: datacore ingest ${destPath}${c2.reset}`);
10772
+ }
10773
+ } else {
10774
+ spinner.succeed(`Found ${count} conversations - copied to inbox`);
10775
+ console.log(` ${c2.dim}Process with /ingest in Claude Code for full knowledge extraction${c2.reset}`);
10776
+ }
10777
+ totalImported += count;
10778
+ } catch {
10779
+ spinner.fail("Could not parse ChatGPT export");
10780
+ try {
10781
+ const fallbackDest = join10(inboxDir, basename5(resolvedPath));
10782
+ copyFileSync(resolvedPath, fallbackDest);
10783
+ if (canBackgroundIngest) {
10784
+ const job = spawnBackground("datacore", ["ingest", fallbackDest], "ingest");
10785
+ if (job) {
10786
+ backgroundJobs.push(job);
10787
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: fallbackDest });
10788
+ bgOp.start();
10789
+ console.log(` ${c2.dim}File queued for background processing${c2.reset}`);
10790
+ } else {
10791
+ console.log(` ${c2.dim}File copied to inbox for later processing${c2.reset}`);
10792
+ }
10793
+ } else {
10794
+ console.log(` ${c2.dim}File copied to inbox for later processing${c2.reset}`);
10795
+ }
10796
+ } catch {}
10797
+ }
10798
+ } else {
10799
+ console.log(` ${c2.yellow}⚠${c2.reset} File not found: ${chatPath}`);
10800
+ }
10801
+ } else if (choiceNum === 2 || choiceNum === 3) {
10802
+ const label = choiceNum === 2 ? "documents" : "notes";
10803
+ const sourcePath = await prompt(` Path to ${label}`);
10804
+ if (sourcePath) {
10805
+ const resolvedPath = sourcePath.startsWith("~") ? join10(process.env.HOME || "", sourcePath.slice(1)) : sourcePath;
10806
+ if (existsSync9(resolvedPath)) {
10807
+ const spinner = new Spinner(`Scanning ${resolvedPath}...`);
10808
+ spinner.start();
10809
+ const stats = statSync2(resolvedPath);
10810
+ if (stats.isDirectory()) {
10811
+ const { total, byExt } = countFiles(resolvedPath);
10812
+ const extSummary = Object.entries(byExt).sort(([, a], [, b]) => b - a).slice(0, 4).map(([ext, count]) => `${count} .${ext}`).join(", ");
10813
+ spinner.succeed(`Found ${total} files (${extSummary})`);
10814
+ const destDir = join10(inboxDir, basename5(resolvedPath));
10815
+ const copySpinner = new Spinner("Copying to inbox...");
10816
+ copySpinner.start();
10817
+ try {
10818
+ cpSync(resolvedPath, destDir, { recursive: true });
10819
+ if (canBackgroundIngest) {
10820
+ const job = spawnBackground("datacore", ["ingest", destDir], "ingest");
10821
+ if (job) {
10822
+ backgroundJobs.push(job);
10823
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: destDir });
10824
+ bgOp.start();
10825
+ copySpinner.succeed(`${total} files queued for background processing`);
10826
+ } else {
10827
+ copySpinner.succeed(`${total} files copied to inbox`);
10828
+ console.log(` ${c2.dim}Process with: datacore ingest ${destDir}${c2.reset}`);
10829
+ }
10830
+ } else {
10831
+ copySpinner.succeed(`${total} files copied to inbox`);
10832
+ console.log(` ${c2.dim}Process with /ingest in Claude Code for full knowledge extraction${c2.reset}`);
10833
+ }
10834
+ totalImported += total;
10835
+ } catch {
10836
+ copySpinner.fail("Copy failed");
10837
+ }
10838
+ } else {
10839
+ const singleDest = join10(inboxDir, basename5(resolvedPath));
10840
+ copyFileSync(resolvedPath, singleDest);
10841
+ if (canBackgroundIngest) {
10842
+ const job = spawnBackground("datacore", ["ingest", singleDest], "ingest");
10843
+ if (job) {
10844
+ backgroundJobs.push(job);
10845
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: singleDest });
10846
+ bgOp.start();
10847
+ spinner.succeed("File queued for background processing");
10848
+ } else {
10849
+ spinner.succeed("File copied to inbox");
10850
+ console.log(` ${c2.dim}Process with: datacore ingest ${singleDest}${c2.reset}`);
10851
+ }
10852
+ } else {
10853
+ spinner.succeed("File copied to inbox");
10854
+ }
10855
+ totalImported++;
10856
+ }
10857
+ } else {
10858
+ console.log(` ${c2.yellow}⚠${c2.reset} Path not found: ${sourcePath}`);
10859
+ }
9429
10860
  }
9430
- creating = await confirm("Create another space?", false);
9431
10861
  }
10862
+ console.log();
10863
+ importing = await confirm(" Import more?", false);
10864
+ if (importing)
10865
+ console.log();
9432
10866
  }
9433
10867
  console.log();
9434
10868
  }
9435
- if (interactive) {
9436
- section("Step 6: Install Modules");
9437
- console.log(` ${c2.dim}Modules add features like overnight AI processing, CRM, and more.${c2.reset}`);
10869
+ op.completeStep("import_data");
10870
+ op.addStep("verification");
10871
+ op.startStep("verification");
10872
+ const claudeAvailable = commandExists3("claude");
10873
+ if (claudeAvailable && isTTY) {
10874
+ section(`Step 9/${TOTAL_STEPS}: Verification`);
9438
10875
  console.log();
9439
- const available = getAvailableModules();
9440
- if (available.length > 0) {
9441
- const wantModules = await confirm("Would you like to install modules?", true);
9442
- if (wantModules) {
9443
- console.log();
9444
- console.log(" Available modules:");
9445
- console.log();
9446
- for (let i = 0;i < available.length; i++) {
9447
- const mod = available[i];
9448
- console.log(` ${c2.cyan}${i + 1}${c2.reset}) ${c2.bold}${mod.name}${c2.reset} - ${mod.description}`);
9449
- for (const feature of mod.features) {
9450
- console.log(` ${c2.dim} ${feature}${c2.reset}`);
9451
- }
9452
- console.log();
9453
- }
9454
- const choices = await prompt('Enter numbers to install (comma-separated, or "all", or "none")');
9455
- let toInstall = [];
9456
- if (choices.toLowerCase() === "all") {
9457
- toInstall = available.map((m) => m.name);
9458
- } else if (choices && choices.toLowerCase() !== "none") {
9459
- const nums = choices.split(",").map((s) => parseInt(s.trim(), 10));
9460
- toInstall = nums.filter((n) => n >= 1 && n <= available.length).map((n) => available[n - 1].name);
10876
+ console.log(` ${c2.dim}Running AI verification to check everything is configured correctly...${c2.reset}`);
10877
+ console.log();
10878
+ const spinner = new Spinner("Claude Code structural integrity check...");
10879
+ spinner.start();
10880
+ try {
10881
+ const agentResult = await invokeAgent({
10882
+ agent: "structural-integrity",
10883
+ params: { mode: "report", scope: "all" }
10884
+ }, { cwd: DATA_DIR8, timeout: 120000 });
10885
+ if (agentResult.success) {
10886
+ spinner.succeed("All checks passed");
10887
+ } else {
10888
+ spinner.fail("Verification completed with issues");
10889
+ if (agentResult.error) {
10890
+ result.warnings.push(`Verification: ${agentResult.error}`);
9461
10891
  }
9462
- for (const modName of toInstall) {
9463
- const mod = available.find((m) => m.name === modName);
9464
- if (!mod)
9465
- continue;
9466
- const spinner = new Spinner(`Installing ${modName}...`);
9467
- spinner.start();
9468
- try {
9469
- await sleep(100);
9470
- installModule(mod.repo);
9471
- spinner.succeed(`Installed ${modName}`);
9472
- result.modulesInstalled.push(modName);
9473
- } catch (err) {
9474
- spinner.fail(`Failed to install ${modName}: ${err.message}`);
9475
- result.warnings.push(`Failed to install ${modName}`);
9476
- }
10892
+ }
10893
+ } catch {
10894
+ spinner.fail("Verification skipped (Claude Code not responding)");
10895
+ result.warnings.push("Could not run verification - run /structural-integrity manually");
10896
+ }
10897
+ const allSpaces = listSpaces();
10898
+ const allInstalledModules = listModules();
10899
+ console.log();
10900
+ console.log(` ${c2.dim}Spaces: ${allSpaces.length} (${allSpaces.map((s) => s.name).join(", ")})${c2.reset}`);
10901
+ console.log(` ${c2.dim}Modules: ${allInstalledModules.length} installed${c2.reset}`);
10902
+ const inboxOrg = join10(DATA_DIR8, "0-personal", "org", "inbox.org");
10903
+ if (existsSync9(inboxOrg)) {
10904
+ try {
10905
+ const content = readFileSync5(inboxOrg, "utf-8");
10906
+ const todoCount = (content.match(/^\* TODO /gm) || []).length;
10907
+ if (todoCount > 0) {
10908
+ console.log(` ${c2.dim}GTD: ${todoCount} inbox items${c2.reset}`);
9477
10909
  }
10910
+ } catch {}
10911
+ }
10912
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10913
+ if (existsSync9(inboxDir)) {
10914
+ const { total } = countFiles(inboxDir);
10915
+ if (total > 0) {
10916
+ console.log(` ${c2.dim}Import: ${total} files in inbox${c2.reset}`);
9478
10917
  }
9479
- } else {
9480
- console.log(` ${c2.dim}No additional modules available to install.${c2.reset}`);
9481
10918
  }
9482
10919
  console.log();
9483
- }
9484
- op.addStep("create_claude_symlink");
9485
- op.startStep("create_claude_symlink");
9486
- if (isTTY) {
9487
- section("Step 7: Claude Code Integration");
9488
- console.log(` ${c2.dim}Connecting Datacore to Claude Code AI assistant.${c2.reset}`);
10920
+ } else if (isTTY) {
10921
+ section(`Step 9/${TOTAL_STEPS}: Verification`);
9489
10922
  console.log();
9490
- }
9491
- const claudeDir = join7(DATA_DIR5, ".claude");
9492
- if (!existsSync7(claudeDir)) {
9493
- try {
9494
- symlinkSync(DATACORE_DIR, claudeDir);
9495
- result.created.push(claudeDir);
9496
- if (isTTY)
9497
- console.log(` ${c2.green}✓${c2.reset} Created .claude -> .datacore symlink`);
9498
- } catch {
9499
- result.warnings.push("Could not create .claude symlink");
9500
- if (isTTY)
9501
- console.log(` ${c2.yellow}○${c2.reset} Could not create symlink`);
9502
- }
9503
- } else {
9504
- if (isTTY)
9505
- console.log(` ${c2.green}✓${c2.reset} Found existing .claude/`);
9506
- }
9507
- const claudeMd = join7(DATA_DIR5, "CLAUDE.md");
9508
- const claudeBaseMd = join7(DATA_DIR5, "CLAUDE.base.md");
9509
- if (!existsSync7(claudeMd) && !existsSync7(claudeBaseMd)) {
9510
- const allSpaces = listSpaces();
9511
- writeFileSync4(claudeBaseMd, `# Datacore
9512
-
9513
- AI-powered second brain built on GTD methodology.
9514
-
9515
- ## Quick Start
9516
-
9517
- \`\`\`bash
9518
- cd ~/Data && claude
9519
- \`\`\`
9520
-
9521
- ## Daily Workflow
9522
-
9523
- 1. **Morning**: Run \`/today\` for your daily briefing
9524
- 2. **Capture**: Add tasks to \`0-personal/org/inbox.org\`
9525
- 3. **Process**: Clear inbox, organize by context
9526
- 4. **Work**: Use \`:AI:\` tags to delegate tasks
9527
- 5. **Evening**: Run \`/tomorrow\` for wrap-up
9528
-
9529
- ## Commands
9530
-
9531
- - \`/today\` - Daily briefing with priorities
9532
- - \`/tomorrow\` - End-of-day wrap-up
9533
- - \`/sync\` - Sync all repos
9534
- - \`datacore doctor\` - Check system health
9535
-
9536
- ## Spaces
9537
-
9538
- ${allSpaces.map((s) => `- ${s.name}`).join(`
9539
- `) || "- 0-personal"}
9540
-
9541
- ## Documentation
9542
-
9543
- See .datacore/specs/ for detailed documentation.
9544
- `);
9545
- result.created.push(claudeBaseMd);
9546
- if (isTTY)
9547
- console.log(` ${c2.green}✓${c2.reset} Created CLAUDE.base.md`);
9548
- } else {
9549
- if (isTTY)
9550
- console.log(` ${c2.green}✓${c2.reset} Found existing CLAUDE.md`);
9551
- }
9552
- if (isTTY)
10923
+ console.log(` ${c2.yellow}○${c2.reset} Claude Code not available ${c2.dim}(skipping verification)${c2.reset}`);
10924
+ console.log(` ${c2.dim}Run /structural-integrity in Claude Code to verify later.${c2.reset}`);
9553
10925
  console.log();
9554
- op.completeStep("create_claude_symlink");
10926
+ }
10927
+ op.completeStep("verification");
9555
10928
  result.success = true;
9556
10929
  result.nextSteps = [
9557
- "cd ~/Data && claude",
9558
- "Add tasks to 0-personal/org/inbox.org",
9559
- "Run /today for your first daily briefing"
10930
+ `cd ${DATA_DIR8} && claude`,
10931
+ "Run /today for your first daily briefing",
10932
+ "Process inbox with /gtd-daily-start",
10933
+ "Run datacore doctor to check system health"
9560
10934
  ];
9561
10935
  if (isTTY) {
9562
10936
  console.log(INIT_COMPLETE);
9563
10937
  console.log();
9564
- console.log(` ${c2.bold}Setup Complete!${c2.reset}`);
10938
+ console.log(` ${c2.bold}Setup Complete${profile.name ? `, ${profile.name}` : ""}!${c2.reset}`);
9565
10939
  console.log();
9566
- if (result.spacesCreated.length > 0) {
9567
- console.log(` ${c2.green}Spaces created:${c2.reset} ${result.spacesCreated.join(", ")}`);
10940
+ const allSpaces = listSpaces();
10941
+ if (allSpaces.length > 0) {
10942
+ console.log(` ${c2.green}Your Datacore:${c2.reset}`);
10943
+ for (const s of allSpaces) {
10944
+ const icon = s.type === "personal" ? "\uD83D\uDC64" : "\uD83D\uDC65";
10945
+ console.log(` ${icon} ${s.name}`);
10946
+ }
10947
+ console.log();
10948
+ }
10949
+ const installedModules = listModules();
10950
+ if (installedModules.length > 0) {
10951
+ console.log(` ${c2.green}Modules:${c2.reset} ${installedModules.map((m) => m.name).join(", ")}`);
10952
+ console.log();
9568
10953
  }
9569
- if (result.modulesInstalled.length > 0) {
9570
- console.log(` ${c2.green}Modules installed:${c2.reset} ${result.modulesInstalled.join(", ")}`);
10954
+ const envDir = join10(DATACORE_DIR, "env");
10955
+ const envFiles = existsSync9(envDir) ? readdirSync3(envDir).filter((f) => f !== ".gitkeep") : [];
10956
+ if (envFiles.length === 0) {
10957
+ console.log(` ${c2.bold}API Keys:${c2.reset}`);
10958
+ console.log(` ${c2.dim}Some modules need API keys to function. Configure them in Claude Code:${c2.reset}`);
10959
+ console.log(` ${c2.dim}cd ~/Data && claude${c2.reset}`);
10960
+ console.log(` ${c2.dim}"Help me set up my API keys"${c2.reset}`);
10961
+ console.log(` ${c2.dim}Keys are stored in .datacore/env/ - gitignored, never leave your machine.${c2.reset}`);
10962
+ console.log();
9571
10963
  }
9572
10964
  if (result.warnings.length > 0) {
9573
10965
  console.log(` ${c2.yellow}Warnings:${c2.reset} ${result.warnings.length}`);
10966
+ for (const w of result.warnings) {
10967
+ console.log(` ${c2.dim}• ${w}${c2.reset}`);
10968
+ }
10969
+ console.log();
9574
10970
  }
9575
- console.log();
9576
10971
  console.log(` ${c2.bold}Get Started:${c2.reset}`);
9577
10972
  console.log();
9578
10973
  console.log(` ${c2.cyan}1.${c2.reset} cd ~/Data && claude`);
9579
10974
  console.log(` ${c2.dim}Start Claude Code in your Datacore directory${c2.reset}`);
9580
10975
  console.log();
9581
10976
  console.log(` ${c2.cyan}2.${c2.reset} Type ${c2.cyan}/today${c2.reset}`);
9582
- console.log(` ${c2.dim}Get your first daily briefing${c2.reset}`);
9583
- console.log();
9584
- console.log(` ${c2.cyan}3.${c2.reset} Add tasks to ${c2.cyan}0-personal/org/inbox.org${c2.reset}`);
9585
- console.log(` ${c2.dim}Your GTD capture inbox${c2.reset}`);
10977
+ console.log(` ${c2.dim}Get your first daily briefing with your imported data${c2.reset}`);
9586
10978
  console.log();
10979
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10980
+ const inboxFileCount = existsSync9(inboxDir) ? countFiles(inboxDir).total : 0;
10981
+ if (inboxFileCount > 0) {
10982
+ console.log(` ${c2.cyan}3.${c2.reset} Process your imports`);
10983
+ console.log(` ${c2.dim}${inboxFileCount} files in inbox - run /ingest in Claude Code${c2.reset}`);
10984
+ console.log();
10985
+ } else {
10986
+ console.log(` ${c2.cyan}3.${c2.reset} Process your inbox`);
10987
+ console.log(` ${c2.dim}Add tasks to org/inbox.org - try /gtd-daily-start${c2.reset}`);
10988
+ console.log();
10989
+ }
10990
+ if (backgroundJobs.length > 0) {
10991
+ console.log(` ${c2.green}Background imports:${c2.reset}`);
10992
+ for (const job of backgroundJobs) {
10993
+ if (verbose) {
10994
+ console.log(` ${c2.green}✓${c2.reset} ${job.label} processing (PID ${job.pid})`);
10995
+ console.log(` ${c2.dim}Log: ${job.logFile}${c2.reset}`);
10996
+ } else {
10997
+ console.log(` ${c2.green}✓${c2.reset} ${job.label} processing`);
10998
+ }
10999
+ }
11000
+ console.log(` ${c2.dim}Check progress: datacore status${c2.reset}`);
11001
+ console.log();
11002
+ }
11003
+ console.log(` ${c2.dim}Edit ~/Data/CLAUDE.local.md to teach Claude about you.${c2.reset}`);
9587
11004
  console.log(` ${c2.dim}Run 'datacore doctor' anytime to check system health.${c2.reset}`);
9588
11005
  console.log();
9589
11006
  }
@@ -9595,233 +11012,8 @@ See .datacore/specs/ for detailed documentation.
9595
11012
  return result;
9596
11013
  }
9597
11014
 
9598
- // src/lib/snapshot.ts
9599
- var import_yaml2 = __toESM(require_dist(), 1);
9600
- import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
9601
- import { execSync as execSync5 } from "child_process";
9602
- import { join as join8 } from "path";
9603
- var DATA_DIR6 = join8(process.env.HOME || "~", "Data");
9604
- var LOCK_FILE = join8(DATA_DIR6, "datacore.lock.yaml");
9605
- var CLI_VERSION = "1.0.6";
9606
- function getGitInfo(path) {
9607
- if (!existsSync8(join8(path, ".git"))) {
9608
- return {};
9609
- }
9610
- try {
9611
- const remote = execSync5("git remote get-url origin 2>/dev/null || true", {
9612
- cwd: path,
9613
- encoding: "utf-8"
9614
- }).trim() || undefined;
9615
- const commit = execSync5("git rev-parse HEAD 2>/dev/null || true", {
9616
- cwd: path,
9617
- encoding: "utf-8"
9618
- }).trim() || undefined;
9619
- const branch = execSync5("git branch --show-current 2>/dev/null || true", {
9620
- cwd: path,
9621
- encoding: "utf-8"
9622
- }).trim() || undefined;
9623
- return { remote, commit, branch };
9624
- } catch {
9625
- return {};
9626
- }
9627
- }
9628
- function createSnapshot(options = {}) {
9629
- const { includeSettings = false } = options;
9630
- const modules = [];
9631
- for (const mod of listModules()) {
9632
- const gitInfo = getGitInfo(mod.path);
9633
- modules.push({
9634
- name: mod.name,
9635
- source: gitInfo.remote || "local",
9636
- version: mod.version,
9637
- commit: gitInfo.commit,
9638
- branch: gitInfo.branch
9639
- });
9640
- }
9641
- const spaces = [];
9642
- for (const space of listSpaces()) {
9643
- const gitInfo = getGitInfo(space.path);
9644
- spaces.push({
9645
- name: space.name,
9646
- number: space.number,
9647
- type: space.type,
9648
- source: gitInfo.remote,
9649
- commit: gitInfo.commit
9650
- });
9651
- }
9652
- const deps = checkDependencies();
9653
- const dependencies = deps.filter((d) => d.installed && d.version).map((d) => ({
9654
- name: d.name,
9655
- version: d.version,
9656
- required: d.required
9657
- }));
9658
- const snapshot = {
9659
- version: "1.0",
9660
- created: new Date().toISOString(),
9661
- cliVersion: CLI_VERSION,
9662
- platform: `${process.platform}-${process.arch}`,
9663
- modules,
9664
- spaces,
9665
- dependencies
9666
- };
9667
- if (includeSettings) {
9668
- const settingsPath = join8(DATA_DIR6, ".datacore", "settings.yaml");
9669
- if (existsSync8(settingsPath)) {
9670
- try {
9671
- const content = readFileSync4(settingsPath, "utf-8");
9672
- snapshot.settings = import_yaml2.parse(content);
9673
- } catch {}
9674
- }
9675
- }
9676
- return snapshot;
9677
- }
9678
- function saveSnapshot(snapshot, path) {
9679
- const lockPath = path || LOCK_FILE;
9680
- const content = import_yaml2.stringify(snapshot, {
9681
- lineWidth: 0
9682
- });
9683
- writeFileSync5(lockPath, content);
9684
- return lockPath;
9685
- }
9686
- function loadSnapshot(path) {
9687
- const lockPath = path || LOCK_FILE;
9688
- if (!existsSync8(lockPath)) {
9689
- return null;
9690
- }
9691
- try {
9692
- const content = readFileSync4(lockPath, "utf-8");
9693
- return import_yaml2.parse(content);
9694
- } catch {
9695
- return null;
9696
- }
9697
- }
9698
- function diffSnapshot(snapshot) {
9699
- const current = createSnapshot();
9700
- const diff = {
9701
- modules: { added: [], removed: [], changed: [] },
9702
- spaces: { added: [], removed: [] },
9703
- dependencies: { changed: [] }
9704
- };
9705
- const currentModules = new Map(current.modules.map((m) => [m.name, m]));
9706
- const snapshotModules = new Map(snapshot.modules.map((m) => [m.name, m]));
9707
- for (const [name, mod] of currentModules) {
9708
- if (!snapshotModules.has(name)) {
9709
- diff.modules.added.push(name);
9710
- } else {
9711
- const expected = snapshotModules.get(name);
9712
- if (expected.commit && mod.commit && expected.commit !== mod.commit) {
9713
- diff.modules.changed.push({
9714
- name,
9715
- from: expected.commit.slice(0, 7),
9716
- to: mod.commit.slice(0, 7)
9717
- });
9718
- }
9719
- }
9720
- }
9721
- for (const name of snapshotModules.keys()) {
9722
- if (!currentModules.has(name)) {
9723
- diff.modules.removed.push(name);
9724
- }
9725
- }
9726
- const currentSpaces = new Set(current.spaces.map((s) => s.name));
9727
- const snapshotSpaces = new Set(snapshot.spaces.map((s) => s.name));
9728
- for (const name of currentSpaces) {
9729
- if (!snapshotSpaces.has(name)) {
9730
- diff.spaces.added.push(name);
9731
- }
9732
- }
9733
- for (const name of snapshotSpaces) {
9734
- if (!currentSpaces.has(name)) {
9735
- diff.spaces.removed.push(name);
9736
- }
9737
- }
9738
- const currentDeps = new Map(current.dependencies.map((d) => [d.name, d.version]));
9739
- for (const dep of snapshot.dependencies) {
9740
- const actualVersion = currentDeps.get(dep.name);
9741
- if (actualVersion && actualVersion !== dep.version) {
9742
- diff.dependencies.changed.push({
9743
- name: dep.name,
9744
- expected: dep.version,
9745
- actual: actualVersion
9746
- });
9747
- }
9748
- }
9749
- return diff;
9750
- }
9751
- function restoreFromSnapshot(snapshot, options = {}) {
9752
- const { modules = true, spaces = true, dryRun = false } = options;
9753
- const result = {
9754
- modulesInstalled: [],
9755
- modulesFailed: [],
9756
- spacesCreated: [],
9757
- warnings: []
9758
- };
9759
- if (modules) {
9760
- const currentModules = new Set(listModules().map((m) => m.name));
9761
- for (const mod of snapshot.modules) {
9762
- if (currentModules.has(mod.name)) {
9763
- continue;
9764
- }
9765
- if (mod.source === "local") {
9766
- result.warnings.push(`Module ${mod.name} was local, cannot restore`);
9767
- continue;
9768
- }
9769
- if (dryRun) {
9770
- result.modulesInstalled.push(mod.name);
9771
- continue;
9772
- }
9773
- try {
9774
- const modulesDir = join8(DATA_DIR6, ".datacore", "modules");
9775
- if (!existsSync8(modulesDir)) {
9776
- mkdirSync5(modulesDir, { recursive: true });
9777
- }
9778
- const modulePath = join8(modulesDir, mod.name);
9779
- execSync5(`git clone ${mod.source} "${modulePath}"`, { stdio: "pipe" });
9780
- if (mod.commit) {
9781
- execSync5(`git checkout ${mod.commit}`, { cwd: modulePath, stdio: "pipe" });
9782
- } else if (mod.branch) {
9783
- execSync5(`git checkout ${mod.branch}`, { cwd: modulePath, stdio: "pipe" });
9784
- }
9785
- result.modulesInstalled.push(mod.name);
9786
- } catch (err) {
9787
- result.modulesFailed.push({
9788
- name: mod.name,
9789
- error: err.message
9790
- });
9791
- }
9792
- }
9793
- }
9794
- if (spaces) {
9795
- const currentSpaces = new Set(listSpaces().map((s) => s.name));
9796
- for (const space of snapshot.spaces) {
9797
- if (currentSpaces.has(space.name)) {
9798
- continue;
9799
- }
9800
- if (dryRun) {
9801
- result.spacesCreated.push(space.name);
9802
- continue;
9803
- }
9804
- if (space.source) {
9805
- try {
9806
- const spacePath = join8(DATA_DIR6, space.name);
9807
- execSync5(`git clone ${space.source} "${spacePath}"`, { stdio: "pipe" });
9808
- if (space.commit) {
9809
- execSync5(`git checkout ${space.commit}`, { cwd: spacePath, stdio: "pipe" });
9810
- }
9811
- result.spacesCreated.push(space.name);
9812
- } catch (err) {
9813
- result.warnings.push(`Could not clone space ${space.name}: ${err.message}`);
9814
- }
9815
- } else {
9816
- result.warnings.push(`Space ${space.name} has no git source, skipping`);
9817
- }
9818
- }
9819
- }
9820
- return result;
9821
- }
9822
-
9823
11015
  // src/index.ts
9824
- var VERSION = "1.0.6";
11016
+ var VERSION = "1.0.7";
9825
11017
  var args = process.argv.slice(2);
9826
11018
  var parsed = parseArgs(args);
9827
11019
  async function handleMeta(command, cmdArgs, flags, format) {
@@ -9832,17 +11024,21 @@ async function handleMeta(command, cmdArgs, flags, format) {
9832
11024
  case "init": {
9833
11025
  if (isInitialized() && !flags.force) {
9834
11026
  if (format === "json") {
9835
- output({ success: true, message: "Datacore already initialized" }, format);
11027
+ output({ success: true, message: "Datacore already initialized", hint: "Use --force to re-initialize or run datacore doctor" }, format);
9836
11028
  } else {
9837
- info("Datacore already initialized");
9838
- info("Use --force to re-initialize");
11029
+ info("Datacore already initialized at ~/Data");
11030
+ info("Run datacore doctor to check health");
11031
+ info("Run datacore module list to see modules");
11032
+ info("Use --force to re-run the setup wizard");
9839
11033
  }
9840
11034
  break;
9841
11035
  }
9842
11036
  const result = await initDatacore({
9843
11037
  nonInteractive: flags.yes === true,
9844
11038
  skipChecks: flags["skip-checks"] === true,
9845
- stream: format === "human"
11039
+ stream: format === "human",
11040
+ verbose: flags.verbose === true,
11041
+ force: flags.force === true
9846
11042
  });
9847
11043
  if (format === "json") {
9848
11044
  output(result, format);
@@ -9895,7 +11091,7 @@ async function handleMeta(command, cmdArgs, flags, format) {
9895
11091
  for (const dep of result.dependencies) {
9896
11092
  const status = dep.installed ? "✓" : "✗";
9897
11093
  const version = dep.version ? ` (${dep.version})` : "";
9898
- const required = dep.required ? "" : " (optional)";
11094
+ const required = dep.required ? "" : " (recommended)";
9899
11095
  console.log(` ${status} ${dep.name}${version}${required}`);
9900
11096
  if (!dep.installed && dep.installCommand) {
9901
11097
  console.log(` Install: ${dep.installCommand}`);
@@ -10252,8 +11448,8 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10252
11448
  throw new CLIError("ERR_INVALID_ARGUMENT", "Missing task description", 'Usage: datacore nightshift queue "Research topic X"');
10253
11449
  }
10254
11450
  const { existsSync: existsSync10, appendFileSync } = await import("fs");
10255
- const { join: join10 } = await import("path");
10256
- const inboxPath = join10(process.env.HOME || "~", "Data", "0-personal", "org", "inbox.org");
11451
+ const { join: join11 } = await import("path");
11452
+ const inboxPath = join11(process.env.HOME || "~", "Data", "0-personal", "org", "inbox.org");
10257
11453
  if (!existsSync10(inboxPath)) {
10258
11454
  throw new CLIError("ERR_NOT_FOUND", "inbox.org not found. Run datacore init first.");
10259
11455
  }
@@ -10277,13 +11473,13 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10277
11473
  break;
10278
11474
  }
10279
11475
  case "cron": {
10280
- const { execSync: execSync6, exec: execAsync } = await import("child_process");
11476
+ const { execSync: execSync5, exec: execAsync } = await import("child_process");
10281
11477
  switch (action) {
10282
11478
  case "install": {
10283
11479
  info("Installing cron jobs...");
10284
11480
  let currentCron = "";
10285
11481
  try {
10286
- currentCron = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11482
+ currentCron = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10287
11483
  } catch {}
10288
11484
  if (currentCron.includes("# Datacore")) {
10289
11485
  if (!flags.force) {
@@ -10306,7 +11502,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10306
11502
  `;
10307
11503
  const newCron = currentCron.trim() + `
10308
11504
  ` + datacoreCron;
10309
- execSync6(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
11505
+ execSync5(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
10310
11506
  success("Cron jobs installed");
10311
11507
  info(" 8:00 AM weekdays - Daily briefing");
10312
11508
  info(" 6:00 PM weekdays - Evening wrap-up");
@@ -10316,7 +11512,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10316
11512
  case "status": {
10317
11513
  let crontab = "";
10318
11514
  try {
10319
- crontab = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11515
+ crontab = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10320
11516
  } catch {}
10321
11517
  const datacoreJobs = crontab.split(`
10322
11518
  `).filter((line) => line.includes("datacore") && !line.startsWith("#"));
@@ -10339,7 +11535,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10339
11535
  info("Removing cron jobs...");
10340
11536
  let currentCron = "";
10341
11537
  try {
10342
- currentCron = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11538
+ currentCron = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10343
11539
  } catch {
10344
11540
  info("No cron jobs to remove");
10345
11541
  break;
@@ -10348,9 +11544,9 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10348
11544
  `).filter((line) => !line.includes("# Datacore") && !line.includes("datacore")).join(`
10349
11545
  `).trim();
10350
11546
  if (newCron) {
10351
- execSync6(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
11547
+ execSync5(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
10352
11548
  } else {
10353
- execSync6("crontab -r", { stdio: "pipe" });
11549
+ execSync5("crontab -r", { stdio: "pipe" });
10354
11550
  }
10355
11551
  success("Cron jobs removed");
10356
11552
  break;