@datacore-one/cli 1.0.5 → 1.0.7

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 +2145 -953
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6980,6 +6980,441 @@ var require_dist = __commonJS((exports) => {
6980
6980
  exports.visitAsync = visit.visitAsync;
6981
6981
  });
6982
6982
 
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
6991
+ });
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 [];
6997
+ }
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"))
7016
+ });
7017
+ }
7018
+ spaces.sort((a, b) => a.number - b.number);
7019
+ return spaces;
7020
+ }
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;
7031
+ }
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;
7038
+ }
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}`);
7046
+ }
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 });
7095
+ }
7096
+ if (type === "personal") {
7097
+ writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
7098
+ #+FILETAGS: :inbox:
7099
+
7100
+ Capture everything here. Process daily to zero.
7101
+
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:
7108
+
7109
+ Tasks organized by focus area. Tag with :AI: to delegate to agents.
7110
+
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:
7129
+
7130
+ AI tasks queued for overnight execution. Managed by /tomorrow command.
7131
+
7132
+ * Queue
7133
+ `);
7134
+ writeFileSync2(join3(spacePath, "org", "habits.org"), `#+TITLE: Habits
7135
+ #+FILETAGS: :habits:
7136
+
7137
+ Recurring behaviors and routines. Track with org-habit.
7138
+
7139
+ * Daily
7140
+ * Weekly
7141
+ * Monthly
7142
+ `);
7143
+ writeFileSync2(join3(spacePath, "org", "someday.org"), `#+TITLE: Someday/Maybe
7144
+ #+FILETAGS: :someday:
7145
+
7146
+ Ideas and projects for the future. Review monthly.
7147
+
7148
+ * Someday
7149
+ * Maybe
7150
+ `);
7151
+ writeFileSync2(join3(spacePath, "org", "archive.org"), `#+TITLE: Archive
7152
+ #+FILETAGS: :archive:
7153
+
7154
+ Completed and canceled tasks. Searchable history.
7155
+
7156
+ * Archived Tasks
7157
+ `);
7158
+ } else {
7159
+ writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
7160
+ #+FILETAGS: :inbox:
7161
+
7162
+ * Capture items here
7163
+ `);
7164
+ writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
7165
+ #+FILETAGS: :tasks:
7166
+
7167
+ * Tasks
7168
+ ** TODO items go here
7169
+ `);
7170
+ }
7171
+ if (type === "personal") {
7172
+ writeFileSync2(join3(spacePath, "_index.md"), `# Personal Space
7173
+
7174
+ Your personal knowledge base and GTD system.
7175
+
7176
+ ## GTD Workflow
7177
+
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
7183
+
7184
+ ## Org Files (GTD)
7185
+
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 |
7194
+
7195
+ ## Knowledge
7196
+
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) |
7202
+
7203
+ ## Other
7204
+
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}
7212
+
7213
+ Team space.
7214
+
7215
+ ## Structure
7216
+
7217
+ - \`org/\` - Task coordination
7218
+ - \`1-tracks/\` - Department work
7219
+ - \`2-projects/\` - Code repositories
7220
+ - \`3-knowledge/\` - Shared knowledge
7221
+ - \`4-archive/\` - Historical content
7222
+
7223
+ ## Quick Links
7224
+
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
7233
+
7234
+ Personal GTD system and knowledge base (per DIP-0009).
7235
+
7236
+ ## GTD Files
7237
+
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 |
7246
+
7247
+ ## Task States
7248
+
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) |
7256
+
7257
+ ## AI Delegation
7258
+
7259
+ Tag tasks with \`:AI:\` to delegate to agents:
7260
+
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 |
7267
+
7268
+ ## Knowledge Structure
7269
+
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 |
7276
+
7277
+ ## Daily Workflow
7278
+
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
7284
+
7285
+ ## See Also
7286
+
7287
+ Parent: ~/Data/CLAUDE.md
7288
+ `);
7289
+ } else {
7290
+ writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# ${name} Space
7291
+
7292
+ Team space for ${name}.
7293
+
7294
+ ## Structure
7295
+
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
7304
+
7305
+ Successful approaches to remember.
7306
+ `);
7307
+ writeFileSync2(join3(spacePath, ".datacore", "learning", "corrections.md"), `# Corrections
7308
+
7309
+ Human feedback log.
7310
+ `);
7311
+ writeFileSync2(join3(spacePath, ".datacore", "learning", "preferences.md"), `# Preferences
7312
+
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
+ };
7333
+ }
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;
7347
+ }
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
+ }
7363
+ }
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
7411
+ };
7412
+ }
7413
+ var DATA_DIR2;
7414
+ var init_space = __esm(() => {
7415
+ DATA_DIR2 = join3(process.env.HOME || "~", "Data");
7416
+ });
7417
+
6983
7418
  // src/lib/agent.ts
6984
7419
  var exports_agent = {};
6985
7420
  __export(exports_agent, {
@@ -6988,19 +7423,19 @@ __export(exports_agent, {
6988
7423
  agentExists: () => agentExists
6989
7424
  });
6990
7425
  import { spawn as spawn2 } from "child_process";
6991
- import { existsSync as existsSync9 } from "fs";
6992
- import { join as join9 } from "path";
7426
+ import { existsSync as existsSync6 } from "fs";
7427
+ import { join as join6 } from "path";
6993
7428
  function commandExists2(cmd) {
6994
7429
  try {
6995
- const { execSync: execSync6 } = __require("child_process");
6996
- execSync6(`which ${cmd}`, { stdio: "pipe" });
7430
+ const { execSync: execSync4 } = __require("child_process");
7431
+ execSync4(`which ${cmd}`, { stdio: "pipe" });
6997
7432
  return true;
6998
7433
  } catch {
6999
7434
  return false;
7000
7435
  }
7001
7436
  }
7002
7437
  async function invokeAgent(invocation, options = {}) {
7003
- const { stream = false, cwd = DATA_DIR7, timeout = 300000 } = options;
7438
+ const { stream = false, cwd = DATA_DIR5, timeout = 300000 } = options;
7004
7439
  if (!commandExists2("claude")) {
7005
7440
  return {
7006
7441
  success: false,
@@ -7008,19 +7443,19 @@ async function invokeAgent(invocation, options = {}) {
7008
7443
  error: "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
7009
7444
  };
7010
7445
  }
7011
- if (!existsSync9(cwd)) {
7446
+ if (!existsSync6(cwd)) {
7012
7447
  return {
7013
7448
  success: false,
7014
7449
  output: "",
7015
7450
  error: `Working directory not found: ${cwd}`
7016
7451
  };
7017
7452
  }
7018
- const prompt2 = buildAgentPrompt(invocation);
7453
+ const prompt = buildAgentPrompt(invocation);
7019
7454
  return new Promise((resolve) => {
7020
7455
  const chunks = [];
7021
7456
  let errorChunks = [];
7022
7457
  let timedOut = false;
7023
- const proc = spawn2("claude", ["--print", prompt2], {
7458
+ const proc = spawn2("claude", ["--print", prompt], {
7024
7459
  cwd,
7025
7460
  stdio: ["ignore", "pipe", "pipe"],
7026
7461
  env: {
@@ -7102,15 +7537,15 @@ function parseArtifacts(output2) {
7102
7537
  return artifacts;
7103
7538
  }
7104
7539
  function listAgents() {
7105
- const registryPath = join9(DATA_DIR7, ".datacore", "registry", "agents.yaml");
7106
- if (!existsSync9(registryPath)) {
7540
+ const registryPath = join6(DATA_DIR5, ".datacore", "registry", "agents.yaml");
7541
+ if (!existsSync6(registryPath)) {
7107
7542
  return [];
7108
7543
  }
7109
7544
  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);
7545
+ const { readFileSync: readFileSync3 } = __require("fs");
7546
+ const { parse: parse2 } = require_dist();
7547
+ const content = readFileSync3(registryPath, "utf-8");
7548
+ const registry = parse2(content);
7114
7549
  return registry.agents?.map((a) => a.name) || [];
7115
7550
  } catch {
7116
7551
  return [];
@@ -7119,9 +7554,9 @@ function listAgents() {
7119
7554
  function agentExists(name) {
7120
7555
  return listAgents().includes(name);
7121
7556
  }
7122
- var DATA_DIR7;
7557
+ var DATA_DIR5;
7123
7558
  var init_agent = __esm(() => {
7124
- DATA_DIR7 = join9(process.env.HOME || "~", "Data");
7559
+ DATA_DIR5 = join6(process.env.HOME || "~", "Data");
7125
7560
  });
7126
7561
 
7127
7562
  // src/routing.ts
@@ -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,6 +8538,23 @@ function checkPython(platform2) {
8096
8538
  installCommand: !installed || !meetsMin ? getInstallCommand("python", platform2) ?? undefined : undefined
8097
8539
  };
8098
8540
  }
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
+ }
8099
8558
  function checkClaude(platform2) {
8100
8559
  const installed = commandExists("claude");
8101
8560
  return {
@@ -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
  }
@@ -8284,291 +8744,42 @@ function getAllConfig() {
8284
8744
  traceSources(value, currentPath);
8285
8745
  } else {
8286
8746
  let check = local;
8287
- for (const p of currentPath) {
8288
- if (typeof check !== "object" || check === null) {
8289
- check = undefined;
8290
- break;
8291
- }
8292
- check = check[p];
8293
- }
8294
- if (check !== undefined) {
8295
- sources[pathStr] = "local";
8296
- continue;
8297
- }
8298
- check = base;
8299
- for (const p of currentPath) {
8300
- if (typeof check !== "object" || check === null) {
8301
- check = undefined;
8302
- break;
8303
- }
8304
- check = check[p];
8305
- }
8306
- if (check !== undefined) {
8307
- 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
- ] : [
8380
- ".datacore",
8381
- ".datacore/commands",
8382
- ".datacore/agents",
8383
- ".datacore/learning",
8384
- ".datacore/state",
8385
- ".datacore/env",
8386
- "org",
8387
- "0-inbox",
8388
- "journal",
8389
- "1-tracks",
8390
- "1-tracks/ops",
8391
- "1-tracks/product",
8392
- "1-tracks/dev",
8393
- "1-tracks/research",
8394
- "1-tracks/comms",
8395
- "2-projects",
8396
- "3-knowledge",
8397
- "3-knowledge/pages",
8398
- "3-knowledge/zettel",
8399
- "3-knowledge/literature",
8400
- "3-knowledge/reference",
8401
- "4-archive"
8402
- ];
8403
- for (const dir of dirs) {
8404
- mkdirSync2(join3(spacePath, dir), { recursive: true });
8405
- }
8406
- if (type === "personal") {
8407
- writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
8408
- #+FILETAGS: :inbox:
8409
-
8410
- Capture everything here. Process daily to zero.
8411
-
8412
- * Inbox
8413
- `);
8414
- writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
8415
- #+FILETAGS: :tasks:
8416
-
8417
- Tasks organized by context. Tag with :AI: to delegate to agents.
8418
-
8419
- * @computer
8420
- * @phone
8421
- * @errands
8422
- * @home
8423
- * @office
8424
- * @waiting
8425
- `);
8426
- writeFileSync2(join3(spacePath, "org", "projects.org"), `#+TITLE: Projects
8427
- #+FILETAGS: :projects:
8428
-
8429
- Active projects (outcomes requiring multiple actions).
8430
-
8431
- * Projects
8432
- `);
8433
- writeFileSync2(join3(spacePath, "org", "someday.org"), `#+TITLE: Someday/Maybe
8434
- #+FILETAGS: :someday:
8435
-
8436
- Ideas and projects for the future. Review monthly.
8437
-
8438
- * Someday
8439
- `);
8440
- } else {
8441
- writeFileSync2(join3(spacePath, "org", "inbox.org"), `#+TITLE: Inbox
8442
- #+FILETAGS: :inbox:
8443
-
8444
- * Capture items here
8445
- `);
8446
- writeFileSync2(join3(spacePath, "org", "next_actions.org"), `#+TITLE: Next Actions
8447
- #+FILETAGS: :tasks:
8448
-
8449
- * Tasks
8450
- ** TODO items go here
8451
- `);
8452
- }
8453
- if (type === "personal") {
8454
- writeFileSync2(join3(spacePath, "_index.md"), `# Personal Space
8455
-
8456
- Your personal knowledge base and GTD system.
8457
-
8458
- ## GTD Workflow
8459
-
8460
- 1. **Capture** → \`org/inbox.org\` - dump everything here
8461
- 2. **Clarify** → Is it actionable? What's the next action?
8462
- 3. **Organize** → Move to \`next_actions.org\` by context
8463
- 4. **Reflect** → Weekly review, monthly strategic
8464
- 5. **Engage** → Do the work, delegate with :AI: tags
8465
-
8466
- ## Quick Links
8467
-
8468
- - [Inbox](org/inbox.org) - Capture here
8469
- - [Next Actions](org/next_actions.org) - Today's work
8470
- - [Projects](org/projects.org) - Active projects
8471
- - [Someday](org/someday.org) - Future ideas
8472
- - [Notes](notes/) - Knowledge base
8473
- - [Journal](journal/) - Daily entries
8474
- `);
8475
- } else {
8476
- writeFileSync2(join3(spacePath, "_index.md"), `# ${name}
8477
-
8478
- Team space.
8479
-
8480
- ## Structure
8481
-
8482
- - \`org/\` - Task coordination
8483
- - \`1-tracks/\` - Department work
8484
- - \`2-projects/\` - Code repositories
8485
- - \`3-knowledge/\` - Shared knowledge
8486
- - \`4-archive/\` - Historical content
8487
-
8488
- ## Quick Links
8489
-
8490
- - [Inbox](org/inbox.org)
8491
- - [Tasks](org/next_actions.org)
8492
- - [Journal](journal/)
8493
- - [Knowledge](3-knowledge/)
8494
- `);
8495
- }
8496
- if (type === "personal") {
8497
- writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# Personal Space
8498
-
8499
- Personal GTD system and knowledge base.
8500
-
8501
- ## GTD Workflow
8502
-
8503
- - \`org/inbox.org\` - Single capture point, process to zero daily
8504
- - \`org/next_actions.org\` - Tasks by context (@computer, @phone, etc.)
8505
- - \`org/projects.org\` - Active multi-step outcomes
8506
- - \`org/someday.org\` - Future ideas, review monthly
8507
-
8508
- ## AI Delegation
8509
-
8510
- Tag tasks with \`:AI:\` to delegate to agents:
8511
- - \`:AI:research:\` - Research and summarize
8512
- - \`:AI:content:\` - Draft content
8513
- - \`:AI:data:\` - Analyze data
8514
-
8515
- ## Knowledge
8516
-
8517
- - \`notes/\` - Personal knowledge base (Obsidian)
8518
- - \`notes/journals/\` - Daily reflections
8519
- - \`notes/zettel/\` - Atomic concept notes
8520
-
8521
- ## See Also
8522
-
8523
- Parent: ~/Data/CLAUDE.md
8524
- `);
8525
- } else {
8526
- writeFileSync2(join3(spacePath, "CLAUDE.base.md"), `# ${name} Space
8527
-
8528
- Team space for ${name}.
8529
-
8530
- ## Structure
8531
-
8532
- See parent CLAUDE.md for full documentation.
8533
- `);
8747
+ for (const p of currentPath) {
8748
+ if (typeof check !== "object" || check === null) {
8749
+ check = undefined;
8750
+ break;
8751
+ }
8752
+ check = check[p];
8753
+ }
8754
+ if (check !== undefined) {
8755
+ sources[pathStr] = "local";
8756
+ continue;
8757
+ }
8758
+ check = base;
8759
+ for (const p of currentPath) {
8760
+ if (typeof check !== "object" || check === null) {
8761
+ check = undefined;
8762
+ break;
8763
+ }
8764
+ check = check[p];
8765
+ }
8766
+ if (check !== undefined) {
8767
+ sources[pathStr] = "base";
8768
+ continue;
8769
+ }
8770
+ sources[pathStr] = "default";
8771
+ }
8772
+ }
8534
8773
  }
8535
- writeFileSync2(join3(spacePath, ".datacore", "config.yaml"), `# Space configuration
8536
- name: ${normalizedName}
8537
- type: ${type}
8538
- `);
8539
- writeFileSync2(join3(spacePath, ".datacore", "learning", "patterns.md"), `# Patterns
8540
-
8541
- Successful approaches to remember.
8542
- `);
8543
- writeFileSync2(join3(spacePath, ".datacore", "learning", "corrections.md"), `# Corrections
8544
-
8545
- Human feedback log.
8546
- `);
8547
- writeFileSync2(join3(spacePath, ".datacore", "learning", "preferences.md"), `# Preferences
8548
-
8549
- Style and preference notes.
8550
- `);
8551
- writeFileSync2(join3(spacePath, ".gitignore"), type === "personal" ? `.datacore/state/
8552
- .datacore/env/
8553
- CLAUDE.md
8554
- CLAUDE.local.md
8555
- ` : `.datacore/state/
8556
- .datacore/env/
8557
- CLAUDE.md
8558
- CLAUDE.local.md
8559
- 2-projects/
8560
- `);
8561
- return {
8562
- name: folderName,
8563
- number,
8564
- path: spacePath,
8565
- type,
8566
- hasGit: false,
8567
- hasClaude: true
8568
- };
8774
+ traceSources(merged);
8775
+ return { merged, sources };
8569
8776
  }
8570
8777
 
8778
+ // src/index.ts
8779
+ init_space();
8780
+
8571
8781
  // src/lib/sync.ts
8782
+ init_space();
8572
8783
  import { execSync as execSync3 } from "child_process";
8573
8784
  import { existsSync as existsSync4 } from "fs";
8574
8785
  import { join as join4, basename as basename2 } from "path";
@@ -8703,46 +8914,89 @@ function getGitRepos() {
8703
8914
  }
8704
8915
 
8705
8916
  // src/lib/init.ts
8706
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, symlinkSync } from "fs";
8707
- 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";
8708
8920
  import { createInterface } from "readline";
8709
8921
 
8710
8922
  // src/lib/module.ts
8711
8923
  import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
8712
8924
  import { join as join5, basename as basename3 } from "path";
8713
- import { execSync as execSync4 } from "child_process";
8714
- var DATA_DIR4 = join5(process.env.HOME || "~", "Data");
8925
+ import { execFileSync } from "child_process";
8926
+ var DATA_DIR4 = join5(process.env.HOME || "", "Data");
8715
8927
  var MODULES_DIR = join5(DATA_DIR4, ".datacore", "modules");
8716
8928
  var AVAILABLE_MODULES = [
8717
8929
  {
8718
8930
  name: "nightshift",
8719
- description: "Overnight AI task execution",
8720
- repo: "https://github.com/datacore-one/module-nightshift",
8721
- 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
8722
8942
  },
8723
8943
  {
8724
8944
  name: "crm",
8725
- description: "Contact relationship management",
8726
- repo: "https://github.com/datacore-one/module-crm",
8727
- 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
8728
8949
  },
8729
8950
  {
8730
8951
  name: "meetings",
8731
- description: "Meeting preparation and follow-up",
8732
- repo: "https://github.com/datacore-one/module-meetings",
8733
- 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
8734
8956
  },
8735
8957
  {
8736
- name: "health",
8737
- description: "Personal health and wellness tracking",
8738
- repo: "https://github.com/datacore-one/module-health",
8739
- 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
8740
8998
  }
8741
8999
  ];
8742
- function getAvailableModules() {
8743
- const installed = listModules().map((m) => m.name);
8744
- return AVAILABLE_MODULES.filter((m) => !installed.includes(m.name));
8745
- }
8746
9000
  function listModules() {
8747
9001
  if (!existsSync5(MODULES_DIR)) {
8748
9002
  return [];
@@ -8788,108 +9042,362 @@ function getModuleInfo(modulePath) {
8788
9042
  }
8789
9043
  }
8790
9044
  }
8791
- const commandsDir = join5(modulePath, "commands");
8792
- const commands = [];
8793
- if (existsSync5(commandsDir)) {
8794
- const cmdFiles = readdirSync2(commandsDir);
8795
- for (const file of cmdFiles) {
8796
- if (file.endsWith(".md")) {
8797
- commands.push(file.replace(".md", ""));
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
+ });
8798
9279
  }
8799
9280
  }
8800
9281
  }
8801
- return {
8802
- name,
8803
- path: modulePath,
8804
- version,
8805
- description,
8806
- agents,
8807
- commands
8808
- };
8809
- }
8810
- function installModule(source) {
8811
- if (!existsSync5(MODULES_DIR)) {
8812
- const { mkdirSync: mkdirSync3 } = __require("fs");
8813
- mkdirSync3(MODULES_DIR, { recursive: true });
8814
- }
8815
- let name;
8816
- let isGit = false;
8817
- if (source.includes("github.com") || source.startsWith("git@") || source.endsWith(".git")) {
8818
- isGit = true;
8819
- const match = source.match(/([^/]+?)(?:\.git)?$/);
8820
- name = match?.[1] ?? source.split("/").pop() ?? "unknown";
8821
- name = name.replace(/^module-/, "");
8822
- } else if (source.startsWith("@")) {
8823
- name = source.split("/").pop()?.replace(/^datacore-/, "") ?? "unknown";
8824
- } else {
8825
- name = source.replace(/^datacore-/, "").replace(/^module-/, "");
9282
+ for (const name of snapshotModules.keys()) {
9283
+ if (!currentModules.has(name)) {
9284
+ diff.modules.removed.push(name);
9285
+ }
8826
9286
  }
8827
- const modulePath = join5(MODULES_DIR, name);
8828
- if (existsSync5(modulePath)) {
8829
- 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
+ }
8830
9293
  }
8831
- if (isGit) {
8832
- execSync4(`git clone ${source} "${modulePath}"`, { stdio: "pipe" });
8833
- } else {
8834
- const gitUrl = `https://github.com/datacore/module-${name}.git`;
8835
- try {
8836
- execSync4(`git clone ${gitUrl} "${modulePath}"`, { stdio: "pipe" });
8837
- } catch {
8838
- 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);
8839
9297
  }
8840
9298
  }
8841
- const info2 = getModuleInfo(modulePath);
8842
- if (!info2) {
8843
- rmSync(modulePath, { recursive: true, force: true });
8844
- 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
+ }
8845
9309
  }
8846
- return info2;
9310
+ return diff;
8847
9311
  }
8848
- function updateModules(name) {
8849
- const modules = listModules();
8850
- const results = [];
8851
- const toUpdate = name ? modules.filter((m) => m.name === name) : modules;
8852
- for (const mod of toUpdate) {
8853
- try {
8854
- const gitDir = join5(mod.path, ".git");
8855
- if (existsSync5(gitDir)) {
8856
- execSync4("git pull", { cwd: mod.path, stdio: "pipe" });
8857
- results.push({ name: mod.name, updated: true });
8858
- } else {
8859
- 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
+ });
8860
9352
  }
8861
- } catch (err) {
8862
- results.push({ name: mod.name, updated: false, error: err.message });
8863
9353
  }
8864
9354
  }
8865
- return results;
8866
- }
8867
- function removeModule(name) {
8868
- const modulePath = join5(MODULES_DIR, name);
8869
- if (!existsSync5(modulePath)) {
8870
- 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
+ }
8871
9380
  }
8872
- rmSync(modulePath, { recursive: true, force: true });
8873
- return true;
9381
+ return result;
8874
9382
  }
