@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.
- package/CHANGELOG.md +13 -0
- package/README.md +52 -789
- package/README_en.md +108 -0
- package/package.json +4 -6
- package/src/commands/init.js +4 -1
- package/src/commands/update.js +13 -8
- package/src/lib/config.js +45 -2
- package/src/lib/files.js +11 -6
- /package/{.obsidian → template/.obsidian}/plugins/obsidian-git/obsidian_askpass.sh +0 -0
- 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/{.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}/01_All_Tasks.md +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_daily_note.md +0 -0
- /package/{99_System → template/99_System}/Templates/tpl_member_done.md +0 -0
- /package/{99_System → template/99_System}/Templates/tpl_member_tasks.md +0 -0
- /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
|
+
[](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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@our2ndbrain/cli",
|
|
3
|
-
"version": "2026.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
|
-
"
|
|
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"
|
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}`);
|
|
@@ -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) => {
|
package/src/commands/update.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
//
|
|
10
|
-
const
|
|
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}
|
|
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,
|
|
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
|
|
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}
|
|
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,
|
|
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 =
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/data.json
RENAMED
|
File without changes
|
/package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/main.js
RENAMED
|
File without changes
|
/package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/manifest.json
RENAMED
|
File without changes
|
/package/{.obsidian → template/.obsidian}/plugins/obsidian-custom-attachment-location/styles.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|