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