@our2ndbrain/cli 1.1.3 → 2026.4.5
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/CHANGELOG.md +30 -2
- package/README.md +54 -582
- package/README_en.md +108 -0
- package/bin/2ndbrain.js +30 -1
- package/package.json +14 -11
- package/src/commands/check.js +199 -0
- package/src/commands/completion.js +35 -1
- package/src/commands/init.js +7 -4
- package/src/commands/member.js +3 -1
- package/src/commands/update.js +38 -12
- package/src/commands/watch.js +212 -0
- package/src/index.js +4 -0
- package/src/lib/config.js +46 -2
- package/src/lib/files.js +58 -19
- package/{.obsidian → template/.obsidian}/plugins/obsidian-git/obsidian_askpass.sh +0 -0
- package/{00_Dashboard → template/00_Dashboard}/01_All_Tasks.md +17 -15
- package/template/10_Inbox/.gitkeep +0 -0
- package/template/20_Areas/.gitkeep +0 -0
- package/template/30_Projects/.gitkeep +0 -0
- package/template/40_Resources/.gitkeep +0 -0
- package/template/90_Archives/.gitkeep +0 -0
- package/{99_System → template/99_System}/Scripts/init_member.sh +0 -0
- package/template/99_System/Templates/tpl_daily_note.md +30 -0
- package/{99_System → template/99_System}/Templates/tpl_member_tasks.md +11 -5
- package/template/99_System/Templates/tpl_member_todo.md +5 -0
- package/99_System/Templates/tpl_daily_note.md +0 -13
- package/AGENTS.md +0 -193
- package/CLAUDE.md +0 -153
- /package/{.obsidian → template/.obsidian}/.2ndbrain-manifest.json +0 -0
- /package/{.obsidian → template/.obsidian}/app.json +0 -0
- /package/{.obsidian → template/.obsidian}/appearance.json +0 -0
- /package/{.obsidian → template/.obsidian}/community-plugins.json +0 -0
- /package/{.obsidian → template/.obsidian}/core-plugins.json +0 -0
- /package/{.obsidian → template/.obsidian}/graph.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/calendar/data.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/calendar/main.js +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/calendar/manifest.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/data.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/main.js +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/manifest.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/styles.css +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/data.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/main.js +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/manifest.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/styles.css +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/main.js +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/manifest.json +0 -0
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/styles.css +0 -0
- /package/{.obsidian → template/.obsidian}/types.json +0 -0
- /package/{00_Dashboard → template/00_Dashboard}/09_All_Done.md +0 -0
- /package/{10_Inbox → template/10_Inbox}/Agents/Journal.md +0 -0
- /package/{99_System → template/99_System}/Templates/tpl_member_done.md +0 -0
package/README_en.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# 🧠 2ndBrain
|
|
2
|
+
|
|
3
|
+
> A lightweight entrypoint for AI agents and human collaborators: use the 2ndBrain template, CLI, and Skill to operate an Obsidian knowledge base together.
|
|
4
|
+
|
|
5
|
+
[English](README_en.md) | [简体中文](README.md)
|
|
6
|
+
|
|
7
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
8
|
+
[](https://www.npmjs.com/package/@our2ndbrain/cli)
|
|
9
|
+
|
|
10
|
+
## What This Repository Is
|
|
11
|
+
|
|
12
|
+
If you hand this repository to an AI agent, this README should be the first file it reads.
|
|
13
|
+
|
|
14
|
+
2ndBrain ships three things:
|
|
15
|
+
|
|
16
|
+
- an Obsidian vault template with PARA folders, inboxes, and dashboards
|
|
17
|
+
- a CLI for checking the environment, initializing a vault, and updating framework files
|
|
18
|
+
- a 2ndBrain Skill that teaches the agent how to capture, organize, review, and process content
|
|
19
|
+
|
|
20
|
+
This README keeps only the minimum needed for AI collaboration. For the fuller project and methodology reference, see the [detailed guide](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/docs/guide_en.md).
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
The default path is `npx`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @our2ndbrain/cli@latest check
|
|
28
|
+
npx @our2ndbrain/cli@latest init my-brain
|
|
29
|
+
cd my-brain
|
|
30
|
+
npx @our2ndbrain/cli@latest member Alice
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If the CLI is already installed globally, you can also use:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
2ndbrain check
|
|
37
|
+
2ndbrain init my-brain
|
|
38
|
+
cd my-brain
|
|
39
|
+
2ndbrain member Alice
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Initialize a Vault
|
|
43
|
+
|
|
44
|
+
### Create a new 2ndBrain vault
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @our2ndbrain/cli@latest check
|
|
48
|
+
npx @our2ndbrain/cli@latest init my-brain
|
|
49
|
+
cd my-brain
|
|
50
|
+
npx @our2ndbrain/cli@latest member Alice
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Integrate into an existing Obsidian vault
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd my-existing-vault
|
|
57
|
+
npx @our2ndbrain/cli@latest check
|
|
58
|
+
npx @our2ndbrain/cli@latest init
|
|
59
|
+
npx @our2ndbrain/cli@latest member Alice
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
After initialization, guide the user to:
|
|
63
|
+
|
|
64
|
+
1. open the directory in Obsidian
|
|
65
|
+
2. click "Trust author and enable plugins"
|
|
66
|
+
3. create the first daily note and start capturing tasks and thoughts
|
|
67
|
+
|
|
68
|
+
## Install the Skill
|
|
69
|
+
|
|
70
|
+
The preferred installation path is the SSH git URL form:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx skills add git@github.com:Our2ndBrain/2ndBrain-Template.git --skill 2ndbrain
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To target a specific agent explicitly:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx skills add git@github.com:Our2ndBrain/2ndBrain-Template.git --skill 2ndbrain -a claude-code
|
|
80
|
+
npx skills add git@github.com:Our2ndBrain/2ndBrain-Template.git --skill 2ndbrain -a cursor
|
|
81
|
+
npx skills add git@github.com:Our2ndBrain/2ndBrain-Template.git --skill 2ndbrain -a openclaw
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you are not using the `skills` CLI in that environment yet, you can still fall back to manually copying `skills/2ndbrain/`.
|
|
85
|
+
|
|
86
|
+
The skill entrypoint is [SKILL.md](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/SKILL.md).
|
|
87
|
+
|
|
88
|
+
## How AI Should Collaborate With Humans
|
|
89
|
+
|
|
90
|
+
An AI agent using this template should at least follow these rules:
|
|
91
|
+
|
|
92
|
+
- Run `2ndbrain check` or `npx @our2ndbrain/cli@latest check` before setup, initialization, or takeover.
|
|
93
|
+
- Capture before organizing. If placement is unclear, write it to `10_Inbox/{member}/` first.
|
|
94
|
+
- Write tasks to `10_Inbox/{member}/00_To-Do.md`. Do not hand-edit query-driven files such as `00_Dashboard/*.md` or `10_Inbox/*/01_Tasks.md`.
|
|
95
|
+
- Follow the user's language instead of switching languages on your own.
|
|
96
|
+
- Prefer the 2ndBrain Skill for capture, organize, review, content processing, and scheduled cleanup.
|
|
97
|
+
- When you need detailed rules, read the Skill and its references instead of inferring policy from this README.
|
|
98
|
+
|
|
99
|
+
## Deep Reading
|
|
100
|
+
|
|
101
|
+
- [Detailed Guide (English)](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/docs/guide_en.md)
|
|
102
|
+
- [详细指南(中文)](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/docs/guide.md)
|
|
103
|
+
- [2ndBrain Skill](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/SKILL.md)
|
|
104
|
+
- [Setup Reference](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/references/setup.md)
|
|
105
|
+
- [Operations and Daily Review](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/references/operations.md)
|
|
106
|
+
- [Content Processing](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/references/content-processing.md)
|
|
107
|
+
- [Scheduling and Automation](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/references/scheduling.md)
|
|
108
|
+
- [Task Conventions](https://github.com/Our2ndBrain/2ndBrain-Template/blob/main/skills/2ndbrain/references/task-conventions.md)
|
package/bin/2ndbrain.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { program } = require('commander');
|
|
10
|
-
const { init, update, remove, member, completion } = require('../src');
|
|
10
|
+
const { init, update, remove, member, completion, check, watch } = require('../src');
|
|
11
11
|
const pkg = require('../package.json');
|
|
12
12
|
|
|
13
13
|
// ANSI color codes for terminal output
|
|
@@ -93,6 +93,35 @@ program
|
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
// Check command
|
|
97
|
+
program
|
|
98
|
+
.command('check [path]')
|
|
99
|
+
.description('Check environment prerequisites (Node.js, Git, Obsidian, Agent CLI)')
|
|
100
|
+
.action(async (targetPath = '.', options) => {
|
|
101
|
+
try {
|
|
102
|
+
const passed = await check(targetPath, options, log);
|
|
103
|
+
if (!passed) process.exit(1);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
log.error(`Error: ${err.message}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Watch command
|
|
111
|
+
program
|
|
112
|
+
.command('watch [path]')
|
|
113
|
+
.description('Watch To-Do files for changes and auto-trigger inbox organization')
|
|
114
|
+
.option('-i, --interval <minutes>', 'Debounce interval in minutes (default: 5)', parseInt)
|
|
115
|
+
.option('--once', 'Exit after first triggered organization')
|
|
116
|
+
.action(async (targetPath = '.', options) => {
|
|
117
|
+
try {
|
|
118
|
+
await watch(targetPath, options, log);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
log.error(`Error: ${err.message}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
96
125
|
// Completion command
|
|
97
126
|
program
|
|
98
127
|
.command('completion <shell>')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@our2ndbrain/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2026.4.5",
|
|
4
4
|
"description": "CLI tool for 2ndBrain - A personal knowledge management system based on PARA + C-O-R-D + Append-and-Review",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"2ndbrain",
|
|
@@ -24,31 +24,34 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"bin/",
|
|
26
26
|
"src/",
|
|
27
|
-
"
|
|
28
|
-
"00_Dashboard/",
|
|
29
|
-
"10_Inbox/Agents/",
|
|
30
|
-
"99_System/",
|
|
27
|
+
"template/",
|
|
31
28
|
"AGENTS.md",
|
|
32
29
|
"CHANGELOG.md",
|
|
33
30
|
"CLAUDE.md",
|
|
34
31
|
"LICENSE",
|
|
35
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"README_en.md"
|
|
36
34
|
],
|
|
37
35
|
"bugs": {
|
|
38
36
|
"url": "https://github.com/Our2ndBrain/2ndBrain-Template/issues"
|
|
39
37
|
},
|
|
40
38
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
39
|
+
"node": ">=18.0.0"
|
|
42
40
|
},
|
|
43
41
|
"scripts": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
42
|
+
"lint": "node scripts/lint.js && node scripts/check-workflow-pinning.js",
|
|
43
|
+
"pack:check": "node scripts/check-package.js",
|
|
44
|
+
"release": "node scripts/release.js",
|
|
45
|
+
"release:check": "node scripts/check-release.js",
|
|
46
|
+
"smoke:install": "node scripts/smoke-install.js",
|
|
47
|
+
"test": "node --test",
|
|
48
|
+
"version": "node scripts/version.js && git add CHANGELOG.md"
|
|
46
49
|
},
|
|
47
50
|
"dependencies": {
|
|
48
51
|
"chalk": "^4.1.2",
|
|
49
52
|
"commander": "^12.0.0",
|
|
50
|
-
"diff": "^
|
|
51
|
-
"fs-extra": "^11.
|
|
53
|
+
"diff": "^8.0.4",
|
|
54
|
+
"fs-extra": "^11.3.4"
|
|
52
55
|
},
|
|
53
56
|
"publishConfig": {
|
|
54
57
|
"access": "public"
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 2ndBrain CLI - Check Command
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform environment check. All platform detection is handled
|
|
5
|
+
* in Node.js — agents never need to write shell/PowerShell scripts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execFileSync } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { is2ndBrainProject } = require('../lib/config');
|
|
12
|
+
|
|
13
|
+
const MIN_NODE_MAJOR = 18;
|
|
14
|
+
|
|
15
|
+
function commandExists(cmd) {
|
|
16
|
+
try {
|
|
17
|
+
const isWin = process.platform === 'win32';
|
|
18
|
+
if (isWin) {
|
|
19
|
+
execFileSync('where', [cmd], { stdio: 'pipe' });
|
|
20
|
+
} else {
|
|
21
|
+
execFileSync('which', [cmd], { stdio: 'pipe' });
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getCommandVersion(cmd, args = ['--version']) {
|
|
30
|
+
try {
|
|
31
|
+
return execFileSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkObsidianInstalled() {
|
|
38
|
+
switch (process.platform) {
|
|
39
|
+
case 'darwin':
|
|
40
|
+
return fs.existsSync('/Applications/Obsidian.app');
|
|
41
|
+
case 'win32': {
|
|
42
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
43
|
+
return fs.existsSync(path.join(localAppData, 'Programs', 'Obsidian', 'Obsidian.exe'));
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return commandExists('obsidian');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getObsidianInstallHint() {
|
|
51
|
+
switch (process.platform) {
|
|
52
|
+
case 'darwin':
|
|
53
|
+
return 'brew install --cask obsidian (或从 https://obsidian.md/ 下载)';
|
|
54
|
+
case 'win32':
|
|
55
|
+
return 'winget install Obsidian.MD.Obsidian (或从 https://obsidian.md/ 下载)';
|
|
56
|
+
default:
|
|
57
|
+
return '从 https://obsidian.md/ 下载 AppImage';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getNodeInstallHint() {
|
|
62
|
+
switch (process.platform) {
|
|
63
|
+
case 'darwin':
|
|
64
|
+
return 'brew install node';
|
|
65
|
+
case 'win32':
|
|
66
|
+
return 'winget install OpenJS.NodeJS.LTS (或从 https://nodejs.org/ 下载)';
|
|
67
|
+
default:
|
|
68
|
+
return 'sudo apt install nodejs npm (Debian/Ubuntu) 或从 https://nodejs.org/ 下载';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getObsidianCLIPathHint() {
|
|
73
|
+
switch (process.platform) {
|
|
74
|
+
case 'darwin':
|
|
75
|
+
return '在 ~/.zshrc 中添加:\nexport PATH="$PATH:/Applications/Obsidian.app/Contents/MacOS"';
|
|
76
|
+
case 'win32':
|
|
77
|
+
return '系统设置 → 环境变量 → Path → 新增:\nC:\\Users\\<用户名>\\AppData\\Local\\Programs\\Obsidian\\';
|
|
78
|
+
default:
|
|
79
|
+
return 'sudo ln -s /opt/obsidian/obsidian /usr/local/bin/obsidian';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function check(targetPath, _options, log) {
|
|
84
|
+
const results = [];
|
|
85
|
+
let allPassed = true;
|
|
86
|
+
|
|
87
|
+
log.info('\n🧠 2ndBrain 环境检测\n');
|
|
88
|
+
|
|
89
|
+
// 1. Node.js version
|
|
90
|
+
const nodeVersion = process.version;
|
|
91
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
92
|
+
if (nodeMajor >= MIN_NODE_MAJOR) {
|
|
93
|
+
results.push({ ok: true, label: `Node.js ${nodeVersion}` });
|
|
94
|
+
} else {
|
|
95
|
+
results.push({
|
|
96
|
+
ok: false,
|
|
97
|
+
label: `Node.js ${nodeVersion} (需要 >= ${MIN_NODE_MAJOR})`,
|
|
98
|
+
hint: getNodeInstallHint(),
|
|
99
|
+
});
|
|
100
|
+
allPassed = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Git
|
|
104
|
+
const gitVersion = getCommandVersion('git');
|
|
105
|
+
if (gitVersion) {
|
|
106
|
+
results.push({ ok: true, label: `Git ${gitVersion.replace('git version ', '')}` });
|
|
107
|
+
} else {
|
|
108
|
+
results.push({
|
|
109
|
+
ok: false,
|
|
110
|
+
label: 'Git 未安装',
|
|
111
|
+
hint: process.platform === 'win32'
|
|
112
|
+
? 'winget install Git.Git'
|
|
113
|
+
: process.platform === 'darwin'
|
|
114
|
+
? 'brew install git'
|
|
115
|
+
: 'sudo apt install git',
|
|
116
|
+
});
|
|
117
|
+
allPassed = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Obsidian app
|
|
121
|
+
if (checkObsidianInstalled()) {
|
|
122
|
+
results.push({ ok: true, label: 'Obsidian 已安装' });
|
|
123
|
+
} else {
|
|
124
|
+
results.push({
|
|
125
|
+
ok: false,
|
|
126
|
+
label: 'Obsidian 未安装',
|
|
127
|
+
hint: getObsidianInstallHint(),
|
|
128
|
+
});
|
|
129
|
+
allPassed = false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 4. Obsidian CLI
|
|
133
|
+
if (commandExists('obsidian')) {
|
|
134
|
+
const obsVersion = getCommandVersion('obsidian');
|
|
135
|
+
results.push({ ok: true, label: `Obsidian CLI ${obsVersion || '可用'}` });
|
|
136
|
+
} else {
|
|
137
|
+
results.push({
|
|
138
|
+
ok: false,
|
|
139
|
+
label: 'Obsidian CLI 未配置',
|
|
140
|
+
hint: `在 Obsidian Settings > General > CLI 中启用,然后配置 PATH:\n${getObsidianCLIPathHint()}`,
|
|
141
|
+
});
|
|
142
|
+
// Obsidian CLI is optional, don't fail the whole check
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 5. 2ndBrain project (if path given)
|
|
146
|
+
const resolvedPath = path.resolve(targetPath);
|
|
147
|
+
if (fs.existsSync(resolvedPath)) {
|
|
148
|
+
if (is2ndBrainProject(resolvedPath)) {
|
|
149
|
+
results.push({ ok: true, label: `2ndBrain 知识库: ${resolvedPath}` });
|
|
150
|
+
} else {
|
|
151
|
+
results.push({
|
|
152
|
+
ok: false,
|
|
153
|
+
label: `${resolvedPath} 不是 2ndBrain 知识库`,
|
|
154
|
+
hint: `运行: 2ndbrain init ${targetPath}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 6. Agent CLI (openclaw or claude)
|
|
160
|
+
if (commandExists('openclaw')) {
|
|
161
|
+
results.push({ ok: true, label: 'OpenClaw CLI 可用' });
|
|
162
|
+
} else if (commandExists('claude')) {
|
|
163
|
+
results.push({ ok: true, label: 'Claude CLI 可用' });
|
|
164
|
+
} else {
|
|
165
|
+
results.push({
|
|
166
|
+
ok: false,
|
|
167
|
+
label: 'Agent CLI 未找到 (openclaw / claude)',
|
|
168
|
+
hint: '安装 OpenClaw: https://docs.openclaw.ai/start\n或 Claude Code: https://code.claude.com',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Print results
|
|
173
|
+
for (const r of results) {
|
|
174
|
+
if (r.ok) {
|
|
175
|
+
log.success(` ✓ ${r.label}`);
|
|
176
|
+
} else {
|
|
177
|
+
log.error(` ✗ ${r.label}`);
|
|
178
|
+
if (r.hint) {
|
|
179
|
+
log.warn(` → ${r.hint.split('\n').join('\n → ')}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
log.info('');
|
|
185
|
+
|
|
186
|
+
if (allPassed) {
|
|
187
|
+
log.success('所有必要组件已就绪 ✓\n');
|
|
188
|
+
} else {
|
|
189
|
+
log.warn('部分组件缺失,请按上方提示安装\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return allPassed;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = check;
|
|
196
|
+
module.exports.MIN_NODE_MAJOR = MIN_NODE_MAJOR;
|
|
197
|
+
module.exports.commandExists = commandExists;
|
|
198
|
+
module.exports.getCommandVersion = getCommandVersion;
|
|
199
|
+
module.exports.checkObsidianInstalled = checkObsidianInstalled;
|
|
@@ -25,7 +25,7 @@ _2ndbrain_completions() {
|
|
|
25
25
|
local cmd="\${COMP_WORDS[1]}"
|
|
26
26
|
|
|
27
27
|
# Top-level commands
|
|
28
|
-
local commands="init update remove member completion"
|
|
28
|
+
local commands="init update remove member check watch completion"
|
|
29
29
|
|
|
30
30
|
case "\$cmd" in
|
|
31
31
|
init)
|
|
@@ -55,6 +55,14 @@ _2ndbrain_completions() {
|
|
|
55
55
|
member)
|
|
56
56
|
COMPREPLY=( $(compgen -W "-f --force --no-config -h --help" -- "\$cur") )
|
|
57
57
|
;;
|
|
58
|
+
check)
|
|
59
|
+
COMPREPLY=( $(compgen -W "-h --help" -- "\$cur") )
|
|
60
|
+
[[ -z "\$cur" || "\$cur" != -* ]] && COMPREPLY+=( $(compgen -d -- "\$cur") )
|
|
61
|
+
;;
|
|
62
|
+
watch)
|
|
63
|
+
COMPREPLY=( $(compgen -W "-i --interval --once -h --help" -- "\$cur") )
|
|
64
|
+
[[ -z "\$cur" || "\$cur" != -* ]] && COMPREPLY+=( $(compgen -d -- "\$cur") )
|
|
65
|
+
;;
|
|
58
66
|
completion)
|
|
59
67
|
COMPREPLY=( $(compgen -W "bash zsh fish" -- "\$cur") )
|
|
60
68
|
;;
|
|
@@ -78,6 +86,8 @@ _2ndbrain() {
|
|
|
78
86
|
'update:Update framework files from template'
|
|
79
87
|
'remove:Remove framework files (preserves user data)'
|
|
80
88
|
'member:Initialize a new member directory'
|
|
89
|
+
'check:Check environment prerequisites'
|
|
90
|
+
'watch:Watch To-Do files and trigger inbox organization'
|
|
81
91
|
'completion:Generate shell completion script'
|
|
82
92
|
)
|
|
83
93
|
|
|
@@ -122,6 +132,18 @@ _2ndbrain() {
|
|
|
122
132
|
'1:name:' \\
|
|
123
133
|
'2:path:_directories'
|
|
124
134
|
;;
|
|
135
|
+
check)
|
|
136
|
+
_arguments \\
|
|
137
|
+
'(-h --help)'{-h,--help}'[Show help]' \\
|
|
138
|
+
'1:path:_directories'
|
|
139
|
+
;;
|
|
140
|
+
watch)
|
|
141
|
+
_arguments \\
|
|
142
|
+
'(-i --interval)'{-i,--interval}'[Debounce interval in minutes]:minutes:' \\
|
|
143
|
+
'--once[Exit after first triggered organization]' \\
|
|
144
|
+
'(-h --help)'{-h,--help}'[Show help]' \\
|
|
145
|
+
'1:path:_directories'
|
|
146
|
+
;;
|
|
125
147
|
completion)
|
|
126
148
|
_arguments \\
|
|
127
149
|
'1:shell:(bash zsh fish)'
|
|
@@ -144,6 +166,8 @@ complete -c 2ndbrain -n '__fish_use_subcommand' -a 'init' -d 'Initialize a new 2
|
|
|
144
166
|
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'update' -d 'Update framework files from template'
|
|
145
167
|
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'remove' -d 'Remove framework files'
|
|
146
168
|
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'member' -d 'Initialize a new member directory'
|
|
169
|
+
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'check' -d 'Check environment prerequisites'
|
|
170
|
+
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'watch' -d 'Watch To-Do files and trigger inbox organization'
|
|
147
171
|
complete -c 2ndbrain -n '__fish_use_subcommand' -a 'completion' -d 'Generate shell completion script'
|
|
148
172
|
|
|
149
173
|
# Global options
|
|
@@ -173,6 +197,16 @@ complete -c 2ndbrain -n '__fish_seen_subcommand_from member' -s f -l force -d 'F
|
|
|
173
197
|
complete -c 2ndbrain -n '__fish_seen_subcommand_from member' -l no-config -d 'Skip Obsidian config update'
|
|
174
198
|
complete -c 2ndbrain -n '__fish_seen_subcommand_from member' -s h -l help -d 'Show help'
|
|
175
199
|
|
|
200
|
+
# check options
|
|
201
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from check' -s h -l help -d 'Show help'
|
|
202
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from check' -a '(__fish_complete_directories)'
|
|
203
|
+
|
|
204
|
+
# watch options
|
|
205
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from watch' -s i -l interval -d 'Debounce interval in minutes' -r
|
|
206
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from watch' -l once -d 'Exit after first triggered organization'
|
|
207
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from watch' -s h -l help -d 'Show help'
|
|
208
|
+
complete -c 2ndbrain -n '__fish_seen_subcommand_from watch' -a '(__fish_complete_directories)'
|
|
209
|
+
|
|
176
210
|
# completion options
|
|
177
211
|
complete -c 2ndbrain -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'
|
|
178
212
|
`;
|
package/src/commands/init.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const fs = require('fs-extra');
|
|
9
9
|
const {
|
|
10
|
+
PACKAGE_ROOT,
|
|
10
11
|
TEMPLATE_ROOT,
|
|
11
12
|
FRAMEWORK_FILES,
|
|
12
13
|
FRAMEWORK_DIRS,
|
|
@@ -14,6 +15,7 @@ const {
|
|
|
14
15
|
COPY_DIRS,
|
|
15
16
|
SMART_COPY_DIRS,
|
|
16
17
|
INIT_ONLY_FILES,
|
|
18
|
+
getFrameworkSourcePath,
|
|
17
19
|
is2ndBrainProject,
|
|
18
20
|
} = require('../lib/config');
|
|
19
21
|
const { copyFiles, ensureDirs, createFile, isDirEmpty, copyFilesSmart } = require('../lib/files');
|
|
@@ -141,6 +143,7 @@ async function handleObsidianReset(obsidianSrc, obsidianDest, log, skipConfirmat
|
|
|
141
143
|
async function init(targetPath, options, log) {
|
|
142
144
|
const resolvedPath = path.resolve(targetPath);
|
|
143
145
|
const templateRoot = options.template ? path.resolve(options.template) : TEMPLATE_ROOT;
|
|
146
|
+
const packageRoot = options.template ? path.resolve(templateRoot, '..') : PACKAGE_ROOT;
|
|
144
147
|
|
|
145
148
|
log.info(`Initializing 2ndBrain project at: ${resolvedPath}`);
|
|
146
149
|
log.info(`Using template from: ${templateRoot}`);
|
|
@@ -166,9 +169,9 @@ async function init(targetPath, options, log) {
|
|
|
166
169
|
|
|
167
170
|
// Scan existing directory for smart integration
|
|
168
171
|
const scan = await scanExistingDirectory(resolvedPath);
|
|
169
|
-
const
|
|
172
|
+
const isIntegrationMode = !isEmpty && scan.hasFiles && !isExistingProject;
|
|
170
173
|
|
|
171
|
-
if (
|
|
174
|
+
if (isIntegrationMode) {
|
|
172
175
|
log.info('');
|
|
173
176
|
log.info('Integration mode: Merging 2ndBrain framework into existing vault');
|
|
174
177
|
if (scan.hasUserDataDirs.size > 0) {
|
|
@@ -217,7 +220,7 @@ async function init(targetPath, options, log) {
|
|
|
217
220
|
log.info('Copying framework files...');
|
|
218
221
|
const fileResult = await copyFilesSmart(
|
|
219
222
|
FRAMEWORK_FILES,
|
|
220
|
-
templateRoot,
|
|
223
|
+
(file) => getFrameworkSourcePath(file, { templateRoot, packageRoot }),
|
|
221
224
|
resolvedPath,
|
|
222
225
|
{},
|
|
223
226
|
(file, action, detail) => {
|
|
@@ -289,7 +292,7 @@ async function init(targetPath, options, log) {
|
|
|
289
292
|
|
|
290
293
|
// Summary
|
|
291
294
|
log.info('');
|
|
292
|
-
log.success(
|
|
295
|
+
log.success(isIntegrationMode ? '2ndBrain framework integrated!' : '2ndBrain project initialized!');
|
|
293
296
|
log.info(` Created: ${fileResult.copied.length} files`);
|
|
294
297
|
if (fileResult.unchanged.length > 0) {
|
|
295
298
|
log.info(` Skipped: ${fileResult.unchanged.length} existing files`);
|
package/src/commands/member.js
CHANGED
|
@@ -18,6 +18,7 @@ const PLACEHOLDER = '{{MEMBER_NAME}}';
|
|
|
18
18
|
* Member files configuration
|
|
19
19
|
*/
|
|
20
20
|
const MEMBER_FILES = [
|
|
21
|
+
{ template: 'tpl_member_todo.md', output: '00_To-Do.md' },
|
|
21
22
|
{ template: 'tpl_member_tasks.md', output: '01_Tasks.md' },
|
|
22
23
|
{ template: 'tpl_member_done.md', output: '09_Done.md' },
|
|
23
24
|
];
|
|
@@ -28,7 +29,7 @@ const MEMBER_FILES = [
|
|
|
28
29
|
* @param {string} targetPath - Target project path
|
|
29
30
|
* @param {Object} options - Command options
|
|
30
31
|
* @param {boolean} [options.force] - Force overwrite existing member
|
|
31
|
-
* @param {boolean} [options.
|
|
32
|
+
* @param {boolean} [options.config] - Whether to update Obsidian config (set false by --no-config)
|
|
32
33
|
* @param {Object} log - Logger object
|
|
33
34
|
*/
|
|
34
35
|
async function member(memberName, targetPath, options, log) {
|
|
@@ -110,6 +111,7 @@ async function member(memberName, targetPath, options, log) {
|
|
|
110
111
|
log.success('🎉 Member init complete!');
|
|
111
112
|
log.info('');
|
|
112
113
|
log.info('Created files:');
|
|
114
|
+
log.info(` - 10_Inbox/${memberName}/00_To-Do.md (append-only task list)`);
|
|
113
115
|
log.info(` - 10_Inbox/${memberName}/01_Tasks.md (personal dashboard)`);
|
|
114
116
|
log.info(` - 10_Inbox/${memberName}/09_Done.md (done records)`);
|
|
115
117
|
log.info('');
|