8875
9383
 
8876
9384
  // src/state.ts
8877
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
8878
- import { join as join6 } from "path";
8879
- var STATE_DIR = join6(process.env.HOME || "~", "Data", ".datacore", "state");
8880
- 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");
8881
9389
  function ensureStateDir() {
8882
- if (!existsSync6(STATE_DIR)) {
8883
- mkdirSync3(STATE_DIR, { recursive: true });
9390
+ if (!existsSync8(STATE_DIR)) {
9391
+ mkdirSync4(STATE_DIR, { recursive: true });
8884
9392
  }
8885
9393
  }
8886
9394
  function loadState() {
8887
9395
  ensureStateDir();
8888
- if (!existsSync6(STATE_FILE)) {
9396
+ if (!existsSync8(STATE_FILE)) {
8889
9397
  return { operations: [] };
8890
9398
  }
8891
9399
  try {
8892
- const content = readFileSync3(STATE_FILE, "utf-8");
9400
+ const content = readFileSync4(STATE_FILE, "utf-8");
8893
9401
  return JSON.parse(content);
8894
9402
  } catch {
8895
9403
  return { operations: [] };
@@ -8897,7 +9405,7 @@ function loadState() {
8897
9405
  }
8898
9406
  function saveState(state) {
8899
9407
  ensureStateDir();
8900
- writeFileSync3(STATE_FILE, JSON.stringify(state, null, 2));
9408
+ writeFileSync4(STATE_FILE, JSON.stringify(state, null, 2));
8901
9409
  }
8902
9410
  function generateId() {
8903
9411
  return `op_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
@@ -8914,7 +9422,8 @@ class Operation {
8914
9422
  status: "pending",
8915
9423
  startedAt: new Date().toISOString(),
8916
9424
  steps: [],
8917
- rollback: []
9425
+ rollback: [],
9426
+ meta: Object.keys(params).length > 0 ? params : undefined
8918
9427
  };
8919
9428
  this.store.operations.push(this.state);
8920
9429
  saveState(this.store);
@@ -9006,16 +9515,12 @@ ${c.reset}${c.dim} AI-Powered Second Brain ${c.reset}
9006
9515
  `;
9007
9516
  var INIT_COMPLETE = `
9008
9517
  ${c.green}${c.bright}
9009
- ╔═══════════════════════════════════════════════════════════════╗
9010
-
9011
- ║ ${c.reset}${c.green}█████╗ ██████╗████████╗██╗██╗ ██╗███████╗${c.bright}
9012
- ║ ${c.reset}${c.green}██╔══██╗██╔════╝╚══██╔══╝██║██║ ██║██╔════╝${c.bright}
9013
- ${c.reset}${c.green}███████║██║ ██║ ██║██║ ██║█████╗${c.bright}
9014
- ║ ${c.reset}${c.green}██╔══██║██║ ██║ ██║╚██╗ ██╔╝██╔══╝${c.bright} ║
9015
- ║ ${c.reset}${c.green}██║ ██║╚██████╗ ██║ ██║ ╚████╔╝ ███████╗${c.bright} ║
9016
- ║ ${c.reset}${c.green}╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝${c.bright} ║
9017
- ║ ║
9018
- ╚═══════════════════════════════════════════════════════════════╝
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
+ ╚══════════════════════════════════════════════════════════╝
9019
9524
  ${c.reset}
9020
9525
  `;
9021
9526
  function sleep(ms) {
@@ -9062,9 +9567,76 @@ function section(title) {
9062
9567
  console.log(`${c.dim}${"─".repeat(50)}${c.reset}`);
9063
9568
  }
9064
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
+
9065
9609
  // src/lib/init.ts
9066
- var DATA_DIR5 = join7(process.env.HOME || "~", "Data");
9067
- 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
+ ];
9068
9640
  var c2 = {
9069
9641
  reset: "\x1B[0m",
9070
9642
  bold: "\x1B[1m",
@@ -9088,20 +9660,220 @@ async function prompt(question, defaultValue) {
9088
9660
  });
9089
9661
  });
9090
9662
  }
9091
- async function confirm(question, defaultYes = true) {
9092
- const hint = defaultYes ? "[Y/n]" : "[y/N]";
9093
- const answer = await prompt(`${question} ${hint}`);
9094
- if (!answer)
9095
- return defaultYes;
9096
- return answer.toLowerCase().startsWith("y");
9663
+ async function confirm(question, defaultYes = true) {
9664
+ const hint = defaultYes ? "[Y/n]" : "[y/N]";
9665
+ const answer = await prompt(`${question} ${hint}`);
9666
+ if (!answer)
9667
+ return defaultYes;
9668
+ return answer.toLowerCase().startsWith("y");
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 runArgsOutput(cmd, args, opts) {
9691
+ try {
9692
+ return execFileSync2(cmd, args, { encoding: "utf-8", stdio: "pipe", timeout: opts?.timeout }).trim();
9693
+ } catch {
9694
+ return "";
9695
+ }
9696
+ }
9697
+ function commandExists3(cmd) {
9698
+ try {
9699
+ execFileSync2("which", [cmd], { stdio: "pipe" });
9700
+ return true;
9701
+ } catch {
9702
+ return false;
9703
+ }
9704
+ }
9705
+ function getVersionString(cmd, flag = "--version") {
9706
+ try {
9707
+ const output2 = execFileSync2(cmd, [flag], { encoding: "utf-8", stdio: "pipe" });
9708
+ return output2.match(/(\d+\.\d+(\.\d+)?)/)?.[0] ?? output2.trim().split(`
9709
+ `)[0]?.slice(0, 30);
9710
+ } catch {
9711
+ return;
9712
+ }
9713
+ }
9714
+ function checkGhAuth() {
9715
+ if (!commandExists3("gh"))
9716
+ return { available: false };
9717
+ try {
9718
+ const output2 = execFileSync2("gh", ["auth", "status"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 });
9719
+ const combined = output2;
9720
+ const userMatch = combined.match(/Logged in to github\.com.*account (\S+)/i);
9721
+ return { available: true, user: userMatch?.[1] };
9722
+ } catch (err) {
9723
+ const stderr = err?.stderr?.toString() || "";
9724
+ const userMatch = stderr.match(/Logged in to github\.com.*account (\S+)/i);
9725
+ if (userMatch)
9726
+ return { available: true, user: userMatch[1] };
9727
+ return { available: false };
9728
+ }
9729
+ }
9730
+ function isGitConfigured() {
9731
+ const name = runArgsOutput("git", ["config", "--global", "user.name"]);
9732
+ const email = runArgsOutput("git", ["config", "--global", "user.email"]);
9733
+ return { name: name || undefined, email: email || undefined, configured: !!(name && email) };
9734
+ }
9735
+ async function ensureDependency(name, checkCmd, platform2, isTTY, versionFlag = "--version") {
9736
+ if (commandExists3(checkCmd)) {
9737
+ const version = getVersionString(checkCmd, versionFlag);
9738
+ return { available: true, version, wasInstalled: false };
9739
+ }
9740
+ const installCmd = getInstallCommand(name, platform2);
9741
+ if (!installCmd) {
9742
+ return { available: false, wasInstalled: false };
9743
+ }
9744
+ const spinner = isTTY ? new Spinner(`Installing ${name}...`) : null;
9745
+ spinner?.start();
9746
+ try {
9747
+ execFileSync2("/bin/bash", ["-c", installCmd], { stdio: "pipe", timeout: 300000 });
9748
+ if (commandExists3(checkCmd)) {
9749
+ const version = getVersionString(checkCmd, versionFlag);
9750
+ spinner?.succeed(`${name} installed${version ? ` (${version})` : ""}`);
9751
+ return { available: true, version, wasInstalled: true };
9752
+ }
9753
+ spinner?.fail(`${name} install completed but command not found in PATH`);
9754
+ if (isTTY)
9755
+ console.log(` ${c2.dim}Try manually: ${installCmd}${c2.reset}`);
9756
+ return { available: false, wasInstalled: false };
9757
+ } catch {
9758
+ spinner?.fail(`Failed to install ${name}`);
9759
+ if (isTTY)
9760
+ console.log(` ${c2.dim}Try manually: ${installCmd}${c2.reset}`);
9761
+ return { available: false, wasInstalled: false };
9762
+ }
9763
+ }
9764
+ async function ensureHomebrew(isTTY) {
9765
+ if (commandExists3("brew"))
9766
+ return true;
9767
+ const brewPaths = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"];
9768
+ for (const p of brewPaths) {
9769
+ if (existsSync9(p)) {
9770
+ const dir = join10(p, "..");
9771
+ process.env.PATH = `${dir}:${process.env.PATH}`;
9772
+ return true;
9773
+ }
9774
+ }
9775
+ const spinner = isTTY ? new Spinner("Installing Homebrew...") : null;
9776
+ spinner?.start();
9777
+ try {
9778
+ execFileSync2("/bin/bash", ["-c", 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'], {
9779
+ stdio: "pipe",
9780
+ timeout: 600000
9781
+ });
9782
+ for (const p of brewPaths) {
9783
+ if (existsSync9(p)) {
9784
+ const dir = join10(p, "..");
9785
+ process.env.PATH = `${dir}:${process.env.PATH}`;
9786
+ break;
9787
+ }
9788
+ }
9789
+ if (commandExists3("brew")) {
9790
+ spinner?.succeed("Homebrew installed");
9791
+ return true;
9792
+ }
9793
+ spinner?.fail("Homebrew installed but not found in PATH");
9794
+ return false;
9795
+ } catch {
9796
+ spinner?.fail("Failed to install Homebrew");
9797
+ if (isTTY) {
9798
+ console.log(` ${c2.dim}Install manually: https://brew.sh${c2.reset}`);
9799
+ }
9800
+ return false;
9801
+ }
9802
+ }
9803
+ function runModulePostInstall(modulePath) {
9804
+ const reqTxt = join10(modulePath, "requirements.txt");
9805
+ if (existsSync9(reqTxt)) {
9806
+ const ok = runArgs("python3", ["-m", "pip", "install", "-r", reqTxt, "--quiet"], { cwd: modulePath, timeout: 120000 });
9807
+ return { ran: true, success: ok, type: "pip" };
9808
+ }
9809
+ const pkgJson = join10(modulePath, "package.json");
9810
+ if (existsSync9(pkgJson)) {
9811
+ try {
9812
+ const pkg = JSON.parse(readFileSync5(pkgJson, "utf-8"));
9813
+ if (pkg.dependencies || pkg.devDependencies) {
9814
+ const ok = runArgs("npm", ["install", "--silent"], { cwd: modulePath, timeout: 120000 });
9815
+ return { ran: true, success: ok, type: "npm" };
9816
+ }
9817
+ } catch {}
9818
+ }
9819
+ const installSh = join10(modulePath, "install.sh");
9820
+ if (existsSync9(installSh)) {
9821
+ const ok = runArgs("bash", [installSh], { cwd: modulePath });
9822
+ return { ran: true, success: ok, type: "script" };
9823
+ }
9824
+ try {
9825
+ const entries = readdirSync3(modulePath, { withFileTypes: true });
9826
+ for (const entry of entries) {
9827
+ if (!entry.isDirectory() || entry.name.startsWith("."))
9828
+ continue;
9829
+ const subPkg = join10(modulePath, entry.name, "package.json");
9830
+ if (existsSync9(subPkg)) {
9831
+ try {
9832
+ const pkg = JSON.parse(readFileSync5(subPkg, "utf-8"));
9833
+ if (pkg.dependencies || pkg.devDependencies) {
9834
+ const ok = runArgs("npm", ["install", "--silent"], { cwd: join10(modulePath, entry.name), timeout: 120000 });
9835
+ return { ran: true, success: ok, type: "npm" };
9836
+ }
9837
+ } catch {}
9838
+ }
9839
+ }
9840
+ } catch {}
9841
+ return { ran: false, success: true };
9842
+ }
9843
+ function countFiles(dir) {
9844
+ let total = 0;
9845
+ const byExt = {};
9846
+ function walk(d) {
9847
+ try {
9848
+ for (const entry of readdirSync3(d, { withFileTypes: true })) {
9849
+ if (entry.name.startsWith("."))
9850
+ continue;
9851
+ const fullPath = join10(d, entry.name);
9852
+ if (entry.isFile()) {
9853
+ total++;
9854
+ const ext = entry.name.includes(".") ? entry.name.split(".").pop().toLowerCase() : "other";
9855
+ byExt[ext] = (byExt[ext] || 0) + 1;
9856
+ } else if (entry.isDirectory()) {
9857
+ walk(fullPath);
9858
+ }
9859
+ }
9860
+ } catch {}
9861
+ }
9862
+ walk(dir);
9863
+ return { total, byExt };
9097
9864
  }
9098
9865
  function isInitialized() {
9099
- return existsSync7(DATACORE_DIR);
9866
+ try {
9867
+ 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"));
9868
+ } catch {
9869
+ return false;
9870
+ }
9100
9871
  }
9101
9872
  async function initDatacore(options = {}) {
9102
- const { nonInteractive = false, skipChecks = false, stream = false } = options;
9873
+ const { nonInteractive = false, skipChecks = false, stream = false, verbose = false } = options;
9103
9874
  const isTTY = stream && process.stdout.isTTY;
9104
9875
  const interactive = isTTY && !nonInteractive;
9876
+ const platform2 = detectPlatform();
9105
9877
  const result = {
9106
9878
  success: false,
9107
9879
  created: [],
@@ -9113,6 +9885,7 @@ async function initDatacore(options = {}) {
9113
9885
  };
9114
9886
  const op = startOperation("init", { options });
9115
9887
  op.start();
9888
+ let profile = { name: "", email: "", useCase: "both", role: "" };
9116
9889
  try {
9117
9890
  if (isTTY) {
9118
9891
  console.clear();
@@ -9121,383 +9894,1024 @@ async function initDatacore(options = {}) {
9121
9894
  console.log();
9122
9895
  await sleep(300);
9123
9896
  }
9124
- op.addStep("check_dependencies");
9125
- op.startStep("check_dependencies");
9897
+ op.addStep("about_you");
9898
+ op.startStep("about_you");
9899
+ if (interactive) {
9900
+ section(`Step 1/${TOTAL_STEPS}: About You`);
9901
+ console.log();
9902
+ console.log(` ${c2.dim}Let's personalize your setup. This configures your identity,${c2.reset}`);
9903
+ console.log(` ${c2.dim}AI context layer, and tailors the experience to your needs.${c2.reset}`);
9904
+ console.log();
9905
+ const gitConfig = isGitConfigured();
9906
+ if (gitConfig.name)
9907
+ profile.name = gitConfig.name;
9908
+ if (gitConfig.email)
9909
+ profile.email = gitConfig.email;
9910
+ profile.name = await prompt(` Your name`, profile.name || undefined);
9911
+ profile.email = await prompt(` Your email`, profile.email || undefined);
9912
+ console.log();
9913
+ console.log(` ${c2.bold}How will you use Datacore?${c2.reset}`);
9914
+ const useCaseIdx = await choose(" Choose", [
9915
+ "Personal productivity (GTD, knowledge management, AI delegation)",
9916
+ "Team management (projects, collaboration, shared knowledge)",
9917
+ "Both personal and team use"
9918
+ ], 2);
9919
+ profile.useCase = ["personal", "team", "both"][useCaseIdx] ?? "both";
9920
+ console.log();
9921
+ profile.role = await prompt(` Your role (e.g., developer, founder, researcher)`, "");
9922
+ console.log();
9923
+ console.log(` ${c2.green}✓${c2.reset} Welcome, ${c2.bold}${profile.name || "friend"}${c2.reset}!`);
9924
+ console.log();
9925
+ } else {
9926
+ const gitConfig = isGitConfigured();
9927
+ if (gitConfig.name)
9928
+ profile.name = gitConfig.name;
9929
+ if (gitConfig.email)
9930
+ profile.email = gitConfig.email;
9931
+ }
9932
+ op.completeStep("about_you");
9933
+ op.addStep("system_setup");
9934
+ op.startStep("system_setup");
9126
9935
  if (!skipChecks) {
9127
9936
  if (isTTY) {
9128
- section("Step 1: Checking Your System");
9937
+ section(`Step 2/${TOTAL_STEPS}: System Setup`);
9938
+ console.log();
9939
+ console.log(` ${c2.dim}Ensuring all tools are installed and configured.${c2.reset}`);
9940
+ console.log(` ${c2.dim}Datacore will install anything that's missing.${c2.reset}`);
9129
9941
  console.log();
9130
9942
  }
9131
- const doctor = runDoctor();
9132
- const gitStatus = checkGitDetailed();
9133
- if (isTTY) {
9134
- if (gitStatus.installed) {
9135
- console.log(` ${c2.green}✓${c2.reset} git ${c2.dim}(${gitStatus.version})${c2.reset}`);
9136
- if (gitStatus.configured) {
9137
- console.log(` ${c2.dim}Configured as: ${gitStatus.userName} <${gitStatus.userEmail}>${c2.reset}`);
9138
- } else {
9139
- console.log(` ${c2.yellow}⚠${c2.reset} ${c2.yellow}Not configured. Run:${c2.reset}`);
9140
- console.log(` ${c2.dim}git config --global user.name "Your Name"${c2.reset}`);
9141
- console.log(` ${c2.dim}git config --global user.email "you@example.com"${c2.reset}`);
9142
- result.warnings.push("git not configured with user.name and user.email");
9943
+ if (platform2 === "macos") {
9944
+ if (!commandExists3("brew")) {
9945
+ if (isTTY) {
9946
+ console.log(` ${c2.dim}Homebrew is needed to install system packages on macOS.${c2.reset}`);
9947
+ console.log();
9143
9948
  }
9144
- } else {
9145
- console.log(` ${c2.red}✗${c2.reset} git ${c2.red}(required)${c2.reset}`);
9146
- console.log(` ${c2.dim}Version control for your knowledge system${c2.reset}`);
9147
- result.errors.push("git is required but not installed");
9949
+ await ensureHomebrew(!!isTTY);
9148
9950
  }
9149
- if (gitStatus.githubAuth) {
9150
- console.log(` ${c2.green}✓${c2.reset} GitHub ${c2.dim}(authenticated as ${gitStatus.githubUser})${c2.reset}`);
9151
- } else {
9152
- console.log(` ${c2.yellow}○${c2.reset} GitHub ${c2.dim}(not authenticated)${c2.reset}`);
9153
- console.log(` ${c2.dim}Optional: Install gh CLI and run 'gh auth login'${c2.reset}`);
9951
+ }
9952
+ const git = await ensureDependency("git", "git", platform2, !!isTTY);
9953
+ if (git.available) {
9954
+ if (!git.wasInstalled && isTTY) {
9955
+ console.log(` ${c2.green}✓${c2.reset} git ${c2.dim}(${git.version})${c2.reset}`);
9154
9956
  }
9155
- for (const dep of doctor.dependencies) {
9156
- if (dep.name === "git")
9157
- continue;
9158
- const info2 = {
9159
- "git-lfs": "Large file support (PDFs, images, videos)",
9160
- node: "Runtime for automation scripts",
9161
- python: "Powers AI agents and data processing",
9162
- claude: "Claude Code AI assistant"
9163
- };
9164
- if (dep.installed) {
9165
- console.log(` ${c2.green}✓${c2.reset} ${dep.name} ${dep.version ? `${c2.dim}(${dep.version})${c2.reset}` : ""}`);
9166
- } else if (dep.required) {
9167
- console.log(` ${c2.red}✗${c2.reset} ${dep.name} ${c2.red}(required)${c2.reset}`);
9168
- console.log(` ${c2.dim}${info2[dep.name] || ""}${c2.reset}`);
9169
- if (dep.installCommand) {
9170
- console.log(` ${c2.yellow}Install: ${dep.installCommand}${c2.reset}`);
9171
- }
9957
+ const gitConfig = isGitConfigured();
9958
+ if (!gitConfig.configured && profile.name && profile.email) {
9959
+ if (isTTY) {
9960
+ const spinner = new Spinner("Configuring git identity...");
9961
+ spinner.start();
9962
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
9963
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
9964
+ spinner.succeed(`Git configured as: ${profile.name} <${profile.email}>`);
9172
9965
  } else {
9173
- console.log(` ${c2.yellow}○${c2.reset} ${dep.name} ${c2.dim}(recommended)${c2.reset}`);
9174
- console.log(` ${c2.dim}${info2[dep.name] || ""}${c2.reset}`);
9175
- if (dep.installCommand) {
9176
- console.log(` ${c2.dim}Install: ${dep.installCommand}${c2.reset}`);
9177
- }
9966
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
9967
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
9968
+ }
9969
+ } else if (gitConfig.configured && interactive && profile.name && profile.email && (gitConfig.name !== profile.name || gitConfig.email !== profile.email)) {
9970
+ console.log(` ${c2.dim}Git configured as: ${gitConfig.name} <${gitConfig.email}>${c2.reset}`);
9971
+ const override = await confirm(` Update to ${profile.name} <${profile.email}>?`, false);
9972
+ if (override) {
9973
+ runArgs("git", ["config", "--global", "user.name", profile.name]);
9974
+ runArgs("git", ["config", "--global", "user.email", profile.email]);
9975
+ console.log(` ${c2.green}✓${c2.reset} Git updated to: ${profile.name} <${profile.email}>`);
9178
9976
  }
9977
+ } else if (gitConfig.configured && isTTY && !git.wasInstalled) {
9978
+ console.log(` ${c2.dim}Configured as: ${gitConfig.name} <${gitConfig.email}>${c2.reset}`);
9179
9979
  }
9180
- console.log();
9980
+ } else {
9981
+ result.errors.push("git is required but could not be installed");
9982
+ }
9983
+ const node = await ensureDependency("node", "node", platform2, !!isTTY, "-v");
9984
+ if (node.available && !node.wasInstalled && isTTY) {
9985
+ console.log(` ${c2.green}✓${c2.reset} node ${c2.dim}(${node.version})${c2.reset}`);
9986
+ }
9987
+ if (node.available && node.version) {
9988
+ const major = parseInt(node.version.split(".")[0] || "0", 10);
9989
+ if (major < 20) {
9990
+ if (isTTY)
9991
+ console.log(` ${c2.yellow}⚠${c2.reset} ${c2.dim}Node ${node.version} is outdated. Consider updating to Node 20+${c2.reset}`);
9992
+ result.warnings.push(`Node.js ${node.version} is outdated - recommend Node 20+`);
9993
+ }
9994
+ }
9995
+ const claude = await ensureDependency("claude", "claude", platform2, !!isTTY);
9996
+ if (claude.available && !claude.wasInstalled && isTTY) {
9997
+ console.log(` ${c2.green}✓${c2.reset} Claude Code ${c2.dim}(${claude.version})${c2.reset}`);
9181
9998
  }
9182
- if (doctor.status === "missing_required") {
9183
- const missing = doctor.dependencies.filter((d) => d.required && !d.installed);
9184
- for (const dep of missing) {
9185
- result.errors.push(`Missing required: ${dep.name}`);
9999
+ if (!claude.available) {
10000
+ result.warnings.push("Claude Code not installed - install with: npm install -g @anthropic-ai/claude-code");
10001
+ }
10002
+ const python = await ensureDependency("python", "python3", platform2, !!isTTY);
10003
+ if (python.available && !python.wasInstalled && isTTY) {
10004
+ console.log(` ${c2.green}✓${c2.reset} python ${c2.dim}(${python.version})${c2.reset}`);
10005
+ }
10006
+ if (!python.available) {
10007
+ result.warnings.push("Python not installed - some agents may not work");
10008
+ }
10009
+ const gh = await ensureDependency("gh", "gh", platform2, !!isTTY);
10010
+ if (gh.available && !gh.wasInstalled && isTTY) {
10011
+ const ghAuth2 = checkGhAuth();
10012
+ if (ghAuth2.available) {
10013
+ console.log(` ${c2.green}✓${c2.reset} GitHub CLI ${c2.dim}(authenticated as ${ghAuth2.user})${c2.reset}`);
10014
+ } else {
10015
+ console.log(` ${c2.green}✓${c2.reset} GitHub CLI ${c2.dim}(installed, not authenticated)${c2.reset}`);
9186
10016
  }
9187
- if (isTTY) {
9188
- console.log(` ${c2.red}${c2.bold}Cannot continue - install required dependencies first.${c2.reset}`);
10017
+ }
10018
+ if (commandExists3("gh")) {
10019
+ const ghAuth2 = checkGhAuth();
10020
+ if (!ghAuth2.available && interactive) {
9189
10021
  console.log();
10022
+ console.log(` ${c2.dim}To connect your GitHub account, a browser window will open.${c2.reset}`);
10023
+ const doAuth = await confirm(" Authenticate with GitHub now?", true);
10024
+ if (doAuth) {
10025
+ try {
10026
+ execFileSync2("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
10027
+ stdio: "inherit",
10028
+ timeout: 120000
10029
+ });
10030
+ const newAuth = checkGhAuth();
10031
+ if (newAuth.available) {
10032
+ console.log(` ${c2.green}✓${c2.reset} GitHub authenticated (${newAuth.user})`);
10033
+ }
10034
+ } catch {
10035
+ console.log(` ${c2.yellow}⚠${c2.reset} GitHub authentication skipped`);
10036
+ result.warnings.push("GitHub CLI not authenticated - fork workflow may not work");
10037
+ }
10038
+ } else {
10039
+ result.warnings.push("GitHub CLI not authenticated - run: gh auth login");
10040
+ }
9190
10041
  }
9191
- op.failStep("check_dependencies", "Missing required dependencies");
9192
- op.fail("Missing required dependencies");
9193
- return result;
9194
10042
  }
9195
- if (interactive && !gitStatus.configured) {
9196
- const proceed = await confirm("Git is not configured. Continue anyway?", false);
9197
- if (!proceed) {
9198
- result.errors.push("User cancelled: git not configured");
9199
- op.fail("User cancelled");
9200
- return result;
10043
+ const gitlfs = await ensureDependency("git-lfs", "git-lfs", platform2, !!isTTY);
10044
+ if (gitlfs.available && !gitlfs.wasInstalled && isTTY) {
10045
+ console.log(` ${c2.green}✓${c2.reset} git-lfs ${c2.dim}(${gitlfs.version})${c2.reset}`);
10046
+ }
10047
+ if (isTTY) {
10048
+ console.log();
10049
+ if (git.available && node.available) {
10050
+ console.log(` ${c2.green}All systems ready.${c2.reset}`);
10051
+ } else {
10052
+ console.log(` ${c2.yellow}Some tools could not be installed. Continuing with what's available.${c2.reset}`);
9201
10053
  }
10054
+ console.log();
9202
10055
  }
9203
10056
  }
9204
- op.completeStep("check_dependencies");
9205
- op.addStep("create_data_dir");
9206
- op.startStep("create_data_dir");
10057
+ op.completeStep("system_setup");
10058
+ op.addStep("clone_repo");
10059
+ op.startStep("clone_repo");
9207
10060
  if (isTTY) {
9208
- section("Step 2: Creating ~/Data");
9209
- console.log(` ${c2.dim}Your central knowledge directory. Everything lives here.${c2.reset}`);
10061
+ section(`Step 3/${TOTAL_STEPS}: Setting Up Repository`);
10062
+ console.log();
10063
+ console.log(` ${c2.dim}Datacore lives in ~/Data. We'll fork the main repository to your${c2.reset}`);
10064
+ console.log(` ${c2.dim}GitHub account so you can customize freely and pull updates.${c2.reset}`);
9210
10065
  console.log();
9211
10066
  }
9212
- if (!existsSync7(DATA_DIR5)) {
9213
- mkdirSync4(DATA_DIR5, { recursive: true });
9214
- result.created.push(DATA_DIR5);
10067
+ const ghAuth = checkGhAuth();
10068
+ if (existsSync9(join10(DATA_DIR8, ".git"))) {
9215
10069
  if (isTTY)
9216
- console.log(` ${c2.green}✓${c2.reset} Created ${DATA_DIR5}`);
10070
+ console.log(` ${c2.green}✓${c2.reset} Found existing repository at ~/Data`);
10071
+ const spinner = isTTY ? new Spinner("Pulling latest changes...") : null;
10072
+ spinner?.start();
10073
+ if (runArgs("git", ["pull", "--rebase", "--autostash"], { cwd: DATA_DIR8, timeout: 60000 })) {
10074
+ spinner?.succeed("Repository up to date");
10075
+ } else {
10076
+ spinner?.fail("Pull failed (non-fatal, continuing)");
10077
+ result.warnings.push("Could not pull latest changes");
10078
+ }
10079
+ } else if (!existsSync9(DATA_DIR8) || readdirSync3(DATA_DIR8).filter((f) => !f.startsWith(".")).length === 0) {
10080
+ if (ghAuth.available) {
10081
+ const spinner = isTTY ? new Spinner("Forking repository...") : null;
10082
+ spinner?.start();
10083
+ const ghUser = ghAuth.user || runArgsOutput("gh", ["api", "user", "-q", ".login"], { timeout: 15000 });
10084
+ const forkExists = runArgs("gh", ["repo", "view", `${ghUser}/datacore`], { cwd: process.env.HOME, timeout: 30000 });
10085
+ if (!forkExists) {
10086
+ const forked = runArgs("gh", ["repo", "fork", UPSTREAM_REPO, "--clone=false"], { timeout: 30000 });
10087
+ if (forked) {
10088
+ spinner?.succeed(`Forked to ${ghUser}/datacore`);
10089
+ } else {
10090
+ spinner?.fail("Fork failed");
10091
+ result.warnings.push("Could not fork repository - will clone directly");
10092
+ }
10093
+ } else {
10094
+ spinner?.succeed(`Fork exists: ${ghUser}/datacore`);
10095
+ }
10096
+ const cloneSpinner = isTTY ? new Spinner("Cloning into ~/Data...") : null;
10097
+ cloneSpinner?.start();
10098
+ mkdirSync6(DATA_DIR8, { recursive: true });
10099
+ const cloneUrl = `https://github.com/${ghUser}/datacore.git`;
10100
+ let cloned = runArgs("git", ["clone", cloneUrl, "."], { cwd: DATA_DIR8, timeout: 300000 });
10101
+ if (!cloned) {
10102
+ cloned = runArgs("git", ["clone", `git@github.com:${ghUser}/datacore.git`, "."], { cwd: DATA_DIR8, timeout: 300000 });
10103
+ }
10104
+ if (!cloned) {
10105
+ cloned = runArgs("git", ["clone", `https://github.com/${UPSTREAM_REPO}.git`, "."], { cwd: DATA_DIR8, timeout: 300000 });
10106
+ if (!cloned) {
10107
+ cloned = runArgs("git", ["clone", `git@github.com:${UPSTREAM_REPO}.git`, "."], { cwd: DATA_DIR8, timeout: 300000 });
10108
+ }
10109
+ }
10110
+ if (cloned) {
10111
+ cloneSpinner?.succeed("Cloned into ~/Data");
10112
+ result.created.push(DATA_DIR8);
10113
+ runArgs("git", ["remote", "add", "upstream", `https://github.com/${UPSTREAM_REPO}.git`], { cwd: DATA_DIR8 });
10114
+ } else {
10115
+ cloneSpinner?.fail("Clone failed");
10116
+ result.errors.push("Could not clone repository");
10117
+ op.failStep("clone_repo", "Clone failed");
10118
+ op.fail("Clone failed");
10119
+ return result;
10120
+ }
10121
+ } else {
10122
+ let repoUrl;
10123
+ if (interactive) {
10124
+ console.log(` ${c2.dim}GitHub CLI not authenticated. Cloning upstream directly.${c2.reset}`);
10125
+ console.log(` ${c2.dim}You can fork later with: gh repo fork --remote${c2.reset}`);
10126
+ console.log();
10127
+ repoUrl = await prompt(" Repository URL", `https://github.com/${UPSTREAM_REPO}.git`);
10128
+ } else {
10129
+ repoUrl = `https://github.com/${UPSTREAM_REPO}.git`;
10130
+ }
10131
+ mkdirSync6(DATA_DIR8, { recursive: true });
10132
+ const spinner = isTTY ? new Spinner("Cloning into ~/Data...") : null;
10133
+ spinner?.start();
10134
+ let cloned = runArgs("git", ["clone", repoUrl, "."], { cwd: DATA_DIR8, timeout: 300000 });
10135
+ if (!cloned) {
10136
+ const sshUrl = repoUrl.replace("https://github.com/", "git@github.com:");
10137
+ cloned = runArgs("git", ["clone", sshUrl, "."], { cwd: DATA_DIR8, timeout: 300000 });
10138
+ }
10139
+ if (cloned) {
10140
+ spinner?.succeed("Cloned into ~/Data");
10141
+ result.created.push(DATA_DIR8);
10142
+ } else {
10143
+ spinner?.fail("Clone failed");
10144
+ result.errors.push(`Could not clone from ${repoUrl}`);
10145
+ op.failStep("clone_repo", "Clone failed");
10146
+ op.fail("Clone failed");
10147
+ return result;
10148
+ }
10149
+ }
9217
10150
  } else {
10151
+ if (isTTY) {
10152
+ console.log(` ${c2.yellow}⚠${c2.reset} ~/Data exists but is not a git repository`);
10153
+ console.log(` ${c2.dim}Expected a cloned Datacore repo. Some features may not work.${c2.reset}`);
10154
+ }
10155
+ result.warnings.push(`${DATA_DIR8} is not a git repository`);
10156
+ }
10157
+ const dipsDir = join10(DATACORE_DIR, "dips");
10158
+ if (existsSync9(DATACORE_DIR) && !existsSync9(join10(dipsDir, ".git"))) {
10159
+ const spinner = isTTY ? new Spinner("Fetching specifications...") : null;
10160
+ spinner?.start();
10161
+ const dipsRepo = "https://github.com/datacore-one/datacore-dips.git";
10162
+ let clonedDips = runArgs("git", ["clone", dipsRepo, dipsDir], { timeout: 300000 });
10163
+ if (!clonedDips) {
10164
+ clonedDips = runArgs("git", ["clone", "git@github.com:datacore-one/datacore-dips.git", dipsDir], { timeout: 300000 });
10165
+ }
10166
+ if (clonedDips) {
10167
+ spinner?.succeed("Specifications installed");
10168
+ } else {
10169
+ spinner?.fail("Could not fetch specifications (non-fatal)");
10170
+ result.warnings.push("Specifications repo not cloned");
10171
+ }
10172
+ } else if (existsSync9(join10(dipsDir, ".git"))) {
9218
10173
  if (isTTY)
9219
- console.log(` ${c2.green}✓${c2.reset} Found ${DATA_DIR5}`);
10174
+ console.log(` ${c2.green}✓${c2.reset} Specifications present`);
10175
+ }
10176
+ if (commandExists3("git-lfs") && existsSync9(join10(DATA_DIR8, ".git"))) {
10177
+ const spinner = isTTY ? new Spinner("Initializing Git LFS...") : null;
10178
+ spinner?.start();
10179
+ if (runArgs("git", ["lfs", "install"], { cwd: DATA_DIR8, timeout: 30000 })) {
10180
+ runArgs("git", ["lfs", "pull"], { cwd: DATA_DIR8, timeout: 120000 });
10181
+ spinner?.succeed("Git LFS initialized");
10182
+ } else {
10183
+ spinner?.fail("Git LFS init failed (non-fatal)");
10184
+ }
9220
10185
  }
9221
10186
  if (isTTY)
9222
10187
  console.log();
9223
- op.completeStep("create_data_dir");
9224
- op.addStep("create_datacore_dir");
9225
- op.startStep("create_datacore_dir");
10188
+ op.completeStep("clone_repo");
10189
+ op.addStep("team_spaces");
10190
+ op.startStep("team_spaces");
10191
+ if (interactive) {
10192
+ section(`Step 4/${TOTAL_STEPS}: Team Spaces`);
10193
+ console.log();
10194
+ console.log(` ${c2.dim}Spaces separate different areas of your life. Your personal space${c2.reset}`);
10195
+ console.log(` ${c2.dim}(0-personal) is created automatically. Team spaces are separate${c2.reset}`);
10196
+ console.log(` ${c2.dim}git repos for organizations you work with.${c2.reset}`);
10197
+ console.log();
10198
+ if (profile.useCase === "personal") {
10199
+ console.log(` ${c2.dim}You selected personal use. You can add team spaces anytime later:${c2.reset}`);
10200
+ console.log(` ${c2.dim}datacore space create <name>${c2.reset}`);
10201
+ } else {
10202
+ console.log(` ${c2.dim}Each team space gets its own:${c2.reset}`);
10203
+ console.log(` ${c2.dim}• GTD task system and AI agents${c2.reset}`);
10204
+ console.log(` ${c2.dim}• Knowledge base (wiki, notes, research)${c2.reset}`);
10205
+ console.log(` ${c2.dim}• Project tracking via GitHub Issues${c2.reset}`);
10206
+ console.log();
10207
+ const existingSpaces = listSpaces();
10208
+ const existingNames = existingSpaces.map((s) => s.name.replace(/^\d+-/, ""));
10209
+ if (existingSpaces.length > 1) {
10210
+ console.log(` ${c2.dim}Existing spaces:${c2.reset}`);
10211
+ for (const s of existingSpaces) {
10212
+ console.log(` ${s.type === "personal" ? "\uD83D\uDC64" : "\uD83D\uDC65"} ${s.name}`);
10213
+ }
10214
+ console.log();
10215
+ }
10216
+ const wantSpace = await confirm(" Would you like to add a team space?", false);
10217
+ if (wantSpace) {
10218
+ let adding = true;
10219
+ while (adding) {
10220
+ const spaceName = await prompt(' Space name (e.g., "datafund", "acme-corp")');
10221
+ if (!spaceName) {
10222
+ adding = false;
10223
+ continue;
10224
+ }
10225
+ const normalized = spaceName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
10226
+ 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()));
10227
+ if (existingNames.includes(normalized) || existingNames.includes(knownMatch?.name ?? "")) {
10228
+ console.log(` ${c2.green}✓${c2.reset} ${spaceName} is already installed`);
10229
+ adding = await confirm(" Add another?", false);
10230
+ continue;
10231
+ }
10232
+ if (knownMatch) {
10233
+ console.log(` ${c2.green}✓${c2.reset} Found registered space: ${c2.bold}${knownMatch.displayName}${c2.reset}`);
10234
+ console.log(` ${c2.dim}${knownMatch.description}${c2.reset}`);
10235
+ const currentSpaces = listSpaces();
10236
+ const nextNum = currentSpaces.length > 0 ? Math.max(...currentSpaces.map((s) => s.number)) + 1 : 1;
10237
+ const spacePath = join10(DATA_DIR8, `${nextNum}-${knownMatch.name}`);
10238
+ const spinner = new Spinner(`Cloning ${knownMatch.displayName}...`);
10239
+ spinner.start();
10240
+ let cloned = runArgs("git", ["clone", knownMatch.repo, spacePath], { timeout: 300000 });
10241
+ if (!cloned) {
10242
+ const sshUrl = knownMatch.repo.replace("https://github.com/", "git@github.com:");
10243
+ cloned = runArgs("git", ["clone", sshUrl, spacePath], { timeout: 300000 });
10244
+ }
10245
+ if (cloned) {
10246
+ spinner.succeed(`Added space: ${nextNum}-${knownMatch.name}`);
10247
+ result.spacesCreated.push(`${nextNum}-${knownMatch.name}`);
10248
+ } else {
10249
+ spinner.fail(`Could not clone ${knownMatch.displayName}`);
10250
+ if (knownMatch.private) {
10251
+ console.log(` ${c2.dim}This is a private repo. Make sure you have access to ${knownMatch.org}.${c2.reset}`);
10252
+ console.log(` ${c2.dim}Request access or try: gh auth refresh -s read:org${c2.reset}`);
10253
+ }
10254
+ result.warnings.push(`Failed to add space: ${knownMatch.name}`);
10255
+ }
10256
+ } else {
10257
+ const repoUrl = await prompt(" Git repo URL (or Enter to create local)", "");
10258
+ if (repoUrl) {
10259
+ const currentSpaces = listSpaces();
10260
+ const nextNum = currentSpaces.length > 0 ? Math.max(...currentSpaces.map((s) => s.number)) + 1 : 1;
10261
+ const spacePath = join10(DATA_DIR8, `${nextNum}-${normalized}`);
10262
+ const spinner = new Spinner(`Cloning ${spaceName}...`);
10263
+ spinner.start();
10264
+ let cloned = runArgs("git", ["clone", repoUrl, spacePath], { timeout: 300000 });
10265
+ if (!cloned) {
10266
+ const sshUrl = repoUrl.replace("https://github.com/", "git@github.com:");
10267
+ cloned = runArgs("git", ["clone", sshUrl, spacePath], { timeout: 300000 });
10268
+ }
10269
+ if (cloned) {
10270
+ spinner.succeed(`Added space: ${nextNum}-${normalized}`);
10271
+ result.spacesCreated.push(`${nextNum}-${normalized}`);
10272
+ } else {
10273
+ spinner.fail(`Could not clone ${repoUrl}`);
10274
+ result.warnings.push(`Failed to add space: ${spaceName}`);
10275
+ }
10276
+ } else {
10277
+ try {
10278
+ const { createSpace: createSpace2 } = await Promise.resolve().then(() => (init_space(), exports_space));
10279
+ const space = createSpace2(spaceName, "team");
10280
+ result.created.push(space.path);
10281
+ result.spacesCreated.push(space.name);
10282
+ console.log(` ${c2.green}✓${c2.reset} Created ${space.name}/`);
10283
+ } catch (err) {
10284
+ console.log(` ${c2.red}✗${c2.reset} ${err.message}`);
10285
+ }
10286
+ }
10287
+ }
10288
+ adding = await confirm(" Add another?", false);
10289
+ }
10290
+ }
10291
+ }
10292
+ console.log();
10293
+ }
10294
+ op.completeStep("team_spaces");
10295
+ op.addStep("second_brain");
10296
+ op.startStep("second_brain");
9226
10297
  if (isTTY) {
9227
- section("Step 3: Creating .datacore");
9228
- console.log(` ${c2.dim}System configuration, agents, commands, and modules.${c2.reset}`);
10298
+ section(`Step 5/${TOTAL_STEPS}: Your Second Brain`);
10299
+ console.log();
10300
+ console.log(` ${c2.dim}Your personal space is your AI-powered second brain. Here's how${c2.reset}`);
10301
+ console.log(` ${c2.dim}it's organized:${c2.reset}`);
10302
+ console.log();
10303
+ console.log(` ${c2.bold}Getting Things Done (GTD)${c2.reset}`);
10304
+ console.log(` ${c2.dim}A trusted system where you capture everything into an inbox, then${c2.reset}`);
10305
+ console.log(` ${c2.dim}process it into actionable next steps. Nothing stays in your head.${c2.reset}`);
10306
+ console.log(` ${c2.dim}• org/inbox.org → Capture anything, anytime${c2.reset}`);
10307
+ console.log(` ${c2.dim}• org/next_actions.org → What you're actually doing${c2.reset}`);
10308
+ console.log(` ${c2.dim}• org/someday.org → Ideas for later${c2.reset}`);
10309
+ console.log();
10310
+ console.log(` ${c2.bold}AI Delegation${c2.reset}`);
10311
+ console.log(` ${c2.dim}Tag tasks with :AI: and agents handle them overnight:${c2.reset}`);
10312
+ console.log(` ${c2.dim}• :AI:research: → Deep research on any topic${c2.reset}`);
10313
+ console.log(` ${c2.dim}• :AI:content: → Draft emails, blog posts, docs${c2.reset}`);
10314
+ console.log(` ${c2.dim}• :AI:data: → Analyze data, generate reports${c2.reset}`);
10315
+ console.log(` ${c2.dim}• :AI:pm: → Track projects, flag blockers${c2.reset}`);
10316
+ console.log();
10317
+ console.log(` ${c2.bold}Knowledge Management${c2.reset}`);
10318
+ console.log(` ${c2.dim}Your knowledge compounds over time. Every note, insight, and${c2.reset}`);
10319
+ console.log(` ${c2.dim}conversation gets woven into a personal knowledge base:${c2.reset}`);
10320
+ console.log(` ${c2.dim}• notes/ → Daily journals, quick captures${c2.reset}`);
10321
+ console.log(` ${c2.dim}• 3-knowledge/ → Permanent knowledge (Zettelkasten)${c2.reset}`);
10322
+ console.log(` ${c2.dim}├── zettel/ → Atomic ideas, one concept per note${c2.reset}`);
10323
+ console.log(` ${c2.dim}├── pages/ → Longer topic pages and guides${c2.reset}`);
10324
+ console.log(` ${c2.dim}├── literature/ → Summaries of things you've read${c2.reset}`);
10325
+ console.log(` ${c2.dim}└── reference/ → People, companies, glossary${c2.reset}`);
10326
+ console.log();
10327
+ console.log(` ${c2.dim}The more you capture, the smarter your system becomes. Agents${c2.reset}`);
10328
+ console.log(` ${c2.dim}cross-reference your knowledge to give better answers over time.${c2.reset}`);
9229
10329
  console.log();
9230
10330
  }
9231
- if (!existsSync7(DATACORE_DIR)) {
9232
- const dirs = [
9233
- { path: "", desc: "" },
9234
- { path: "commands", desc: "Slash commands (/today, /sync, etc.)" },
9235
- { path: "agents", desc: "AI agents for task automation" },
9236
- { path: "modules", desc: "Optional extensions" },
9237
- { path: "specs", desc: "System documentation" },
9238
- { path: "lib", desc: "Shared utilities" },
9239
- { path: "env", desc: "API keys and secrets" },
9240
- { path: "state", desc: "Runtime state" },
9241
- { path: "registry", desc: "Agent/command discovery" }
9242
- ];
9243
- for (const { path: dir, desc } of dirs) {
9244
- const path = join7(DATACORE_DIR, dir);
9245
- mkdirSync4(path, { recursive: true });
9246
- result.created.push(path);
9247
- if (isTTY && dir) {
9248
- console.log(` ${c2.green}✓${c2.reset} ${dir}/ ${c2.dim}- ${desc}${c2.reset}`);
10331
+ const personalPath = join10(DATA_DIR8, "0-personal");
10332
+ if (existsSync9(personalPath)) {
10333
+ if (isTTY)
10334
+ console.log(` ${c2.green}✓${c2.reset} Personal space ready (0-personal/)`);
10335
+ result.spacesCreated.push("0-personal");
10336
+ } else {
10337
+ if (isTTY)
10338
+ console.log(` ${c2.dim}Creating personal space...${c2.reset}`);
10339
+ try {
10340
+ const { createSpace: createSpace2 } = await Promise.resolve().then(() => (init_space(), exports_space));
10341
+ const space = createSpace2("personal", "personal");
10342
+ result.created.push(space.path);
10343
+ result.spacesCreated.push("0-personal");
10344
+ if (isTTY)
10345
+ console.log(` ${c2.green}✓${c2.reset} Personal space created (0-personal/)`);
10346
+ } catch {
10347
+ const dirs = ["org", "notes", "notes/journals", "notes/pages", "journal", "3-knowledge", "content", "0-inbox"];
10348
+ for (const dir of dirs) {
10349
+ mkdirSync6(join10(personalPath, dir), { recursive: true });
9249
10350
  }
10351
+ result.created.push(personalPath);
10352
+ result.spacesCreated.push("0-personal");
10353
+ if (isTTY)
10354
+ console.log(` ${c2.green}✓${c2.reset} Personal space initialized`);
9250
10355
  }
9251
- writeFileSync4(join7(DATACORE_DIR, "settings.yaml"), `# Datacore Settings
9252
- # Override in settings.local.yaml (gitignored)
9253
-
9254
- editor:
9255
- open_markdown_on_generate: true
9256
- open_command: ""
9257
-
9258
- sync:
9259
- pull_on_today: true
9260
- push_on_wrap_up: true
9261
-
9262
- journal:
9263
- open_after_update: false
9264
-
9265
- nightshift:
9266
- server: ""
9267
- auto_trigger: false
9268
- `);
9269
- result.created.push(join7(DATACORE_DIR, "settings.yaml"));
9270
- writeFileSync4(join7(DATACORE_DIR, "registry", "agents.yaml"), `# Agent Registry
9271
- agents: []
10356
+ }
10357
+ const gtdFiles = ["inbox.org", "next_actions.org", "someday.org"];
10358
+ const orgDir = join10(personalPath, "org");
10359
+ let gtdReady = true;
10360
+ for (const file of gtdFiles) {
10361
+ if (!existsSync9(join10(orgDir, file)))
10362
+ gtdReady = false;
10363
+ }
10364
+ if (gtdReady) {
10365
+ if (isTTY)
10366
+ console.log(` ${c2.green}✓${c2.reset} GTD system initialized`);
10367
+ }
10368
+ if (existsSync9(join10(personalPath, "3-knowledge"))) {
10369
+ if (isTTY)
10370
+ console.log(` ${c2.green}✓${c2.reset} Knowledge base ready`);
10371
+ }
10372
+ const templates = [
10373
+ { src: "install.yaml.example", dst: "install.yaml", label: "Installation manifest" },
10374
+ { src: "0-personal/org/inbox.org.example", dst: "0-personal/org/inbox.org", label: "GTD inbox" },
10375
+ { src: "0-personal/org/next_actions.org.example", dst: "0-personal/org/next_actions.org", label: "GTD next actions" },
10376
+ { src: "0-personal/org/someday.org.example", dst: "0-personal/org/someday.org", label: "GTD someday" },
10377
+ { src: "0-personal/org/habits.org.example", dst: "0-personal/org/habits.org", label: "GTD habits" }
10378
+ ];
10379
+ for (const { src, dst } of templates) {
10380
+ const srcPath = join10(DATA_DIR8, src);
10381
+ const dstPath = join10(DATA_DIR8, dst);
10382
+ if (existsSync9(srcPath) && !existsSync9(dstPath)) {
10383
+ copyFileSync(srcPath, dstPath);
10384
+ result.created.push(dstPath);
10385
+ }
10386
+ }
10387
+ if (isTTY)
10388
+ console.log(` ${c2.green}✓${c2.reset} Templates activated`);
10389
+ const claudeLocalPath = join10(DATA_DIR8, "CLAUDE.local.md");
10390
+ if (!existsSync9(claudeLocalPath)) {
10391
+ const localContent = [
10392
+ `<!-- PRIVATE LAYER - This file is gitignored and never shared -->`,
10393
+ ``,
10394
+ `# ${profile.name || "My"}'s Datacore`,
10395
+ ``,
10396
+ profile.role ? `Role: ${profile.role}` : "",
10397
+ ``,
10398
+ `## My Workflow`,
10399
+ ``,
10400
+ `<!-- Add your personal workflow notes, preferences, and shortcuts here. -->`,
10401
+ `<!-- This file is gitignored and only visible to your local Claude Code. -->`,
10402
+ ``,
10403
+ `## Custom Context`,
10404
+ ``,
10405
+ `<!-- Any private context that helps Claude assist you better: -->`,
10406
+ `<!-- - Project abbreviations and shorthand -->`,
10407
+ `<!-- - Personal communication preferences -->`,
10408
+ `<!-- - Domain expertise and background -->`,
10409
+ ``
10410
+ ].filter(Boolean).join(`
9272
10411
  `);
9273
- writeFileSync4(join7(DATACORE_DIR, "registry", "commands.yaml"), `# Command Registry
9274
- commands: []
10412
+ writeFileSync5(claudeLocalPath, localContent);
10413
+ result.created.push(claudeLocalPath);
10414
+ if (isTTY)
10415
+ console.log(` ${c2.green}✓${c2.reset} CLAUDE.local.md created (your private AI context)`);
10416
+ }
10417
+ const settingsLocalPath = join10(DATACORE_DIR, "settings.local.yaml");
10418
+ if (existsSync9(DATACORE_DIR) && !existsSync9(settingsLocalPath)) {
10419
+ const settingsContent = [
10420
+ `# Personal Settings Overrides (gitignored)`,
10421
+ `# See settings.yaml for all available options`,
10422
+ ``,
10423
+ `editor:`,
10424
+ ` open_markdown_on_generate: false`,
10425
+ ``,
10426
+ `sync:`,
10427
+ ` pull_on_today: true`,
10428
+ ` push_on_wrap_up: true`,
10429
+ ``
10430
+ ].join(`
9275
10431
  `);
9276
- if (isTTY) {
9277
- console.log();
9278
- console.log(` ${c2.green}✓${c2.reset} Created settings.yaml`);
9279
- console.log(` ${c2.dim}Customize in settings.local.yaml${c2.reset}`);
9280
- }
9281
- } else {
10432
+ writeFileSync5(settingsLocalPath, settingsContent);
10433
+ result.created.push(settingsLocalPath);
9282
10434
  if (isTTY)
9283
- console.log(` ${c2.green}✓${c2.reset} Found existing .datacore/`);
10435
+ console.log(` ${c2.green}✓${c2.reset} settings.local.yaml created (your preferences)`);
9284
10436
  }
9285
10437
  if (isTTY)
9286
10438
  console.log();
9287
- op.completeStep("create_datacore_dir");
9288
- op.addStep("create_personal_space");
9289
- op.startStep("create_personal_space");
10439
+ op.completeStep("second_brain");
10440
+ op.addStep("modules");
10441
+ op.startStep("modules");
9290
10442
  if (isTTY) {
9291
- section("Step 4: Creating Personal Space");
9292
- console.log(` ${c2.dim}Your personal GTD system and knowledge base.${c2.reset}`);
10443
+ section(`Step 6/${TOTAL_STEPS}: Modules`);
10444
+ console.log();
10445
+ console.log(` ${c2.dim}Modules extend Datacore with specialized capabilities. Each adds${c2.reset}`);
10446
+ console.log(` ${c2.dim}new AI agents and /commands you can use in Claude Code.${c2.reset}`);
9293
10447
  console.log();
9294
10448
  }
9295
- const existingSpaces = listSpaces();
9296
- const hasPersonal = existingSpaces.some((s) => s.number === 0);
9297
- if (!hasPersonal) {
9298
- const space = createSpace("personal", "personal");
9299
- result.created.push(space.path);
9300
- result.spacesCreated.push(space.name);
9301
- if (isTTY) {
9302
- console.log(` ${c2.green}✓${c2.reset} Created 0-personal/`);
9303
- console.log();
9304
- console.log(` ${c2.bold}GTD Inbox:${c2.reset} org/inbox.org`);
9305
- console.log(` ${c2.dim}Capture everything here, process to zero daily${c2.reset}`);
9306
- console.log();
9307
- console.log(` ${c2.bold}Next Actions:${c2.reset} org/next_actions.org`);
9308
- console.log(` ${c2.dim}Tasks by context: @computer, @phone, @errands${c2.reset}`);
9309
- console.log(` ${c2.dim}Tag with :AI: to delegate to agents${c2.reset}`);
9310
- console.log();
9311
- console.log(` ${c2.bold}Knowledge:${c2.reset} notes/`);
9312
- console.log(` ${c2.dim}Your personal wiki and note archive${c2.reset}`);
10449
+ const modulesDir = join10(DATACORE_DIR, "modules");
10450
+ if (existsSync9(DATACORE_DIR)) {
10451
+ mkdirSync6(modulesDir, { recursive: true });
10452
+ }
10453
+ const installedNames = listModules().map((m) => m.name);
10454
+ const allModules = AVAILABLE_MODULES;
10455
+ let modulesToInstall = [];
10456
+ if (interactive) {
10457
+ console.log(` ${c2.dim}All modules are selected by default. Deselect any you don't need:${c2.reset}`);
10458
+ console.log();
10459
+ const selected = new Set(allModules.map((_, i) => i));
10460
+ for (let i = 0;i < allModules.length; i++) {
10461
+ const mod = allModules[i];
10462
+ const isInstalled = installedNames.includes(mod.name);
10463
+ const coreLabel = mod.core ? ` ${c2.dim}(core)${c2.reset}` : "";
10464
+ const installedLabel = isInstalled ? ` ${c2.dim}(installed)${c2.reset}` : "";
10465
+ const num = String(i + 1).padStart(2, " ");
10466
+ console.log(` [x] ${c2.cyan}${num}${c2.reset}. ${c2.bold}${mod.name.padEnd(12)}${c2.reset} ${mod.description}${coreLabel}${installedLabel}`);
10467
+ }
10468
+ console.log();
10469
+ const removeInput = await prompt(" Enter numbers to REMOVE, or press Enter to install all", "");
10470
+ if (removeInput) {
10471
+ const nums = removeInput.split(/[,\s]+/).map((s) => parseInt(s.trim(), 10));
10472
+ for (const n of nums) {
10473
+ if (n >= 1 && n <= allModules.length) {
10474
+ const mod = allModules[n - 1];
10475
+ if (!mod.core) {
10476
+ selected.delete(n - 1);
10477
+ }
10478
+ }
10479
+ }
9313
10480
  }
10481
+ modulesToInstall = allModules.filter((_, i) => selected.has(i));
9314
10482
  } else {
9315
- if (isTTY) {
9316
- console.log(` ${c2.green}✓${c2.reset} Found existing personal space`);
10483
+ modulesToInstall = [...allModules];
10484
+ }
10485
+ let installCount = 0;
10486
+ for (const mod of modulesToInstall) {
10487
+ if (installedNames.includes(mod.name)) {
10488
+ installCount++;
10489
+ continue;
10490
+ }
10491
+ const spinner = isTTY ? new Spinner(`Installing ${mod.name}...`) : null;
10492
+ spinner?.start();
10493
+ try {
10494
+ await sleep(100);
10495
+ const info2 = installModule(mod.repo);
10496
+ result.modulesInstalled.push(mod.name);
10497
+ installCount++;
10498
+ const postInstall = runModulePostInstall(info2.path);
10499
+ if (postInstall.ran && postInstall.success) {
10500
+ spinner?.succeed(`${mod.name} - dependencies installed (${postInstall.type})`);
10501
+ } else if (postInstall.ran && !postInstall.success) {
10502
+ spinner?.succeed(`${mod.name}`);
10503
+ if (isTTY)
10504
+ console.log(` ${c2.yellow}⚠${c2.reset} ${c2.dim}Dependency install failed (${postInstall.type})${c2.reset}`);
10505
+ } else {
10506
+ spinner?.succeed(mod.name);
10507
+ }
10508
+ } catch (err) {
10509
+ spinner?.fail(`${mod.name} - ${err.message}`);
10510
+ result.warnings.push(`Module ${mod.name} failed to install: ${err.message}`);
10511
+ }
10512
+ }
10513
+ if (isTTY) {
10514
+ console.log();
10515
+ console.log(` ${c2.green}${installCount} modules installed.${c2.reset}`);
10516
+ console.log();
10517
+ }
10518
+ op.completeStep("modules");
10519
+ op.addStep("finalize");
10520
+ op.startStep("finalize");
10521
+ if (isTTY) {
10522
+ section(`Step 7/${TOTAL_STEPS}: Finalize`);
10523
+ console.log();
10524
+ }
10525
+ const contextMerge = join10(DATACORE_DIR, "lib", "context_merge.py");
10526
+ if (existsSync9(contextMerge)) {
10527
+ const spinner = isTTY ? new Spinner("Building CLAUDE.md from layers...") : null;
10528
+ spinner?.start();
10529
+ if (runArgs("python3", [contextMerge, "rebuild", "--path", DATA_DIR8, "--all"])) {
10530
+ spinner?.succeed("CLAUDE.md built from layers (all spaces)");
10531
+ } else {
10532
+ spinner?.fail("CLAUDE.md build failed (can rebuild later)");
10533
+ result.warnings.push("Could not build CLAUDE.md");
10534
+ }
10535
+ }
10536
+ const zettelDb = join10(DATACORE_DIR, "lib", "zettel_db.py");
10537
+ if (existsSync9(zettelDb)) {
10538
+ const spinner = isTTY ? new Spinner("Initializing knowledge database...") : null;
10539
+ spinner?.start();
10540
+ if (runArgs("python3", [zettelDb, "init-all"], { cwd: DATA_DIR8 })) {
10541
+ spinner?.succeed("Knowledge database initialized");
10542
+ } else {
10543
+ spinner?.fail("Database init failed (non-fatal)");
10544
+ result.warnings.push("Database initialization failed");
10545
+ }
10546
+ }
10547
+ if (existsSync9(DATACORE_DIR)) {
10548
+ const stateDir = join10(DATACORE_DIR, "state");
10549
+ const envDir = join10(DATACORE_DIR, "env");
10550
+ mkdirSync6(stateDir, { recursive: true });
10551
+ mkdirSync6(envDir, { recursive: true });
10552
+ if (!existsSync9(join10(stateDir, ".gitkeep")))
10553
+ writeFileSync5(join10(stateDir, ".gitkeep"), "");
10554
+ if (!existsSync9(join10(envDir, ".gitkeep")))
10555
+ writeFileSync5(join10(envDir, ".gitkeep"), "");
10556
+ if (isTTY)
10557
+ console.log(` ${c2.green}✓${c2.reset} Runtime directories ready`);
10558
+ }
10559
+ const claudeDir = join10(DATA_DIR8, ".claude");
10560
+ if (!existsSync9(claudeDir) && existsSync9(DATACORE_DIR)) {
10561
+ try {
10562
+ symlinkSync(DATACORE_DIR, claudeDir);
10563
+ result.created.push(claudeDir);
10564
+ } catch {}
10565
+ }
10566
+ const syncScript = join10(DATA_DIR8, "sync");
10567
+ if (existsSync9(syncScript)) {
10568
+ if (platform2 !== "windows") {
10569
+ runArgs("chmod", ["+x", syncScript]);
10570
+ }
10571
+ if (isTTY)
10572
+ console.log(` ${c2.green}✓${c2.reset} Sync script configured`);
10573
+ }
10574
+ const installYaml = join10(DATA_DIR8, "install.yaml");
10575
+ if (existsSync9(installYaml) || existsSync9(DATACORE_DIR)) {
10576
+ try {
10577
+ const allSpaces = listSpaces();
10578
+ const allInstalledModules = listModules();
10579
+ const spacesYaml = allSpaces.filter((s) => s.type !== "personal").map((s) => ` ${s.name}:
10580
+ path: ${s.name}`).join(`
10581
+ `);
10582
+ const modulesYaml = allInstalledModules.map((m) => ` - ${m.name}`).join(`
10583
+ `);
10584
+ const yamlContent = [
10585
+ `# Datacore Installation Manifest`,
10586
+ `# Generated by: datacore init`,
10587
+ `# Date: ${new Date().toISOString().split("T")[0]}`,
10588
+ ``,
10589
+ `meta:`,
10590
+ ` name: "${profile.name ? `${profile.name}'s Datacore` : "My Datacore"}"`,
10591
+ ` root: "${DATA_DIR8}"`,
10592
+ ` version: 1.0.0`,
10593
+ profile.role ? ` role: "${profile.role}"` : null,
10594
+ ` use_case: ${profile.useCase}`,
10595
+ ``,
10596
+ `modules:`,
10597
+ modulesYaml || " []",
10598
+ ``,
10599
+ `personal:`,
10600
+ ` path: 0-personal`,
10601
+ ``,
10602
+ `spaces:`,
10603
+ spacesYaml || " {}",
10604
+ ``
10605
+ ].filter((line) => line !== null).join(`
10606
+ `);
10607
+ writeFileSync5(installYaml, yamlContent);
10608
+ if (isTTY)
10609
+ console.log(` ${c2.green}✓${c2.reset} install.yaml saved`);
10610
+ } catch {
10611
+ result.warnings.push("Could not update install.yaml");
9317
10612
  }
9318
10613
  }
10614
+ try {
10615
+ const snapshot = createSnapshot();
10616
+ saveSnapshot(snapshot);
10617
+ if (isTTY)
10618
+ console.log(` ${c2.green}✓${c2.reset} Snapshot created (datacore.lock.yaml)`);
10619
+ } catch {
10620
+ result.warnings.push("Could not create snapshot");
10621
+ }
9319
10622
  if (isTTY)
9320
10623
  console.log();
9321
- op.completeStep("create_personal_space");
10624
+ op.completeStep("finalize");
10625
+ op.addStep("import_data");
10626
+ op.startStep("import_data");
10627
+ const backgroundJobs = [];
10628
+ const canBackgroundIngest = commandExists3("datacore") && commandExists3("claude");
9322
10629
  if (interactive) {
9323
- section("Step 5: Additional Spaces");
9324
- console.log(` ${c2.dim}Spaces separate different areas of your life (work, projects, teams).${c2.reset}`);
9325
- console.log(` ${c2.dim}Each space is a separate git repo with its own GTD system.${c2.reset}`);
10630
+ section(`Step 8/${TOTAL_STEPS}: Import Your Data`);
9326
10631
  console.log();
9327
- const wantMoreSpaces = await confirm("Would you like to create additional spaces?", false);
9328
- if (wantMoreSpaces) {
9329
- let creating = true;
9330
- while (creating) {
9331
- const name = await prompt('Space name (e.g., "work", "acme-corp")');
9332
- if (!name) {
9333
- creating = false;
9334
- continue;
10632
+ console.log(` ${c2.dim}Your second brain works best when it has your existing knowledge.${c2.reset}`);
10633
+ console.log(` ${c2.dim}You can import data now or do it later with 'datacore ingest'.${c2.reset}`);
10634
+ console.log();
10635
+ let importing = true;
10636
+ let totalImported = 0;
10637
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10638
+ mkdirSync6(inboxDir, { recursive: true });
10639
+ const IMPORT_SKIP = 4;
10640
+ while (importing) {
10641
+ console.log(` ${c2.dim}Common sources to import:${c2.reset}`);
10642
+ console.log(` ${c2.cyan}1${c2.reset}) ChatGPT conversation exports (JSON)`);
10643
+ console.log(` ${c2.cyan}2${c2.reset}) Documents folder (PDFs, Word docs, markdown)`);
10644
+ console.log(` ${c2.cyan}3${c2.reset}) Existing notes (Obsidian, Notion export, etc.)`);
10645
+ console.log(` ${c2.cyan}4${c2.reset}) Skip for now`);
10646
+ console.log();
10647
+ const choice = await prompt(" What would you like to import?", "4");
10648
+ const choiceNum = parseInt(choice, 10);
10649
+ if (choiceNum === IMPORT_SKIP || !choice) {
10650
+ if (totalImported === 0) {
10651
+ console.log();
10652
+ console.log(` ${c2.dim}No problem! You can import data anytime:${c2.reset}`);
10653
+ console.log(` ${c2.dim}datacore ingest ~/path/to/files${c2.reset}`);
10654
+ console.log(` ${c2.dim}datacore ingest ~/Downloads/chatgpt-export.json${c2.reset}`);
9335
10655
  }
9336
- try {
9337
- const space = createSpace(name, "team");
9338
- result.created.push(space.path);
9339
- result.spacesCreated.push(space.name);
9340
- console.log(` ${c2.green}✓${c2.reset} Created ${space.name}/`);
9341
- } catch (err) {
9342
- console.log(` ${c2.red}✗${c2.reset} ${err.message}`);
10656
+ importing = false;
10657
+ continue;
10658
+ }
10659
+ if (choiceNum === 1) {
10660
+ const chatPath = await prompt(" Path to ChatGPT export");
10661
+ const chatResolved = chatPath?.startsWith("~") ? join10(process.env.HOME || "", chatPath.slice(1)) : chatPath;
10662
+ if (chatResolved && existsSync9(chatResolved)) {
10663
+ const resolvedPath = chatResolved;
10664
+ const spinner = new Spinner("Processing ChatGPT export...");
10665
+ spinner.start();
10666
+ try {
10667
+ const content = readFileSync5(resolvedPath, "utf-8");
10668
+ const data = JSON.parse(content);
10669
+ const count = Array.isArray(data) ? data.length : 1;
10670
+ const destName = `chatgpt-export-${Date.now()}.json`;
10671
+ const destPath = join10(inboxDir, destName);
10672
+ copyFileSync(resolvedPath, destPath);
10673
+ if (canBackgroundIngest) {
10674
+ const job = spawnBackground("datacore", ["ingest", destPath], "ingest");
10675
+ if (job) {
10676
+ backgroundJobs.push(job);
10677
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: destPath });
10678
+ bgOp.start();
10679
+ spinner.succeed(`Found ${count} conversations - queued for background processing`);
10680
+ } else {
10681
+ spinner.succeed(`Found ${count} conversations - copied to inbox`);
10682
+ console.log(` ${c2.dim}Process with: datacore ingest ${destPath}${c2.reset}`);
10683
+ }
10684
+ } else {
10685
+ spinner.succeed(`Found ${count} conversations - copied to inbox`);
10686
+ console.log(` ${c2.dim}Process with /ingest in Claude Code for full knowledge extraction${c2.reset}`);
10687
+ }
10688
+ totalImported += count;
10689
+ } catch {
10690
+ spinner.fail("Could not parse ChatGPT export");
10691
+ try {
10692
+ const fallbackDest = join10(inboxDir, basename5(resolvedPath));
10693
+ copyFileSync(resolvedPath, fallbackDest);
10694
+ if (canBackgroundIngest) {
10695
+ const job = spawnBackground("datacore", ["ingest", fallbackDest], "ingest");
10696
+ if (job) {
10697
+ backgroundJobs.push(job);
10698
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: fallbackDest });
10699
+ bgOp.start();
10700
+ console.log(` ${c2.dim}File queued for background processing${c2.reset}`);
10701
+ } else {
10702
+ console.log(` ${c2.dim}File copied to inbox for later processing${c2.reset}`);
10703
+ }
10704
+ } else {
10705
+ console.log(` ${c2.dim}File copied to inbox for later processing${c2.reset}`);
10706
+ }
10707
+ } catch {}
10708
+ }
10709
+ } else {
10710
+ console.log(` ${c2.yellow}⚠${c2.reset} File not found: ${chatPath}`);
10711
+ }
10712
+ } else if (choiceNum === 2 || choiceNum === 3) {
10713
+ const label = choiceNum === 2 ? "documents" : "notes";
10714
+ const sourcePath = await prompt(` Path to ${label}`);
10715
+ if (sourcePath) {
10716
+ const resolvedPath = sourcePath.startsWith("~") ? join10(process.env.HOME || "", sourcePath.slice(1)) : sourcePath;
10717
+ if (existsSync9(resolvedPath)) {
10718
+ const spinner = new Spinner(`Scanning ${resolvedPath}...`);
10719
+ spinner.start();
10720
+ const stats = statSync2(resolvedPath);
10721
+ if (stats.isDirectory()) {
10722
+ const { total, byExt } = countFiles(resolvedPath);
10723
+ const extSummary = Object.entries(byExt).sort(([, a], [, b]) => b - a).slice(0, 4).map(([ext, count]) => `${count} .${ext}`).join(", ");
10724
+ spinner.succeed(`Found ${total} files (${extSummary})`);
10725
+ const destDir = join10(inboxDir, basename5(resolvedPath));
10726
+ const copySpinner = new Spinner("Copying to inbox...");
10727
+ copySpinner.start();
10728
+ try {
10729
+ cpSync(resolvedPath, destDir, { recursive: true });
10730
+ if (canBackgroundIngest) {
10731
+ const job = spawnBackground("datacore", ["ingest", destDir], "ingest");
10732
+ if (job) {
10733
+ backgroundJobs.push(job);
10734
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: destDir });
10735
+ bgOp.start();
10736
+ copySpinner.succeed(`${total} files queued for background processing`);
10737
+ } else {
10738
+ copySpinner.succeed(`${total} files copied to inbox`);
10739
+ console.log(` ${c2.dim}Process with: datacore ingest ${destDir}${c2.reset}`);
10740
+ }
10741
+ } else {
10742
+ copySpinner.succeed(`${total} files copied to inbox`);
10743
+ console.log(` ${c2.dim}Process with /ingest in Claude Code for full knowledge extraction${c2.reset}`);
10744
+ }
10745
+ totalImported += total;
10746
+ } catch {
10747
+ copySpinner.fail("Copy failed");
10748
+ }
10749
+ } else {
10750
+ const singleDest = join10(inboxDir, basename5(resolvedPath));
10751
+ copyFileSync(resolvedPath, singleDest);
10752
+ if (canBackgroundIngest) {
10753
+ const job = spawnBackground("datacore", ["ingest", singleDest], "ingest");
10754
+ if (job) {
10755
+ backgroundJobs.push(job);
10756
+ const bgOp = startOperation("background-ingest", { pid: job.pid, logFile: job.logFile, path: singleDest });
10757
+ bgOp.start();
10758
+ spinner.succeed("File queued for background processing");
10759
+ } else {
10760
+ spinner.succeed("File copied to inbox");
10761
+ console.log(` ${c2.dim}Process with: datacore ingest ${singleDest}${c2.reset}`);
10762
+ }
10763
+ } else {
10764
+ spinner.succeed("File copied to inbox");
10765
+ }
10766
+ totalImported++;
10767
+ }
10768
+ } else {
10769
+ console.log(` ${c2.yellow}⚠${c2.reset} Path not found: ${sourcePath}`);
10770
+ }
9343
10771
  }
9344
- creating = await confirm("Create another space?", false);
9345
10772
  }
10773
+ console.log();
10774
+ importing = await confirm(" Import more?", false);
10775
+ if (importing)
10776
+ console.log();
9346
10777
  }
9347
10778
  console.log();
9348
10779
  }
9349
- if (interactive) {
9350
- section("Step 6: Install Modules");
9351
- console.log(` ${c2.dim}Modules add features like overnight AI processing, CRM, and more.${c2.reset}`);
10780
+ op.completeStep("import_data");
10781
+ op.addStep("verification");
10782
+ op.startStep("verification");
10783
+ const claudeAvailable = commandExists3("claude");
10784
+ if (claudeAvailable && isTTY) {
10785
+ section(`Step 9/${TOTAL_STEPS}: Verification`);
9352
10786
  console.log();
9353
- const available = getAvailableModules();
9354
- if (available.length > 0) {
9355
- const wantModules = await confirm("Would you like to install modules?", true);
9356
- if (wantModules) {
9357
- console.log();
9358
- console.log(" Available modules:");
9359
- console.log();
9360
- for (let i = 0;i < available.length; i++) {
9361
- const mod = available[i];
9362
- console.log(` ${c2.cyan}${i + 1}${c2.reset}) ${c2.bold}${mod.name}${c2.reset} - ${mod.description}`);
9363
- for (const feature of mod.features) {
9364
- console.log(` ${c2.dim} ${feature}${c2.reset}`);
9365
- }
9366
- console.log();
9367
- }
9368
- const choices = await prompt('Enter numbers to install (comma-separated, or "all", or "none")');
9369
- let toInstall = [];
9370
- if (choices.toLowerCase() === "all") {
9371
- toInstall = available.map((m) => m.name);
9372
- } else if (choices && choices.toLowerCase() !== "none") {
9373
- const nums = choices.split(",").map((s) => parseInt(s.trim(), 10));
9374
- toInstall = nums.filter((n) => n >= 1 && n <= available.length).map((n) => available[n - 1].name);
10787
+ console.log(` ${c2.dim}Running AI verification to check everything is configured correctly...${c2.reset}`);
10788
+ console.log();
10789
+ const spinner = new Spinner("Claude Code structural integrity check...");
10790
+ spinner.start();
10791
+ try {
10792
+ const agentResult = await invokeAgent({
10793
+ agent: "structural-integrity",
10794
+ params: { mode: "report", scope: "all" }
10795
+ }, { cwd: DATA_DIR8, timeout: 120000 });
10796
+ if (agentResult.success) {
10797
+ spinner.succeed("All checks passed");
10798
+ } else {
10799
+ spinner.fail("Verification completed with issues");
10800
+ if (agentResult.error) {
10801
+ result.warnings.push(`Verification: ${agentResult.error}`);
9375
10802
  }
9376
- for (const modName of toInstall) {
9377
- const mod = available.find((m) => m.name === modName);
9378
- if (!mod)
9379
- continue;
9380
- const spinner = new Spinner(`Installing ${modName}...`);
9381
- spinner.start();
9382
- try {
9383
- await sleep(100);
9384
- installModule(mod.repo);
9385
- spinner.succeed(`Installed ${modName}`);
9386
- result.modulesInstalled.push(modName);
9387
- } catch (err) {
9388
- spinner.fail(`Failed to install ${modName}: ${err.message}`);
9389
- result.warnings.push(`Failed to install ${modName}`);
9390
- }
10803
+ }
10804
+ } catch {
10805
+ spinner.fail("Verification skipped (Claude Code not responding)");
10806
+ result.warnings.push("Could not run verification - run /structural-integrity manually");
10807
+ }
10808
+ const allSpaces = listSpaces();
10809
+ const allInstalledModules = listModules();
10810
+ console.log();
10811
+ console.log(` ${c2.dim}Spaces: ${allSpaces.length} (${allSpaces.map((s) => s.name).join(", ")})${c2.reset}`);
10812
+ console.log(` ${c2.dim}Modules: ${allInstalledModules.length} installed${c2.reset}`);
10813
+ const inboxOrg = join10(DATA_DIR8, "0-personal", "org", "inbox.org");
10814
+ if (existsSync9(inboxOrg)) {
10815
+ try {
10816
+ const content = readFileSync5(inboxOrg, "utf-8");
10817
+ const todoCount = (content.match(/^\* TODO /gm) || []).length;
10818
+ if (todoCount > 0) {
10819
+ console.log(` ${c2.dim}GTD: ${todoCount} inbox items${c2.reset}`);
9391
10820
  }
10821
+ } catch {}
10822
+ }
10823
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10824
+ if (existsSync9(inboxDir)) {
10825
+ const { total } = countFiles(inboxDir);
10826
+ if (total > 0) {
10827
+ console.log(` ${c2.dim}Import: ${total} files in inbox${c2.reset}`);
9392
10828
  }
9393
- } else {
9394
- console.log(` ${c2.dim}No additional modules available to install.${c2.reset}`);
9395
10829
  }
9396
10830
  console.log();
9397
- }
9398
- op.addStep("create_claude_symlink");
9399
- op.startStep("create_claude_symlink");
9400
- if (isTTY) {
9401
- section("Step 7: Claude Code Integration");
9402
- console.log(` ${c2.dim}Connecting Datacore to Claude Code AI assistant.${c2.reset}`);
10831
+ } else if (isTTY) {
10832
+ section(`Step 9/${TOTAL_STEPS}: Verification`);
9403
10833
  console.log();
9404
- }
9405
- const claudeDir = join7(DATA_DIR5, ".claude");
9406
- if (!existsSync7(claudeDir)) {
9407
- try {
9408
- symlinkSync(DATACORE_DIR, claudeDir);
9409
- result.created.push(claudeDir);
9410
- if (isTTY)
9411
- console.log(` ${c2.green}✓${c2.reset} Created .claude -> .datacore symlink`);
9412
- } catch {
9413
- result.warnings.push("Could not create .claude symlink");
9414
- if (isTTY)
9415
- console.log(` ${c2.yellow}○${c2.reset} Could not create symlink`);
9416
- }
9417
- } else {
9418
- if (isTTY)
9419
- console.log(` ${c2.green}✓${c2.reset} Found existing .claude/`);
9420
- }
9421
- const claudeMd = join7(DATA_DIR5, "CLAUDE.md");
9422
- const claudeBaseMd = join7(DATA_DIR5, "CLAUDE.base.md");
9423
- if (!existsSync7(claudeMd) && !existsSync7(claudeBaseMd)) {
9424
- const allSpaces = listSpaces();
9425
- writeFileSync4(claudeBaseMd, `# Datacore
9426
-
9427
- AI-powered second brain built on GTD methodology.
9428
-
9429
- ## Quick Start
9430
-
9431
- \`\`\`bash
9432
- cd ~/Data && claude
9433
- \`\`\`
9434
-
9435
- ## Daily Workflow
9436
-
9437
- 1. **Morning**: Run \`/today\` for your daily briefing
9438
- 2. **Capture**: Add tasks to \`0-personal/org/inbox.org\`
9439
- 3. **Process**: Clear inbox, organize by context
9440
- 4. **Work**: Use \`:AI:\` tags to delegate tasks
9441
- 5. **Evening**: Run \`/tomorrow\` for wrap-up
9442
-
9443
- ## Commands
9444
-
9445
- - \`/today\` - Daily briefing with priorities
9446
- - \`/tomorrow\` - End-of-day wrap-up
9447
- - \`/sync\` - Sync all repos
9448
- - \`datacore doctor\` - Check system health
9449
-
9450
- ## Spaces
9451
-
9452
- ${allSpaces.map((s) => `- ${s.name}`).join(`
9453
- `) || "- 0-personal"}
9454
-
9455
- ## Documentation
9456
-
9457
- See .datacore/specs/ for detailed documentation.
9458
- `);
9459
- result.created.push(claudeBaseMd);
9460
- if (isTTY)
9461
- console.log(` ${c2.green}✓${c2.reset} Created CLAUDE.base.md`);
9462
- } else {
9463
- if (isTTY)
9464
- console.log(` ${c2.green}✓${c2.reset} Found existing CLAUDE.md`);
9465
- }
9466
- if (isTTY)
10834
+ console.log(` ${c2.yellow}○${c2.reset} Claude Code not available ${c2.dim}(skipping verification)${c2.reset}`);
10835
+ console.log(` ${c2.dim}Run /structural-integrity in Claude Code to verify later.${c2.reset}`);
9467
10836
  console.log();
9468
- op.completeStep("create_claude_symlink");
10837
+ }
10838
+ op.completeStep("verification");
9469
10839
  result.success = true;
9470
10840
  result.nextSteps = [
9471
- "cd ~/Data && claude",
9472
- "Add tasks to 0-personal/org/inbox.org",
9473
- "Run /today for your first daily briefing"
10841
+ `cd ${DATA_DIR8} && claude`,
10842
+ "Run /today for your first daily briefing",
10843
+ "Process inbox with /gtd-daily-start",
10844
+ "Run datacore doctor to check system health"
9474
10845
  ];
9475
10846
  if (isTTY) {
9476
10847
  console.log(INIT_COMPLETE);
9477
10848
  console.log();
9478
- console.log(` ${c2.bold}Setup Complete!${c2.reset}`);
10849
+ console.log(` ${c2.bold}Setup Complete${profile.name ? `, ${profile.name}` : ""}!${c2.reset}`);
9479
10850
  console.log();
9480
- if (result.spacesCreated.length > 0) {
9481
- console.log(` ${c2.green}Spaces created:${c2.reset} ${result.spacesCreated.join(", ")}`);
10851
+ const allSpaces = listSpaces();
10852
+ if (allSpaces.length > 0) {
10853
+ console.log(` ${c2.green}Your Datacore:${c2.reset}`);
10854
+ for (const s of allSpaces) {
10855
+ const icon = s.type === "personal" ? "\uD83D\uDC64" : "\uD83D\uDC65";
10856
+ console.log(` ${icon} ${s.name}`);
10857
+ }
10858
+ console.log();
9482
10859
  }
9483
- if (result.modulesInstalled.length > 0) {
9484
- console.log(` ${c2.green}Modules installed:${c2.reset} ${result.modulesInstalled.join(", ")}`);
10860
+ const installedModules = listModules();
10861
+ if (installedModules.length > 0) {
10862
+ console.log(` ${c2.green}Modules:${c2.reset} ${installedModules.map((m) => m.name).join(", ")}`);
10863
+ console.log();
10864
+ }
10865
+ const envDir = join10(DATACORE_DIR, "env");
10866
+ const envFiles = existsSync9(envDir) ? readdirSync3(envDir).filter((f) => f !== ".gitkeep") : [];
10867
+ if (envFiles.length === 0) {
10868
+ console.log(` ${c2.bold}API Keys:${c2.reset}`);
10869
+ console.log(` ${c2.dim}Some modules need API keys to function. Configure them in Claude Code:${c2.reset}`);
10870
+ console.log(` ${c2.dim}cd ~/Data && claude${c2.reset}`);
10871
+ console.log(` ${c2.dim}"Help me set up my API keys"${c2.reset}`);
10872
+ console.log(` ${c2.dim}Keys are stored in .datacore/env/ - gitignored, never leave your machine.${c2.reset}`);
10873
+ console.log();
9485
10874
  }
9486
10875
  if (result.warnings.length > 0) {
9487
10876
  console.log(` ${c2.yellow}Warnings:${c2.reset} ${result.warnings.length}`);
10877
+ for (const w of result.warnings) {
10878
+ console.log(` ${c2.dim}• ${w}${c2.reset}`);
10879
+ }
10880
+ console.log();
9488
10881
  }
9489
- console.log();
9490
10882
  console.log(` ${c2.bold}Get Started:${c2.reset}`);
9491
10883
  console.log();
9492
10884
  console.log(` ${c2.cyan}1.${c2.reset} cd ~/Data && claude`);
9493
10885
  console.log(` ${c2.dim}Start Claude Code in your Datacore directory${c2.reset}`);
9494
10886
  console.log();
9495
10887
  console.log(` ${c2.cyan}2.${c2.reset} Type ${c2.cyan}/today${c2.reset}`);
9496
- console.log(` ${c2.dim}Get your first daily briefing${c2.reset}`);
9497
- console.log();
9498
- console.log(` ${c2.cyan}3.${c2.reset} Add tasks to ${c2.cyan}0-personal/org/inbox.org${c2.reset}`);
9499
- console.log(` ${c2.dim}Your GTD capture inbox${c2.reset}`);
10888
+ console.log(` ${c2.dim}Get your first daily briefing with your imported data${c2.reset}`);
9500
10889
  console.log();
10890
+ const inboxDir = join10(DATA_DIR8, "0-personal", "0-inbox");
10891
+ const inboxFileCount = existsSync9(inboxDir) ? countFiles(inboxDir).total : 0;
10892
+ if (inboxFileCount > 0) {
10893
+ console.log(` ${c2.cyan}3.${c2.reset} Process your imports`);
10894
+ console.log(` ${c2.dim}${inboxFileCount} files in inbox - run /ingest in Claude Code${c2.reset}`);
10895
+ console.log();
10896
+ } else {
10897
+ console.log(` ${c2.cyan}3.${c2.reset} Process your inbox`);
10898
+ console.log(` ${c2.dim}Add tasks to org/inbox.org - try /gtd-daily-start${c2.reset}`);
10899
+ console.log();
10900
+ }
10901
+ if (backgroundJobs.length > 0) {
10902
+ console.log(` ${c2.green}Background imports:${c2.reset}`);
10903
+ for (const job of backgroundJobs) {
10904
+ if (verbose) {
10905
+ console.log(` ${c2.green}✓${c2.reset} ${job.label} processing (PID ${job.pid})`);
10906
+ console.log(` ${c2.dim}Log: ${job.logFile}${c2.reset}`);
10907
+ } else {
10908
+ console.log(` ${c2.green}✓${c2.reset} ${job.label} processing`);
10909
+ }
10910
+ }
10911
+ console.log(` ${c2.dim}Check progress: datacore status${c2.reset}`);
10912
+ console.log();
10913
+ }
10914
+ console.log(` ${c2.dim}Edit ~/Data/CLAUDE.local.md to teach Claude about you.${c2.reset}`);
9501
10915
  console.log(` ${c2.dim}Run 'datacore doctor' anytime to check system health.${c2.reset}`);
9502
10916
  console.log();
9503
10917
  }
@@ -9509,233 +10923,8 @@ See .datacore/specs/ for detailed documentation.
9509
10923
  return result;
9510
10924
  }
9511
10925
 
9512
- // src/lib/snapshot.ts
9513
- var import_yaml2 = __toESM(require_dist(), 1);
9514
- import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
9515
- import { execSync as execSync5 } from "child_process";
9516
- import { join as join8 } from "path";
9517
- var DATA_DIR6 = join8(process.env.HOME || "~", "Data");
9518
- var LOCK_FILE = join8(DATA_DIR6, "datacore.lock.yaml");
9519
- var CLI_VERSION = "1.0.5";
9520
- function getGitInfo(path) {
9521
- if (!existsSync8(join8(path, ".git"))) {
9522
- return {};
9523
- }
9524
- try {
9525
- const remote = execSync5("git remote get-url origin 2>/dev/null || true", {
9526
- cwd: path,
9527
- encoding: "utf-8"
9528
- }).trim() || undefined;
9529
- const commit = execSync5("git rev-parse HEAD 2>/dev/null || true", {
9530
- cwd: path,
9531
- encoding: "utf-8"
9532
- }).trim() || undefined;
9533
- const branch = execSync5("git branch --show-current 2>/dev/null || true", {
9534
- cwd: path,
9535
- encoding: "utf-8"
9536
- }).trim() || undefined;
9537
- return { remote, commit, branch };
9538
- } catch {
9539
- return {};
9540
- }
9541
- }
9542
- function createSnapshot(options = {}) {
9543
- const { includeSettings = false } = options;
9544
- const modules = [];
9545
- for (const mod of listModules()) {
9546
- const gitInfo = getGitInfo(mod.path);
9547
- modules.push({
9548
- name: mod.name,
9549
- source: gitInfo.remote || "local",
9550
- version: mod.version,
9551
- commit: gitInfo.commit,
9552
- branch: gitInfo.branch
9553
- });
9554
- }
9555
- const spaces = [];
9556
- for (const space of listSpaces()) {
9557
- const gitInfo = getGitInfo(space.path);
9558
- spaces.push({
9559
- name: space.name,
9560
- number: space.number,
9561
- type: space.type,
9562
- source: gitInfo.remote,
9563
- commit: gitInfo.commit
9564
- });
9565
- }
9566
- const deps = checkDependencies();
9567
- const dependencies = deps.filter((d) => d.installed && d.version).map((d) => ({
9568
- name: d.name,
9569
- version: d.version,
9570
- required: d.required
9571
- }));
9572
- const snapshot = {
9573
- version: "1.0",
9574
- created: new Date().toISOString(),
9575
- cliVersion: CLI_VERSION,
9576
- platform: `${process.platform}-${process.arch}`,
9577
- modules,
9578
- spaces,
9579
- dependencies
9580
- };
9581
- if (includeSettings) {
9582
- const settingsPath = join8(DATA_DIR6, ".datacore", "settings.yaml");
9583
- if (existsSync8(settingsPath)) {
9584
- try {
9585
- const content = readFileSync4(settingsPath, "utf-8");
9586
- snapshot.settings = import_yaml2.parse(content);
9587
- } catch {}
9588
- }
9589
- }
9590
- return snapshot;
9591
- }
9592
- function saveSnapshot(snapshot, path) {
9593
- const lockPath = path || LOCK_FILE;
9594
- const content = import_yaml2.stringify(snapshot, {
9595
- lineWidth: 0
9596
- });
9597
- writeFileSync5(lockPath, content);
9598
- return lockPath;
9599
- }
9600
- function loadSnapshot(path) {
9601
- const lockPath = path || LOCK_FILE;
9602
- if (!existsSync8(lockPath)) {
9603
- return null;
9604
- }
9605
- try {
9606
- const content = readFileSync4(lockPath, "utf-8");
9607
- return import_yaml2.parse(content);
9608
- } catch {
9609
- return null;
9610
- }
9611
- }
9612
- function diffSnapshot(snapshot) {
9613
- const current = createSnapshot();
9614
- const diff = {
9615
- modules: { added: [], removed: [], changed: [] },
9616
- spaces: { added: [], removed: [] },
9617
- dependencies: { changed: [] }
9618
- };
9619
- const currentModules = new Map(current.modules.map((m) => [m.name, m]));
9620
- const snapshotModules = new Map(snapshot.modules.map((m) => [m.name, m]));
9621
- for (const [name, mod] of currentModules) {
9622
- if (!snapshotModules.has(name)) {
9623
- diff.modules.added.push(name);
9624
- } else {
9625
- const expected = snapshotModules.get(name);
9626
- if (expected.commit && mod.commit && expected.commit !== mod.commit) {
9627
- diff.modules.changed.push({
9628
- name,
9629
- from: expected.commit.slice(0, 7),
9630
- to: mod.commit.slice(0, 7)
9631
- });
9632
- }
9633
- }
9634
- }
9635
- for (const name of snapshotModules.keys()) {
9636
- if (!currentModules.has(name)) {
9637
- diff.modules.removed.push(name);
9638
- }
9639
- }
9640
- const currentSpaces = new Set(current.spaces.map((s) => s.name));
9641
- const snapshotSpaces = new Set(snapshot.spaces.map((s) => s.name));
9642
- for (const name of currentSpaces) {
9643
- if (!snapshotSpaces.has(name)) {
9644
- diff.spaces.added.push(name);
9645
- }
9646
- }
9647
- for (const name of snapshotSpaces) {
9648
- if (!currentSpaces.has(name)) {
9649
- diff.spaces.removed.push(name);
9650
- }
9651
- }
9652
- const currentDeps = new Map(current.dependencies.map((d) => [d.name, d.version]));
9653
- for (const dep of snapshot.dependencies) {
9654
- const actualVersion = currentDeps.get(dep.name);
9655
- if (actualVersion && actualVersion !== dep.version) {
9656
- diff.dependencies.changed.push({
9657
- name: dep.name,
9658
- expected: dep.version,
9659
- actual: actualVersion
9660
- });
9661
- }
9662
- }
9663
- return diff;
9664
- }
9665
- function restoreFromSnapshot(snapshot, options = {}) {
9666
- const { modules = true, spaces = true, dryRun = false } = options;
9667
- const result = {
9668
- modulesInstalled: [],
9669
- modulesFailed: [],
9670
- spacesCreated: [],
9671
- warnings: []
9672
- };
9673
- if (modules) {
9674
- const currentModules = new Set(listModules().map((m) => m.name));
9675
- for (const mod of snapshot.modules) {
9676
- if (currentModules.has(mod.name)) {
9677
- continue;
9678
- }
9679
- if (mod.source === "local") {
9680
- result.warnings.push(`Module ${mod.name} was local, cannot restore`);
9681
- continue;
9682
- }
9683
- if (dryRun) {
9684
- result.modulesInstalled.push(mod.name);
9685
- continue;
9686
- }
9687
- try {
9688
- const modulesDir = join8(DATA_DIR6, ".datacore", "modules");
9689
- if (!existsSync8(modulesDir)) {
9690
- mkdirSync5(modulesDir, { recursive: true });
9691
- }
9692
- const modulePath = join8(modulesDir, mod.name);
9693
- execSync5(`git clone ${mod.source} "${modulePath}"`, { stdio: "pipe" });
9694
- if (mod.commit) {
9695
- execSync5(`git checkout ${mod.commit}`, { cwd: modulePath, stdio: "pipe" });
9696
- } else if (mod.branch) {
9697
- execSync5(`git checkout ${mod.branch}`, { cwd: modulePath, stdio: "pipe" });
9698
- }
9699
- result.modulesInstalled.push(mod.name);
9700
- } catch (err) {
9701
- result.modulesFailed.push({
9702
- name: mod.name,
9703
- error: err.message
9704
- });
9705
- }
9706
- }
9707
- }
9708
- if (spaces) {
9709
- const currentSpaces = new Set(listSpaces().map((s) => s.name));
9710
- for (const space of snapshot.spaces) {
9711
- if (currentSpaces.has(space.name)) {
9712
- continue;
9713
- }
9714
- if (dryRun) {
9715
- result.spacesCreated.push(space.name);
9716
- continue;
9717
- }
9718
- if (space.source) {
9719
- try {
9720
- const spacePath = join8(DATA_DIR6, space.name);
9721
- execSync5(`git clone ${space.source} "${spacePath}"`, { stdio: "pipe" });
9722
- if (space.commit) {
9723
- execSync5(`git checkout ${space.commit}`, { cwd: spacePath, stdio: "pipe" });
9724
- }
9725
- result.spacesCreated.push(space.name);
9726
- } catch (err) {
9727
- result.warnings.push(`Could not clone space ${space.name}: ${err.message}`);
9728
- }
9729
- } else {
9730
- result.warnings.push(`Space ${space.name} has no git source, skipping`);
9731
- }
9732
- }
9733
- }
9734
- return result;
9735
- }
9736
-
9737
10926
  // src/index.ts
9738
- var VERSION = "1.0.5";
10927
+ var VERSION = "1.0.7";
9739
10928
  var args = process.argv.slice(2);
9740
10929
  var parsed = parseArgs(args);
9741
10930
  async function handleMeta(command, cmdArgs, flags, format) {
@@ -9746,17 +10935,20 @@ async function handleMeta(command, cmdArgs, flags, format) {
9746
10935
  case "init": {
9747
10936
  if (isInitialized() && !flags.force) {
9748
10937
  if (format === "json") {
9749
- output({ success: true, message: "Datacore already initialized" }, format);
10938
+ output({ success: true, message: "Datacore already initialized", hint: "Use --force to re-initialize or run datacore doctor" }, format);
9750
10939
  } else {
9751
- info("Datacore already initialized");
9752
- info("Use --force to re-initialize");
10940
+ info("Datacore already initialized at ~/Data");
10941
+ info("Run datacore doctor to check health");
10942
+ info("Run datacore module list to see modules");
10943
+ info("Use --force to re-run the setup wizard");
9753
10944
  }
9754
10945
  break;
9755
10946
  }
9756
10947
  const result = await initDatacore({
9757
10948
  nonInteractive: flags.yes === true,
9758
10949
  skipChecks: flags["skip-checks"] === true,
9759
- stream: format === "human"
10950
+ stream: format === "human",
10951
+ verbose: flags.verbose === true
9760
10952
  });
9761
10953
  if (format === "json") {
9762
10954
  output(result, format);
@@ -9809,7 +11001,7 @@ async function handleMeta(command, cmdArgs, flags, format) {
9809
11001
  for (const dep of result.dependencies) {
9810
11002
  const status = dep.installed ? "✓" : "✗";
9811
11003
  const version = dep.version ? ` (${dep.version})` : "";
9812
- const required = dep.required ? "" : " (optional)";
11004
+ const required = dep.required ? "" : " (recommended)";
9813
11005
  console.log(` ${status} ${dep.name}${version}${required}`);
9814
11006
  if (!dep.installed && dep.installCommand) {
9815
11007
  console.log(` Install: ${dep.installCommand}`);
@@ -10166,8 +11358,8 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10166
11358
  throw new CLIError("ERR_INVALID_ARGUMENT", "Missing task description", 'Usage: datacore nightshift queue "Research topic X"');
10167
11359
  }
10168
11360
  const { existsSync: existsSync10, appendFileSync } = await import("fs");
10169
- const { join: join10 } = await import("path");
10170
- const inboxPath = join10(process.env.HOME || "~", "Data", "0-personal", "org", "inbox.org");
11361
+ const { join: join11 } = await import("path");
11362
+ const inboxPath = join11(process.env.HOME || "~", "Data", "0-personal", "org", "inbox.org");
10171
11363
  if (!existsSync10(inboxPath)) {
10172
11364
  throw new CLIError("ERR_NOT_FOUND", "inbox.org not found. Run datacore init first.");
10173
11365
  }
@@ -10191,13 +11383,13 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10191
11383
  break;
10192
11384
  }
10193
11385
  case "cron": {
10194
- const { execSync: execSync6, exec: execAsync } = await import("child_process");
11386
+ const { execSync: execSync5, exec: execAsync } = await import("child_process");
10195
11387
  switch (action) {
10196
11388
  case "install": {
10197
11389
  info("Installing cron jobs...");
10198
11390
  let currentCron = "";
10199
11391
  try {
10200
- currentCron = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11392
+ currentCron = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10201
11393
  } catch {}
10202
11394
  if (currentCron.includes("# Datacore")) {
10203
11395
  if (!flags.force) {
@@ -10220,7 +11412,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10220
11412
  `;
10221
11413
  const newCron = currentCron.trim() + `
10222
11414
  ` + datacoreCron;
10223
- execSync6(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
11415
+ execSync5(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
10224
11416
  success("Cron jobs installed");
10225
11417
  info(" 8:00 AM weekdays - Daily briefing");
10226
11418
  info(" 6:00 PM weekdays - Evening wrap-up");
@@ -10230,7 +11422,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10230
11422
  case "status": {
10231
11423
  let crontab = "";
10232
11424
  try {
10233
- crontab = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11425
+ crontab = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10234
11426
  } catch {}
10235
11427
  const datacoreJobs = crontab.split(`
10236
11428
  `).filter((line) => line.includes("datacore") && !line.startsWith("#"));
@@ -10253,7 +11445,7 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10253
11445
  info("Removing cron jobs...");
10254
11446
  let currentCron = "";
10255
11447
  try {
10256
- currentCron = execSync6("crontab -l 2>/dev/null", { encoding: "utf-8" });
11448
+ currentCron = execSync5("crontab -l 2>/dev/null", { encoding: "utf-8" });
10257
11449
  } catch {
10258
11450
  info("No cron jobs to remove");
10259
11451
  break;
@@ -10262,9 +11454,9 @@ async function handleResource(resource, action, cmdArgs, flags, format) {
10262
11454
  `).filter((line) => !line.includes("# Datacore") && !line.includes("datacore")).join(`
10263
11455
  `).trim();
10264
11456
  if (newCron) {
10265
- execSync6(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
11457
+ execSync5(`echo "${newCron}" | crontab -`, { stdio: "pipe" });
10266
11458
  } else {
10267
- execSync6("crontab -r", { stdio: "pipe" });
11459
+ execSync5("crontab -r", { stdio: "pipe" });
10268
11460
  }
10269
11461
  success("Cron jobs removed");
10270
11462
  break;