@our2ndbrain/cli 2026.4.4 → 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.
Files changed (43) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +52 -789
  3. package/README_en.md +108 -0
  4. package/package.json +4 -6
  5. package/src/commands/init.js +4 -1
  6. package/src/commands/update.js +13 -8
  7. package/src/lib/config.js +45 -2
  8. package/src/lib/files.js +11 -6
  9. /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/obsidian_askpass.sh +0 -0
  10. package/template/10_Inbox/.gitkeep +0 -0
  11. package/template/20_Areas/.gitkeep +0 -0
  12. package/template/30_Projects/.gitkeep +0 -0
  13. package/template/40_Resources/.gitkeep +0 -0
  14. package/template/90_Archives/.gitkeep +0 -0
  15. /package/{99_System → template/99_System}/Scripts/init_member.sh +0 -0
  16. /package/{.obsidian → template/.obsidian}/.2ndbrain-manifest.json +0 -0
  17. /package/{.obsidian → template/.obsidian}/app.json +0 -0
  18. /package/{.obsidian → template/.obsidian}/appearance.json +0 -0
  19. /package/{.obsidian → template/.obsidian}/community-plugins.json +0 -0
  20. /package/{.obsidian → template/.obsidian}/core-plugins.json +0 -0
  21. /package/{.obsidian → template/.obsidian}/graph.json +0 -0
  22. /package/{.obsidian → template/.obsidian}/plugins/calendar/data.json +0 -0
  23. /package/{.obsidian → template/.obsidian}/plugins/calendar/main.js +0 -0
  24. /package/{.obsidian → template/.obsidian}/plugins/calendar/manifest.json +0 -0
  25. /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/data.json +0 -0
  26. /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/main.js +0 -0
  27. /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/manifest.json +0 -0
  28. /package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/styles.css +0 -0
  29. /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/data.json +0 -0
  30. /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/main.js +0 -0
  31. /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/manifest.json +0 -0
  32. /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/styles.css +0 -0
  33. /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/main.js +0 -0
  34. /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/manifest.json +0 -0
  35. /package/{.obsidian → template/.obsidian}/plugins/obsidian-tasks-plugin/styles.css +0 -0
  36. /package/{.obsidian → template/.obsidian}/types.json +0 -0
  37. /package/{00_Dashboard → template/00_Dashboard}/01_All_Tasks.md +0 -0
  38. /package/{00_Dashboard → template/00_Dashboard}/09_All_Done.md +0 -0
  39. /package/{10_Inbox → template/10_Inbox}/Agents/Journal.md +0 -0
  40. /package/{99_System → template/99_System}/Templates/tpl_daily_note.md +0 -0
  41. /package/{99_System → template/99_System}/Templates/tpl_member_done.md +0 -0
  42. /package/{99_System → template/99_System}/Templates/tpl_member_tasks.md +0 -0
  43. /package/{99_System → template/99_System}/Templates/tpl_member_todo.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
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
+ [![npm version](https://img.shields.io/npm/v/@our2ndbrain/cli.svg)](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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@our2ndbrain/cli",
3
- "version": "2026.4.4",
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,15 +24,13 @@
24
24
  "files": [
25
25
  "bin/",
26
26
  "src/",
27
- ".obsidian/",
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"
@@ -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}`);
@@ -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) => {
@@ -8,9 +8,11 @@ const path = require('path');
8
8
  const fs = require('fs-extra');
9
9
  const chalk = require('chalk');
10
10
  const {
11
+ PACKAGE_ROOT,
11
12
  TEMPLATE_ROOT,
12
13
  FRAMEWORK_FILES,
13
14
  SMART_COPY_DIRS,
15
+ getFrameworkSourcePath,
14
16
  is2ndBrainProject,
15
17
  } = require('../lib/config');
16
18
  const {
@@ -35,6 +37,7 @@ const { copyObsidianDirSmart, MERGE_STRATEGIES } = require('../lib/obsidian');
35
37
  async function update(targetPath, options, log) {
36
38
  const resolvedPath = path.resolve(targetPath);
37
39
  const templateRoot = options.template ? path.resolve(options.template) : TEMPLATE_ROOT;
40
+ const packageRoot = options.template ? path.resolve(templateRoot, '..') : PACKAGE_ROOT;
38
41
 
39
42
  log.info(`Updating 2ndBrain project at: ${resolvedPath}`);
40
43
  log.info(`Using template from: ${templateRoot}`);
@@ -47,21 +50,22 @@ async function update(targetPath, options, log) {
47
50
  }
48
51
 
49
52
  if (options.dryRun) {
50
- await performDryRun(resolvedPath, templateRoot, log);
53
+ await performDryRun(resolvedPath, templateRoot, packageRoot, log);
51
54
  return;
52
55
  }
53
56
 
54
57
  // Main update flow
55
- await performUpdate(resolvedPath, templateRoot, options, log);
58
+ await performUpdate(resolvedPath, templateRoot, packageRoot, options, log);
56
59
  }
57
60
 
58
61
  /**
59
62
  * Perform dry run - show what would be updated
60
63
  * @param {string} resolvedPath - Target project path
61
64
  * @param {string} templateRoot - Template root path
65
+ * @param {string} packageRoot - Package root path
62
66
  * @param {Function} log - Logger function
63
67
  */
64
- async function performDryRun(resolvedPath, templateRoot, log) {
68
+ async function performDryRun(resolvedPath, templateRoot, packageRoot, log) {
65
69
  log.warn('[DRY RUN] No files will be modified.');
66
70
  log.info('');
67
71
 
@@ -69,7 +73,7 @@ async function performDryRun(resolvedPath, templateRoot, log) {
69
73
 
70
74
  const result = await copyFilesSmart(
71
75
  FRAMEWORK_FILES,
72
- templateRoot,
76
+ (file) => getFrameworkSourcePath(file, { templateRoot, packageRoot }),
73
77
  resolvedPath,
74
78
  { dryRun: true },
75
79
  (file, action, detail) => {
@@ -163,17 +167,18 @@ async function performDryRun(resolvedPath, templateRoot, log) {
163
167
  * Perform the actual update with user confirmation
164
168
  * @param {string} resolvedPath - Target project path
165
169
  * @param {string} templateRoot - Template root path
170
+ * @param {string} packageRoot - Package root path
166
171
  * @param {Object} options - Command options
167
172
  * @param {boolean} [options.yes] - Auto-confirm all updates
168
173
  * @param {Function} log - Logger function
169
174
  */
170
- async function performUpdate(resolvedPath, templateRoot, options, log) {
175
+ async function performUpdate(resolvedPath, templateRoot, packageRoot, options, log) {
171
176
  log.info('Analyzing framework files...');
172
177
 
173
178
  // First pass: analyze all files
174
179
  const analysis = await copyFilesSmart(
175
180
  FRAMEWORK_FILES,
176
- templateRoot,
181
+ (file) => getFrameworkSourcePath(file, { templateRoot, packageRoot }),
177
182
  resolvedPath,
178
183
  {},
179
184
  (file, action, detail) => {
@@ -218,7 +223,7 @@ async function performUpdate(resolvedPath, templateRoot, options, log) {
218
223
  if (userChoice === 'all') {
219
224
  // Apply all changes
220
225
  for (const change of changes) {
221
- const src = path.join(templateRoot, change.file);
226
+ const src = getFrameworkSourcePath(change.file, { templateRoot, packageRoot });
222
227
  const resolvedSrc = await resolveTemplateSourcePath(src, change.file);
223
228
  const dest = path.join(resolvedPath, change.file);
224
229
 
@@ -245,7 +250,7 @@ async function performUpdate(resolvedPath, templateRoot, options, log) {
245
250
  // Review each file individually
246
251
  for (const change of changes) {
247
252
  const file = change.file;
248
- const src = path.join(templateRoot, file);
253
+ const src = getFrameworkSourcePath(file, { templateRoot, packageRoot });
249
254
  const resolvedSrc = await resolveTemplateSourcePath(src, file);
250
255
  const dest = path.join(resolvedPath, file);
251
256
 
package/src/lib/config.js CHANGED
@@ -6,8 +6,20 @@
6
6
 
7
7
  const path = require('path');
8
8
 
9
- // Template root directory (npm package root)
10
- const TEMPLATE_ROOT = path.resolve(__dirname, '../../');
9
+ // npm package root containing CLI docs and metadata
10
+ const PACKAGE_ROOT = path.resolve(__dirname, '../..');
11
+
12
+ // Vault template root containing shipped vault assets
13
+ const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, 'template');
14
+
15
+ const PACKAGE_DOC_FILES = [
16
+ 'AGENTS.md',
17
+ 'README.md',
18
+ 'README_en.md',
19
+ 'CHANGELOG.md',
20
+ 'CLAUDE.md',
21
+ 'LICENSE',
22
+ ];
11
23
 
12
24
  /**
13
25
  * Framework files - managed by init/update/remove commands
@@ -16,6 +28,7 @@ const TEMPLATE_ROOT = path.resolve(__dirname, '../../');
16
28
  const FRAMEWORK_FILES = [
17
29
  'AGENTS.md',
18
30
  'README.md',
31
+ 'README_en.md',
19
32
  'CHANGELOG.md',
20
33
  'CLAUDE.md',
21
34
  'LICENSE',
@@ -84,6 +97,32 @@ function getTemplatePath(relativePath, templateRoot = TEMPLATE_ROOT) {
84
97
  return path.join(templateRoot, relativePath);
85
98
  }
86
99
 
100
+ /**
101
+ * Check whether a framework file is sourced from the package root docs.
102
+ * @param {string} relativePath - Relative framework file path
103
+ * @returns {boolean}
104
+ */
105
+ function isPackageDocFile(relativePath) {
106
+ return PACKAGE_DOC_FILES.includes(relativePath);
107
+ }
108
+
109
+ /**
110
+ * Resolve the source path for a framework file.
111
+ * Package docs come from the npm package root; vault assets come from template/.
112
+ * @param {string} relativePath - Relative framework file path
113
+ * @param {Object} [options]
114
+ * @param {string} [options.templateRoot] - Template directory override
115
+ * @param {string} [options.packageRoot] - Package root override
116
+ * @returns {string} Absolute source path
117
+ */
118
+ function getFrameworkSourcePath(
119
+ relativePath,
120
+ { templateRoot = TEMPLATE_ROOT, packageRoot = PACKAGE_ROOT } = {}
121
+ ) {
122
+ const sourceRoot = isPackageDocFile(relativePath) ? packageRoot : templateRoot;
123
+ return path.join(sourceRoot, relativePath);
124
+ }
125
+
87
126
  /**
88
127
  * Check if a path is a 2ndBrain project
89
128
  * @param {string} targetPath - Path to check
@@ -100,7 +139,9 @@ function is2ndBrainProject(targetPath) {
100
139
  }
101
140
 
102
141
  module.exports = {
142
+ PACKAGE_ROOT,
103
143
  TEMPLATE_ROOT,
144
+ PACKAGE_DOC_FILES,
104
145
  FRAMEWORK_FILES,
105
146
  FRAMEWORK_DIRS,
106
147
  USER_DATA_DIRS,
@@ -109,5 +150,7 @@ module.exports = {
109
150
  INIT_ONLY_FILES,
110
151
  MARKER_FILE,
111
152
  getTemplatePath,
153
+ isPackageDocFile,
154
+ getFrameworkSourcePath,
112
155
  is2ndBrainProject,
113
156
  };
package/src/lib/files.js CHANGED
@@ -45,16 +45,19 @@ async function copyFile(src, dest) {
45
45
  /**
46
46
  * Copy multiple files from template to target
47
47
  * @param {string[]} files - Array of relative file paths
48
- * @param {string} templateRoot - Template root directory
48
+ * @param {string|Function} sourceRootOrResolver - Source root directory or resolver
49
49
  * @param {string} targetRoot - Target root directory
50
50
  * @param {Function} [onFile] - Callback for each file (relativePath, action)
51
51
  * @returns {Promise<{copied: string[], skipped: string[], errors: string[]}>}
52
52
  */
53
- async function copyFiles(files, templateRoot, targetRoot, onFile) {
53
+ async function copyFiles(files, sourceRootOrResolver, targetRoot, onFile) {
54
54
  const result = { copied: [], skipped: [], errors: [] };
55
55
 
56
56
  for (const file of files) {
57
- const src = await resolveSourcePath(path.join(templateRoot, file));
57
+ const requestedSrc = typeof sourceRootOrResolver === 'function'
58
+ ? sourceRootOrResolver(file)
59
+ : path.join(sourceRootOrResolver, file);
60
+ const src = await resolveSourcePath(requestedSrc);
58
61
  const dest = path.join(targetRoot, file);
59
62
 
60
63
  try {
@@ -302,7 +305,7 @@ async function copyFileWithCompare(src, dest, options = {}) {
302
305
  /**
303
306
  * Copy multiple files with smart comparison
304
307
  * @param {string[]} files - Array of relative file paths
305
- * @param {string} templateRoot - Template root directory
308
+ * @param {string|Function} sourceRootOrResolver - Source root directory or resolver
306
309
  * @param {string} targetRoot - Target root directory
307
310
  * @param {Object} options - Copy options
308
311
  * @param {boolean} [options.force] - Force copy even if equal
@@ -310,7 +313,7 @@ async function copyFileWithCompare(src, dest, options = {}) {
310
313
  * @param {Function} [onFile] - Callback for each file (relativePath, action, detail)
311
314
  * @returns {Promise<{copied: string[], skipped: string[], unchanged: string[], errors: string[], changes: Array}>}
312
315
  */
313
- async function copyFilesSmart(files, templateRoot, targetRoot, options = {}, onFile) {
316
+ async function copyFilesSmart(files, sourceRootOrResolver, targetRoot, options = {}, onFile) {
314
317
  const result = {
315
318
  copied: [],
316
319
  skipped: [],
@@ -320,7 +323,9 @@ async function copyFilesSmart(files, templateRoot, targetRoot, options = {}, onF
320
323
  };
321
324
 
322
325
  for (const file of files) {
323
- const src = path.join(templateRoot, file);
326
+ const src = typeof sourceRootOrResolver === 'function'
327
+ ? sourceRootOrResolver(file)
328
+ : path.join(sourceRootOrResolver, file);
324
329
  const dest = path.join(targetRoot, file);
325
330
 
326
331
  try {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes