im-nobsidian 0.1.0
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/LICENSE +21 -0
- package/dist/index.js +577 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hanseungheon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command9 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { ConfigManager, NotionClient } from "@im-nobsidian/core";
|
|
9
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
var initCommand = new Command("init").description("Im-Nobsidian \uCD08\uAE30 \uC124\uC815").option("--token <token>", "Notion Integration Token").option("--root-page-id <id>", "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID").option("--non-interactive", "\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC").action(async (options) => {
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const configManager = new ConfigManager(cwd);
|
|
14
|
+
if (options.nonInteractive) {
|
|
15
|
+
if (!options.token || !options.rootPageId) {
|
|
16
|
+
console.error("\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC\uC5D0\uC11C\uB294 --token\uACFC --root-page-id\uAC00 \uD544\uC218\uC785\uB2C8\uB2E4.");
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!options.token.startsWith("ntn_")) {
|
|
21
|
+
console.error("\uD1A0\uD070\uC740 ntn_\uC73C\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.");
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const spinner2 = ora("Notion \uC5F0\uACB0 \uD655\uC778 \uC911...").start();
|
|
26
|
+
try {
|
|
27
|
+
const client = new NotionClient({ token: options.token });
|
|
28
|
+
await client.search({ pageSize: 1, filter: { property: "object", value: "page" } });
|
|
29
|
+
spinner2.succeed("Notion \uC5F0\uACB0 \uC131\uACF5");
|
|
30
|
+
await configManager.init({ token: options.token, rootPageId: options.rootPageId });
|
|
31
|
+
console.log("\n\u2713 Im-Nobsidian \uCD08\uAE30\uD654 \uC644\uB8CC!");
|
|
32
|
+
console.log(` \uC124\uC815: ${configManager.configPath}`);
|
|
33
|
+
console.log(` DB: ${configManager.dbPath}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
spinner2.fail("Notion \uC5F0\uACB0 \uC2E4\uD328");
|
|
36
|
+
console.error(error instanceof Error ? error.message : error);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const isInit = await configManager.isInitialized();
|
|
42
|
+
if (isInit) {
|
|
43
|
+
const overwrite = await confirm({
|
|
44
|
+
message: "\uC774\uBBF8 \uCD08\uAE30\uD654\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC124\uC815\uD560\uAE4C\uC694?",
|
|
45
|
+
default: false
|
|
46
|
+
});
|
|
47
|
+
if (!overwrite) return;
|
|
48
|
+
}
|
|
49
|
+
const token = await input({
|
|
50
|
+
message: "Notion Internal Integration Token:",
|
|
51
|
+
validate: (v) => v.startsWith("ntn_") || "ntn_\uC73C\uB85C \uC2DC\uC791\uD558\uB294 \uD1A0\uD070\uC744 \uC785\uB825\uD558\uC138\uC694"
|
|
52
|
+
});
|
|
53
|
+
const spinner = ora("Notion \uC5F0\uACB0 \uD655\uC778 \uC911...").start();
|
|
54
|
+
try {
|
|
55
|
+
const client = new NotionClient({ token });
|
|
56
|
+
const result = await client.search({
|
|
57
|
+
pageSize: 10,
|
|
58
|
+
filter: { property: "object", value: "page" }
|
|
59
|
+
});
|
|
60
|
+
spinner.succeed("Notion \uC5F0\uACB0 \uC131\uACF5");
|
|
61
|
+
if (result.results.length === 0) {
|
|
62
|
+
spinner.warn("\uC811\uADFC \uAC00\uB2A5\uD55C \uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. Integration\uC5D0 \uD398\uC774\uC9C0\uB97C \uACF5\uC720\uD574\uC8FC\uC138\uC694.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log("\n\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB8E8\uD2B8 \uD398\uC774\uC9C0:");
|
|
66
|
+
for (const [i, page] of result.results.entries()) {
|
|
67
|
+
const title = extractPageTitle(page);
|
|
68
|
+
console.log(` [${i + 1}] ${title}`);
|
|
69
|
+
}
|
|
70
|
+
const rootPageId = await input({
|
|
71
|
+
message: "\uB8E8\uD2B8 \uD398\uC774\uC9C0 ID (\uC704\uC5D0\uC11C \uC120\uD0DD\uD558\uAC70\uB098 \uC9C1\uC811 \uC785\uB825):",
|
|
72
|
+
default: result.results[0]?.id
|
|
73
|
+
});
|
|
74
|
+
await configManager.init({ token, rootPageId });
|
|
75
|
+
console.log("\n\u2713 Im-Nobsidian \uCD08\uAE30\uD654 \uC644\uB8CC!");
|
|
76
|
+
console.log(` \uC124\uC815: ${configManager.configPath}`);
|
|
77
|
+
console.log(` DB: ${configManager.dbPath}`);
|
|
78
|
+
console.log("\n\uB2E4\uC74C \uBA85\uB839\uC73C\uB85C \uB3D9\uAE30\uD654\uB97C \uC2DC\uC791\uD558\uC138\uC694:");
|
|
79
|
+
console.log(" nobsi pull \u2014 Notion \u2192 \uB85C\uCEEC");
|
|
80
|
+
console.log(" nobsi push \u2014 \uB85C\uCEEC \u2192 Notion");
|
|
81
|
+
console.log(" nobsi sync \u2014 \uC591\uBC29\uD5A5");
|
|
82
|
+
} catch (error) {
|
|
83
|
+
spinner.fail("Notion \uC5F0\uACB0 \uC2E4\uD328");
|
|
84
|
+
console.error(error instanceof Error ? error.message : error);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
function extractPageTitle(page) {
|
|
89
|
+
const properties = page["properties"];
|
|
90
|
+
if (!properties) return "(\uC81C\uBAA9 \uC5C6\uC74C)";
|
|
91
|
+
for (const prop of Object.values(properties)) {
|
|
92
|
+
const p = prop;
|
|
93
|
+
if (p.type === "title" && p.title?.[0]?.plain_text) {
|
|
94
|
+
return p.title[0].plain_text;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return "(\uC81C\uBAA9 \uC5C6\uC74C)";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/commands/push.ts
|
|
101
|
+
import { Command as Command2 } from "commander";
|
|
102
|
+
import {
|
|
103
|
+
ConfigManager as ConfigManager2,
|
|
104
|
+
StateDB,
|
|
105
|
+
NotionClient as NotionClient2,
|
|
106
|
+
SyncOrchestrator,
|
|
107
|
+
NodeVaultFS
|
|
108
|
+
} from "@im-nobsidian/core";
|
|
109
|
+
import ora2 from "ora";
|
|
110
|
+
var pushCommand = new Command2("push").description("\uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D\uC744 Notion\uC5D0 \uBC18\uC601").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("-p, --path <paths...>", "\uD2B9\uC815 \uACBD\uB85C\uB9CC push").action(async (options) => {
|
|
111
|
+
const cwd = process.cwd();
|
|
112
|
+
const configManager = new ConfigManager2(cwd);
|
|
113
|
+
const config = await configManager.load();
|
|
114
|
+
const stateDb = StateDB.open(configManager.dbPath);
|
|
115
|
+
try {
|
|
116
|
+
const client = new NotionClient2({
|
|
117
|
+
token: config.notion.token,
|
|
118
|
+
concurrency: config.advanced.concurrency,
|
|
119
|
+
timeoutMs: config.advanced.timeoutMs
|
|
120
|
+
});
|
|
121
|
+
const vaultFs = new NodeVaultFS(cwd, config.paths);
|
|
122
|
+
const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);
|
|
123
|
+
const spinner = ora2("Push \uC911...").start();
|
|
124
|
+
const result = await orchestrator.push({
|
|
125
|
+
dryRun: options.dryRun,
|
|
126
|
+
paths: options.path,
|
|
127
|
+
onProgress: (current, total, path) => {
|
|
128
|
+
const name = path.split("/").pop() ?? path;
|
|
129
|
+
spinner.text = `Push \uC911... [${current}/${total}] ${name}`;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
spinner.stop();
|
|
133
|
+
console.log(`
|
|
134
|
+
\u2713 Push \uC644\uB8CC (${(result.duration / 1e3).toFixed(1)}s)`);
|
|
135
|
+
console.log(` \uC0DD\uC131: ${result.created}`);
|
|
136
|
+
console.log(` \uC218\uC815: ${result.updated}`);
|
|
137
|
+
console.log(` \uC0AD\uC81C: ${result.deleted}`);
|
|
138
|
+
if (result.failed.length > 0) {
|
|
139
|
+
console.log(` \uC2E4\uD328: ${result.failed.length}`);
|
|
140
|
+
for (const f of result.failed) {
|
|
141
|
+
console.log(` \u2715 ${f.path}: ${f.error}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
stateDb.close();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// src/commands/pull.ts
|
|
150
|
+
import { Command as Command3 } from "commander";
|
|
151
|
+
import {
|
|
152
|
+
ConfigManager as ConfigManager3,
|
|
153
|
+
StateDB as StateDB2,
|
|
154
|
+
NotionClient as NotionClient3,
|
|
155
|
+
SyncOrchestrator as SyncOrchestrator2,
|
|
156
|
+
NodeVaultFS as NodeVaultFS2
|
|
157
|
+
} from "@im-nobsidian/core";
|
|
158
|
+
import ora3 from "ora";
|
|
159
|
+
var pullCommand = new Command3("pull").description("Notion \uBCC0\uACBD\uC0AC\uD56D\uC744 \uB85C\uCEEC\uC5D0 \uBC18\uC601").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("-p, --path <paths...>", "\uD2B9\uC815 \uACBD\uB85C\uB9CC pull").action(async (options) => {
|
|
160
|
+
const cwd = process.cwd();
|
|
161
|
+
const configManager = new ConfigManager3(cwd);
|
|
162
|
+
const config = await configManager.load();
|
|
163
|
+
const stateDb = StateDB2.open(configManager.dbPath);
|
|
164
|
+
try {
|
|
165
|
+
const client = new NotionClient3({
|
|
166
|
+
token: config.notion.token,
|
|
167
|
+
concurrency: config.advanced.concurrency,
|
|
168
|
+
timeoutMs: config.advanced.timeoutMs
|
|
169
|
+
});
|
|
170
|
+
const vaultFs = new NodeVaultFS2(cwd, config.paths);
|
|
171
|
+
const orchestrator = new SyncOrchestrator2(config, stateDb, client, vaultFs);
|
|
172
|
+
const spinner = ora3("Pull \uC911...").start();
|
|
173
|
+
const result = await orchestrator.pull({
|
|
174
|
+
dryRun: options.dryRun,
|
|
175
|
+
paths: options.path,
|
|
176
|
+
onProgress: (current, total, _pageId) => {
|
|
177
|
+
spinner.text = `Pull \uC911... [${current}/${total}]`;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
spinner.stop();
|
|
181
|
+
console.log(`
|
|
182
|
+
\u2713 Pull \uC644\uB8CC (${(result.duration / 1e3).toFixed(1)}s)`);
|
|
183
|
+
console.log(` \uC0DD\uC131: ${result.created}`);
|
|
184
|
+
console.log(` \uC218\uC815: ${result.updated}`);
|
|
185
|
+
console.log(` \uC0AD\uC81C: ${result.deleted}`);
|
|
186
|
+
if (result.conflicts.length > 0) {
|
|
187
|
+
console.log(` \uCDA9\uB3CC: ${result.conflicts.length}`);
|
|
188
|
+
console.log(' "nobsi resolve"\uB85C \uCDA9\uB3CC\uC744 \uD574\uACB0\uD558\uC138\uC694.');
|
|
189
|
+
}
|
|
190
|
+
if (result.failed.length > 0) {
|
|
191
|
+
console.log(` \uC2E4\uD328: ${result.failed.length}`);
|
|
192
|
+
for (const f of result.failed) {
|
|
193
|
+
console.log(` \u2715 ${f.path}: ${f.error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} finally {
|
|
197
|
+
stateDb.close();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// src/commands/sync.ts
|
|
202
|
+
import { Command as Command4 } from "commander";
|
|
203
|
+
import {
|
|
204
|
+
ConfigManager as ConfigManager4,
|
|
205
|
+
StateDB as StateDB3,
|
|
206
|
+
NotionClient as NotionClient4,
|
|
207
|
+
SyncOrchestrator as SyncOrchestrator3,
|
|
208
|
+
NodeVaultFS as NodeVaultFS3
|
|
209
|
+
} from "@im-nobsidian/core";
|
|
210
|
+
import ora4 from "ora";
|
|
211
|
+
var syncCommand = new Command4("sync").description("\uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 (Pull \u2192 Push)").option("--dry-run", "\uC2E4\uC81C \uBC18\uC601 \uC5C6\uC774 \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").action(async (options) => {
|
|
212
|
+
const cwd = process.cwd();
|
|
213
|
+
const configManager = new ConfigManager4(cwd);
|
|
214
|
+
const config = await configManager.load();
|
|
215
|
+
const stateDb = StateDB3.open(configManager.dbPath);
|
|
216
|
+
try {
|
|
217
|
+
const client = new NotionClient4({
|
|
218
|
+
token: config.notion.token,
|
|
219
|
+
concurrency: config.advanced.concurrency,
|
|
220
|
+
timeoutMs: config.advanced.timeoutMs
|
|
221
|
+
});
|
|
222
|
+
const vaultFs = new NodeVaultFS3(cwd, config.paths);
|
|
223
|
+
const orchestrator = new SyncOrchestrator3(config, stateDb, client, vaultFs);
|
|
224
|
+
const spinner = ora4("\uB3D9\uAE30\uD654 \uC911...").start();
|
|
225
|
+
const result = await orchestrator.sync({
|
|
226
|
+
dryRun: options.dryRun,
|
|
227
|
+
onProgress: (current, total, _item) => {
|
|
228
|
+
spinner.text = `\uB3D9\uAE30\uD654 \uC911... [${current}/${total}]`;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
spinner.stop();
|
|
232
|
+
console.log(`
|
|
233
|
+
\u2713 \uB3D9\uAE30\uD654 \uC644\uB8CC (${(result.duration / 1e3).toFixed(1)}s)`);
|
|
234
|
+
console.log(
|
|
235
|
+
` Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`
|
|
236
|
+
);
|
|
237
|
+
console.log(
|
|
238
|
+
` Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}`
|
|
239
|
+
);
|
|
240
|
+
if (result.conflicts.length > 0) {
|
|
241
|
+
console.log(`
|
|
242
|
+
\u26A0 \uCDA9\uB3CC ${result.conflicts.length}\uAC74`);
|
|
243
|
+
console.log(' "nobsi resolve"\uB85C \uCDA9\uB3CC\uC744 \uD574\uACB0\uD558\uC138\uC694.');
|
|
244
|
+
}
|
|
245
|
+
} finally {
|
|
246
|
+
stateDb.close();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// src/commands/status.ts
|
|
251
|
+
import { Command as Command5 } from "commander";
|
|
252
|
+
import {
|
|
253
|
+
ConfigManager as ConfigManager5,
|
|
254
|
+
StateDB as StateDB4,
|
|
255
|
+
NotionClient as NotionClient5,
|
|
256
|
+
SyncOrchestrator as SyncOrchestrator4,
|
|
257
|
+
NodeVaultFS as NodeVaultFS4
|
|
258
|
+
} from "@im-nobsidian/core";
|
|
259
|
+
var statusCommand = new Command5("status").description("\uB3D9\uAE30\uD654 \uC0C1\uD0DC \uD655\uC778").action(async () => {
|
|
260
|
+
const cwd = process.cwd();
|
|
261
|
+
const configManager = new ConfigManager5(cwd);
|
|
262
|
+
const config = await configManager.load();
|
|
263
|
+
const stateDb = StateDB4.open(configManager.dbPath);
|
|
264
|
+
try {
|
|
265
|
+
const client = new NotionClient5({
|
|
266
|
+
token: config.notion.token,
|
|
267
|
+
concurrency: config.advanced.concurrency
|
|
268
|
+
});
|
|
269
|
+
const vaultFs = new NodeVaultFS4(cwd, config.paths);
|
|
270
|
+
const orchestrator = new SyncOrchestrator4(config, stateDb, client, vaultFs);
|
|
271
|
+
const status = await orchestrator.status();
|
|
272
|
+
if (status.lastSyncAt) {
|
|
273
|
+
console.log(`\uB9C8\uC9C0\uB9C9 \uB3D9\uAE30\uD654: ${status.lastSyncAt}`);
|
|
274
|
+
} else {
|
|
275
|
+
console.log("\uC544\uC9C1 \uB3D9\uAE30\uD654\uB41C \uC801 \uC5C6\uC74C");
|
|
276
|
+
}
|
|
277
|
+
console.log("");
|
|
278
|
+
if (status.localChanges.length === 0) {
|
|
279
|
+
console.log("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C (clean)");
|
|
280
|
+
} else {
|
|
281
|
+
console.log(`\uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D (${status.localChanges.length}\uAC74):`);
|
|
282
|
+
for (const change of status.localChanges) {
|
|
283
|
+
const prefix = change.type === "created" ? "+" : change.type === "deleted" ? "-" : "~";
|
|
284
|
+
console.log(` ${prefix} ${change.path}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (status.conflictRecords.length > 0) {
|
|
288
|
+
console.log(`
|
|
289
|
+
\uCDA9\uB3CC (${status.conflictRecords.length}\uAC74):`);
|
|
290
|
+
for (const record of status.conflictRecords) {
|
|
291
|
+
console.log(` \u26A0 ${record.obsidianPath}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} finally {
|
|
295
|
+
stateDb.close();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// src/commands/diff.ts
|
|
300
|
+
import { Command as Command6 } from "commander";
|
|
301
|
+
import {
|
|
302
|
+
ConfigManager as ConfigManager6,
|
|
303
|
+
StateDB as StateDB5,
|
|
304
|
+
NotionClient as NotionClient6,
|
|
305
|
+
SyncOrchestrator as SyncOrchestrator5,
|
|
306
|
+
NodeVaultFS as NodeVaultFS5
|
|
307
|
+
} from "@im-nobsidian/core";
|
|
308
|
+
import { createTwoFilesPatch } from "diff";
|
|
309
|
+
var diffCommand = new Command6("diff").description("\uB85C\uCEEC\uACFC Notion \uAC04\uC758 \uCC28\uC774 \uD45C\uC2DC").argument("[path]", "\uD2B9\uC815 \uD30C\uC77C \uACBD\uB85C (\uC0DD\uB7B5 \uC2DC \uBCC0\uACBD\uB41C \uBAA8\uB4E0 \uD30C\uC77C)").option("--color", "\uCEEC\uB7EC \uCD9C\uB825", true).action(async (targetPath, options) => {
|
|
310
|
+
const cwd = process.cwd();
|
|
311
|
+
const configManager = new ConfigManager6(cwd);
|
|
312
|
+
const config = await configManager.load();
|
|
313
|
+
const stateDb = StateDB5.open(configManager.dbPath);
|
|
314
|
+
try {
|
|
315
|
+
const client = new NotionClient6({
|
|
316
|
+
token: config.notion.token,
|
|
317
|
+
concurrency: config.advanced.concurrency,
|
|
318
|
+
timeoutMs: config.advanced.timeoutMs
|
|
319
|
+
});
|
|
320
|
+
const vaultFs = new NodeVaultFS5(cwd, config.paths);
|
|
321
|
+
const orchestrator = new SyncOrchestrator5(config, stateDb, client, vaultFs);
|
|
322
|
+
const status = await orchestrator.status();
|
|
323
|
+
const changes = targetPath ? status.localChanges.filter((c) => c.path === targetPath || c.path.startsWith(targetPath)) : status.localChanges;
|
|
324
|
+
if (changes.length === 0) {
|
|
325
|
+
console.log("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
for (const change of changes) {
|
|
329
|
+
const record = stateDb.getByPath(change.path);
|
|
330
|
+
if (change.type === "created") {
|
|
331
|
+
const content = await vaultFs.readFile(change.path);
|
|
332
|
+
const patch = createTwoFilesPatch(`/dev/null`, `b/${change.path}`, "", content);
|
|
333
|
+
printPatch(patch, options.color);
|
|
334
|
+
} else if (change.type === "deleted") {
|
|
335
|
+
if (record?.baseSnapshot) {
|
|
336
|
+
const oldContent = record.baseSnapshot.toString("utf-8");
|
|
337
|
+
const patch = createTwoFilesPatch(`a/${change.path}`, `/dev/null`, oldContent, "");
|
|
338
|
+
printPatch(patch, options.color);
|
|
339
|
+
} else {
|
|
340
|
+
console.log(`--- a/${change.path}`);
|
|
341
|
+
console.log(`+++ /dev/null`);
|
|
342
|
+
console.log(`(\uC774\uC804 \uC2A4\uB0C5\uC0F7 \uC5C6\uC74C)`);
|
|
343
|
+
}
|
|
344
|
+
} else if (change.type === "modified" && record?.baseSnapshot) {
|
|
345
|
+
const currentContent = await vaultFs.readFile(change.path);
|
|
346
|
+
const baseContent = record.baseSnapshot.toString("utf-8");
|
|
347
|
+
const patch = createTwoFilesPatch(
|
|
348
|
+
`a/${change.path}`,
|
|
349
|
+
`b/${change.path}`,
|
|
350
|
+
baseContent,
|
|
351
|
+
currentContent
|
|
352
|
+
);
|
|
353
|
+
printPatch(patch, options.color);
|
|
354
|
+
}
|
|
355
|
+
console.log("");
|
|
356
|
+
}
|
|
357
|
+
} finally {
|
|
358
|
+
stateDb.close();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
function printPatch(patch, color) {
|
|
362
|
+
for (const line of patch.split("\n")) {
|
|
363
|
+
if (!color) {
|
|
364
|
+
console.log(line);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
368
|
+
console.log(`\x1B[1m${line}\x1B[0m`);
|
|
369
|
+
} else if (line.startsWith("+")) {
|
|
370
|
+
console.log(`\x1B[32m${line}\x1B[0m`);
|
|
371
|
+
} else if (line.startsWith("-")) {
|
|
372
|
+
console.log(`\x1B[31m${line}\x1B[0m`);
|
|
373
|
+
} else if (line.startsWith("@@")) {
|
|
374
|
+
console.log(`\x1B[36m${line}\x1B[0m`);
|
|
375
|
+
} else {
|
|
376
|
+
console.log(line);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/commands/resolve.ts
|
|
382
|
+
import { Command as Command7 } from "commander";
|
|
383
|
+
import { select, confirm as confirm2 } from "@inquirer/prompts";
|
|
384
|
+
import {
|
|
385
|
+
ConfigManager as ConfigManager7,
|
|
386
|
+
StateDB as StateDB6,
|
|
387
|
+
NotionClient as NotionClient7,
|
|
388
|
+
SyncOrchestrator as SyncOrchestrator6,
|
|
389
|
+
NodeVaultFS as NodeVaultFS6,
|
|
390
|
+
ConflictResolver
|
|
391
|
+
} from "@im-nobsidian/core";
|
|
392
|
+
var resolveCommand = new Command7("resolve").description("\uCDA9\uB3CC\uC744 \uD574\uACB0\uD569\uB2C8\uB2E4").option("-s, --strategy <strategy>", "\uC790\uB3D9 \uD574\uACB0 \uC804\uB7B5 (local-first, remote-first, duplicate)").action(async (options) => {
|
|
393
|
+
const cwd = process.cwd();
|
|
394
|
+
const configManager = new ConfigManager7(cwd);
|
|
395
|
+
try {
|
|
396
|
+
const config = await configManager.load();
|
|
397
|
+
const stateDb = StateDB6.open(configManager.dbPath);
|
|
398
|
+
const client = new NotionClient7({
|
|
399
|
+
token: config.notion.token,
|
|
400
|
+
concurrency: 3,
|
|
401
|
+
timeoutMs: 3e4
|
|
402
|
+
});
|
|
403
|
+
const vaultFs = new NodeVaultFS6(cwd, config.paths);
|
|
404
|
+
const orchestrator = new SyncOrchestrator6(config, stateDb, client, vaultFs);
|
|
405
|
+
const resolver = new ConflictResolver(stateDb, vaultFs);
|
|
406
|
+
await orchestrator.status();
|
|
407
|
+
const conflictRecords = stateDb.getByStatus("conflict");
|
|
408
|
+
if (conflictRecords.length === 0) {
|
|
409
|
+
console.log("\uCDA9\uB3CC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
410
|
+
stateDb.close();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
console.log(`
|
|
414
|
+
\uCDA9\uB3CC ${conflictRecords.length}\uAC74 \uBC1C\uACAC
|
|
415
|
+
`);
|
|
416
|
+
if (options.strategy) {
|
|
417
|
+
const strategy = options.strategy;
|
|
418
|
+
const shouldProceed = await confirm2({
|
|
419
|
+
message: `${conflictRecords.length}\uAC74\uC758 \uCDA9\uB3CC\uC744 '${strategy}' \uC804\uB7B5\uC73C\uB85C \uC77C\uAD04 \uD574\uACB0\uD560\uAE4C\uC694?`
|
|
420
|
+
});
|
|
421
|
+
if (!shouldProceed) {
|
|
422
|
+
stateDb.close();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const pullResult = await orchestrator.pull();
|
|
426
|
+
const results = await resolver.resolveAll(pullResult.conflicts, strategy);
|
|
427
|
+
for (const result of results) {
|
|
428
|
+
const icon = result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
|
|
429
|
+
console.log(` ${icon} ${result.path} \u2192 ${result.choice}`);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
const pullResult = await orchestrator.pull();
|
|
433
|
+
for (const conflict of pullResult.conflicts) {
|
|
434
|
+
await resolveInteractive(conflict, resolver);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
stateDb.close();
|
|
438
|
+
console.log("\n\uCDA9\uB3CC \uD574\uACB0 \uC644\uB8CC");
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.error("\uC624\uB958:", error instanceof Error ? error.message : String(error));
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
async function resolveInteractive(conflict, resolver) {
|
|
445
|
+
const path = conflict.syncRecord.obsidianPath;
|
|
446
|
+
console.log(`
|
|
447
|
+
${"\u2500".repeat(60)}`);
|
|
448
|
+
console.log(`\uD30C\uC77C: \x1B[1m${path}\x1B[0m`);
|
|
449
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
450
|
+
const diff = resolver.generateDiff(conflict);
|
|
451
|
+
const diffLines = diff.split("\n");
|
|
452
|
+
for (const line of diffLines) {
|
|
453
|
+
if (line.startsWith("- ")) {
|
|
454
|
+
console.log(`\x1B[31m${line}\x1B[0m`);
|
|
455
|
+
} else if (line.startsWith("+ ")) {
|
|
456
|
+
console.log(`\x1B[32m${line}\x1B[0m`);
|
|
457
|
+
} else if (line.startsWith("---") || line.startsWith("+++")) {
|
|
458
|
+
console.log(`\x1B[1m${line}\x1B[0m`);
|
|
459
|
+
} else {
|
|
460
|
+
console.log(line);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const choice = await select({
|
|
464
|
+
message: "\uC5B4\uB5BB\uAC8C \uD574\uACB0\uD560\uAE4C\uC694?",
|
|
465
|
+
choices: [
|
|
466
|
+
{ name: "\uB85C\uCEEC \uC720\uC9C0 (Obsidian \uBC84\uC804 \uC720\uC9C0)", value: "local" },
|
|
467
|
+
{ name: "\uC6D0\uACA9 \uC720\uC9C0 (Notion \uBC84\uC804\uC73C\uB85C \uB36E\uC5B4\uC4F0\uAE30)", value: "remote" },
|
|
468
|
+
{ name: "\uC790\uB3D9 \uBCD1\uD569 (3-way merge)", value: "merge" },
|
|
469
|
+
{ name: "\uBCF5\uC81C (\uC6D0\uBCF8 \uC720\uC9C0 + .conflict \uD30C\uC77C \uC0DD\uC131)", value: "duplicate" }
|
|
470
|
+
]
|
|
471
|
+
});
|
|
472
|
+
const result = await resolver.resolve(conflict, choice);
|
|
473
|
+
if (result.success) {
|
|
474
|
+
console.log(`\x1B[32m\u2713 ${path} \u2192 ${choice}\uB85C \uD574\uACB0\x1B[0m`);
|
|
475
|
+
} else if (result.mergeHadConflicts) {
|
|
476
|
+
console.log(`\x1B[33m\u26A0 ${path} \u2192 \uC790\uB3D9 \uBCD1\uD569 \uC644\uB8CC (\uC218\uB3D9 \uD655\uC778 \uD544\uC694\uD55C \uCDA9\uB3CC \uB9C8\uCEE4 \uC788\uC74C)\x1B[0m`);
|
|
477
|
+
} else {
|
|
478
|
+
console.log(`\x1B[31m\u2717 ${path} \u2192 \uD574\uACB0 \uC2E4\uD328\x1B[0m`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/commands/watch.ts
|
|
483
|
+
import { Command as Command8 } from "commander";
|
|
484
|
+
import {
|
|
485
|
+
ConfigManager as ConfigManager8,
|
|
486
|
+
StateDB as StateDB7,
|
|
487
|
+
NotionClient as NotionClient8,
|
|
488
|
+
SyncOrchestrator as SyncOrchestrator7,
|
|
489
|
+
NodeVaultFS as NodeVaultFS7,
|
|
490
|
+
WatchSyncService
|
|
491
|
+
} from "@im-nobsidian/core";
|
|
492
|
+
function formatResult(result) {
|
|
493
|
+
const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;
|
|
494
|
+
const push = `Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}`;
|
|
495
|
+
const parts = [pull, push];
|
|
496
|
+
if (result.conflicts.length > 0) {
|
|
497
|
+
parts.push(`\uCDA9\uB3CC ${result.conflicts.length}\uAC74`);
|
|
498
|
+
}
|
|
499
|
+
return parts.join(" | ");
|
|
500
|
+
}
|
|
501
|
+
function timestamp() {
|
|
502
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("ko-KR", { hour12: false });
|
|
503
|
+
}
|
|
504
|
+
var watchCommand = new Command8("watch").description("\uD30C\uC77C \uBCC0\uACBD \uAC10\uC9C0 + \uC790\uB3D9 \uB3D9\uAE30\uD654").option("-d, --debounce <ms>", "\uB514\uBC14\uC6B4\uC2A4 \uB300\uAE30 \uC2DC\uAC04(ms)", "2000").option("-i, --interval <seconds>", "\uD480 \uB3D9\uAE30\uD654 \uC8FC\uAE30(\uCD08, 0\uC774\uBA74 \uBE44\uD65C\uC131)", "0").action(async (options) => {
|
|
505
|
+
const cwd = process.cwd();
|
|
506
|
+
const configManager = new ConfigManager8(cwd);
|
|
507
|
+
const config = await configManager.load();
|
|
508
|
+
const stateDb = StateDB7.open(configManager.dbPath);
|
|
509
|
+
const client = new NotionClient8({
|
|
510
|
+
token: config.notion.token,
|
|
511
|
+
concurrency: config.advanced.concurrency,
|
|
512
|
+
timeoutMs: config.advanced.timeoutMs
|
|
513
|
+
});
|
|
514
|
+
const vaultFs = new NodeVaultFS7(cwd, config.paths);
|
|
515
|
+
const orchestrator = new SyncOrchestrator7(config, stateDb, client, vaultFs);
|
|
516
|
+
const debounceMs = parseInt(options.debounce, 10);
|
|
517
|
+
const intervalSec = parseInt(options.interval, 10);
|
|
518
|
+
const service = new WatchSyncService(cwd, orchestrator, {
|
|
519
|
+
debounceMs,
|
|
520
|
+
onFileChange: (event, path) => {
|
|
521
|
+
console.log(`[${timestamp()}] ${event}: ${path}`);
|
|
522
|
+
},
|
|
523
|
+
onSyncStart: () => {
|
|
524
|
+
console.log(`[${timestamp()}] \uB3D9\uAE30\uD654 \uC2DC\uC791...`);
|
|
525
|
+
},
|
|
526
|
+
onSyncComplete: (result) => {
|
|
527
|
+
console.log(`[${timestamp()}] \uB3D9\uAE30\uD654 \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
528
|
+
},
|
|
529
|
+
onSyncError: (error) => {
|
|
530
|
+
console.error(`[${timestamp()}] \uB3D9\uAE30\uD654 \uC2E4\uD328: ${error.message}`);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
let pullTimer = null;
|
|
534
|
+
const shutdown = async () => {
|
|
535
|
+
console.log(`
|
|
536
|
+
[${timestamp()}] \uAC10\uC2DC \uC885\uB8CC \uC911...`);
|
|
537
|
+
if (pullTimer) clearInterval(pullTimer);
|
|
538
|
+
await service.stop();
|
|
539
|
+
stateDb.close();
|
|
540
|
+
console.log(`[${timestamp()}] \uC885\uB8CC \uC644\uB8CC`);
|
|
541
|
+
process.exit(0);
|
|
542
|
+
};
|
|
543
|
+
process.on("SIGINT", () => void shutdown());
|
|
544
|
+
process.on("SIGTERM", () => void shutdown());
|
|
545
|
+
service.start();
|
|
546
|
+
console.log(`[${timestamp()}] \uD30C\uC77C \uAC10\uC2DC \uC2DC\uC791 (debounce: ${debounceMs}ms)`);
|
|
547
|
+
if (intervalSec > 0) {
|
|
548
|
+
console.log(`[${timestamp()}] \uD480 \uB3D9\uAE30\uD654 \uC8FC\uAE30: ${intervalSec}\uCD08`);
|
|
549
|
+
pullTimer = setInterval(async () => {
|
|
550
|
+
if (service.isSyncing()) return;
|
|
551
|
+
try {
|
|
552
|
+
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2DC\uC791...`);
|
|
553
|
+
const result = await orchestrator.sync();
|
|
554
|
+
console.log(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC644\uB8CC \u2014 ${formatResult(result)}`);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
557
|
+
console.error(`[${timestamp()}] \uC8FC\uAE30\uC801 \uB3D9\uAE30\uD654 \uC2E4\uD328: ${msg}`);
|
|
558
|
+
}
|
|
559
|
+
}, intervalSec * 1e3);
|
|
560
|
+
}
|
|
561
|
+
await new Promise(() => {
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// src/index.ts
|
|
566
|
+
var program = new Command9();
|
|
567
|
+
program.name("im-nobsidian").description("Im-Nobsidian: Obsidian \u2194 Notion \uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 CLI").version("0.1.0").option("--verbose", "\uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825");
|
|
568
|
+
program.addCommand(initCommand);
|
|
569
|
+
program.addCommand(pushCommand);
|
|
570
|
+
program.addCommand(pullCommand);
|
|
571
|
+
program.addCommand(syncCommand);
|
|
572
|
+
program.addCommand(statusCommand);
|
|
573
|
+
program.addCommand(diffCommand);
|
|
574
|
+
program.addCommand(resolveCommand);
|
|
575
|
+
program.addCommand(watchCommand);
|
|
576
|
+
program.parse();
|
|
577
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/push.ts","../src/commands/pull.ts","../src/commands/sync.ts","../src/commands/status.ts","../src/commands/diff.ts","../src/commands/resolve.ts","../src/commands/watch.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { pushCommand } from \"./commands/push.js\";\nimport { pullCommand } from \"./commands/pull.js\";\nimport { syncCommand } from \"./commands/sync.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { diffCommand } from \"./commands/diff.js\";\nimport { resolveCommand } from \"./commands/resolve.js\";\nimport { watchCommand } from \"./commands/watch.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"im-nobsidian\")\n .description(\"Im-Nobsidian: Obsidian ↔ Notion 양방향 동기화 CLI\")\n .version(\"0.1.0\")\n .option(\"--verbose\", \"상세 로그 출력\")\n .option(\"--quiet\", \"최소 출력\");\n\nprogram.addCommand(initCommand);\nprogram.addCommand(pushCommand);\nprogram.addCommand(pullCommand);\nprogram.addCommand(syncCommand);\nprogram.addCommand(statusCommand);\nprogram.addCommand(diffCommand);\nprogram.addCommand(resolveCommand);\nprogram.addCommand(watchCommand);\n\nprogram.parse();\n","import { Command } from \"commander\";\nimport { ConfigManager, NotionClient } from \"@im-nobsidian/core\";\nimport { input, confirm } from \"@inquirer/prompts\";\nimport ora from \"ora\";\n\nexport const initCommand = new Command(\"init\")\n .description(\"Im-Nobsidian 초기 설정\")\n .option(\"--token <token>\", \"Notion Integration Token\")\n .option(\"--root-page-id <id>\", \"루트 페이지 ID\")\n .option(\"--non-interactive\", \"비대화형 모드\")\n .action(async (options: { token?: string; rootPageId?: string; nonInteractive?: boolean }) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n\n if (options.nonInteractive) {\n if (!options.token || !options.rootPageId) {\n console.error(\"비대화형 모드에서는 --token과 --root-page-id가 필수입니다.\");\n process.exitCode = 1;\n return;\n }\n\n if (!options.token.startsWith(\"ntn_\")) {\n console.error(\"토큰은 ntn_으로 시작해야 합니다.\");\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(\"Notion 연결 확인 중...\").start();\n try {\n const client = new NotionClient({ token: options.token });\n await client.search({ pageSize: 1, filter: { property: \"object\", value: \"page\" } });\n spinner.succeed(\"Notion 연결 성공\");\n\n await configManager.init({ token: options.token, rootPageId: options.rootPageId });\n console.log(\"\\n✓ Im-Nobsidian 초기화 완료!\");\n console.log(` 설정: ${configManager.configPath}`);\n console.log(` DB: ${configManager.dbPath}`);\n } catch (error) {\n spinner.fail(\"Notion 연결 실패\");\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n }\n return;\n }\n\n const isInit = await configManager.isInitialized();\n if (isInit) {\n const overwrite = await confirm({\n message: \"이미 초기화되어 있습니다. 다시 설정할까요?\",\n default: false,\n });\n if (!overwrite) return;\n }\n\n const token = await input({\n message: \"Notion Internal Integration Token:\",\n validate: (v) => v.startsWith(\"ntn_\") || \"ntn_으로 시작하는 토큰을 입력하세요\",\n });\n\n const spinner = ora(\"Notion 연결 확인 중...\").start();\n\n try {\n const client = new NotionClient({ token });\n const result = await client.search({\n pageSize: 10,\n filter: { property: \"object\", value: \"page\" },\n });\n spinner.succeed(\"Notion 연결 성공\");\n\n if (result.results.length === 0) {\n spinner.warn(\"접근 가능한 페이지가 없습니다. Integration에 페이지를 공유해주세요.\");\n return;\n }\n\n console.log(\"\\n사용 가능한 루트 페이지:\");\n for (const [i, page] of result.results.entries()) {\n const title = extractPageTitle(page);\n console.log(` [${i + 1}] ${title}`);\n }\n\n const rootPageId = await input({\n message: \"루트 페이지 ID (위에서 선택하거나 직접 입력):\",\n default: result.results[0]?.id,\n });\n\n await configManager.init({ token, rootPageId });\n console.log(\"\\n✓ Im-Nobsidian 초기화 완료!\");\n console.log(` 설정: ${configManager.configPath}`);\n console.log(` DB: ${configManager.dbPath}`);\n console.log(\"\\n다음 명령으로 동기화를 시작하세요:\");\n console.log(\" nobsi pull — Notion → 로컬\");\n console.log(\" nobsi push — 로컬 → Notion\");\n console.log(\" nobsi sync — 양방향\");\n } catch (error) {\n spinner.fail(\"Notion 연결 실패\");\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n }\n });\n\nfunction extractPageTitle(page: Record<string, unknown>): string {\n const properties = page[\"properties\"] as Record<string, unknown> | undefined;\n if (!properties) return \"(제목 없음)\";\n\n for (const prop of Object.values(properties)) {\n const p = prop as { type?: string; title?: Array<{ plain_text?: string }> };\n if (p.type === \"title\" && p.title?.[0]?.plain_text) {\n return p.title[0].plain_text;\n }\n }\n return \"(제목 없음)\";\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport ora from \"ora\";\n\nexport const pushCommand = new Command(\"push\")\n .description(\"로컬 변경사항을 Notion에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 push\")\n .action(async (options) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n try {\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n timeoutMs: config.advanced.timeoutMs,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const spinner = ora(\"Push 중...\").start();\n const result = await orchestrator.push({\n dryRun: options.dryRun,\n paths: options.path,\n onProgress: (current, total, path) => {\n const name = path.split(\"/\").pop() ?? path;\n spinner.text = `Push 중... [${current}/${total}] ${name}`;\n },\n });\n spinner.stop();\n\n console.log(`\\n✓ Push 완료 (${(result.duration / 1000).toFixed(1)}s)`);\n console.log(` 생성: ${result.created}`);\n console.log(` 수정: ${result.updated}`);\n console.log(` 삭제: ${result.deleted}`);\n\n if (result.failed.length > 0) {\n console.log(` 실패: ${result.failed.length}`);\n for (const f of result.failed) {\n console.log(` ✕ ${f.path}: ${f.error}`);\n }\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport ora from \"ora\";\n\nexport const pullCommand = new Command(\"pull\")\n .description(\"Notion 변경사항을 로컬에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 pull\")\n .action(async (options) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n try {\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n timeoutMs: config.advanced.timeoutMs,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const spinner = ora(\"Pull 중...\").start();\n const result = await orchestrator.pull({\n dryRun: options.dryRun,\n paths: options.path,\n onProgress: (current, total, _pageId) => {\n spinner.text = `Pull 중... [${current}/${total}]`;\n },\n });\n spinner.stop();\n\n console.log(`\\n✓ Pull 완료 (${(result.duration / 1000).toFixed(1)}s)`);\n console.log(` 생성: ${result.created}`);\n console.log(` 수정: ${result.updated}`);\n console.log(` 삭제: ${result.deleted}`);\n\n if (result.conflicts.length > 0) {\n console.log(` 충돌: ${result.conflicts.length}`);\n console.log(' \"nobsi resolve\"로 충돌을 해결하세요.');\n }\n\n if (result.failed.length > 0) {\n console.log(` 실패: ${result.failed.length}`);\n for (const f of result.failed) {\n console.log(` ✕ ${f.path}: ${f.error}`);\n }\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport ora from \"ora\";\n\nexport const syncCommand = new Command(\"sync\")\n .description(\"양방향 동기화 (Pull → Push)\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .action(async (options) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n try {\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n timeoutMs: config.advanced.timeoutMs,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const spinner = ora(\"동기화 중...\").start();\n const result = await orchestrator.sync({\n dryRun: options.dryRun,\n onProgress: (current, total, _item) => {\n spinner.text = `동기화 중... [${current}/${total}]`;\n },\n });\n spinner.stop();\n\n console.log(`\\n✓ 동기화 완료 (${(result.duration / 1000).toFixed(1)}s)`);\n console.log(\n ` Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`,\n );\n console.log(\n ` Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}`,\n );\n\n if (result.conflicts.length > 0) {\n console.log(`\\n ⚠ 충돌 ${result.conflicts.length}건`);\n console.log(' \"nobsi resolve\"로 충돌을 해결하세요.');\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\n\nexport const statusCommand = new Command(\"status\")\n .description(\"동기화 상태 확인\")\n .action(async () => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n try {\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = await orchestrator.status();\n\n if (status.lastSyncAt) {\n console.log(`마지막 동기화: ${status.lastSyncAt}`);\n } else {\n console.log(\"아직 동기화된 적 없음\");\n }\n\n console.log(\"\");\n\n if (status.localChanges.length === 0) {\n console.log(\"변경사항 없음 (clean)\");\n } else {\n console.log(`로컬 변경사항 (${status.localChanges.length}건):`);\n for (const change of status.localChanges) {\n const prefix = change.type === \"created\" ? \"+\" : change.type === \"deleted\" ? \"-\" : \"~\";\n console.log(` ${prefix} ${change.path}`);\n }\n }\n\n if (status.conflictRecords.length > 0) {\n console.log(`\\n충돌 (${status.conflictRecords.length}건):`);\n for (const record of status.conflictRecords) {\n console.log(` ⚠ ${record.obsidianPath}`);\n }\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport { createTwoFilesPatch } from \"diff\";\n\nexport const diffCommand = new Command(\"diff\")\n .description(\"로컬과 Notion 간의 차이 표시\")\n .argument(\"[path]\", \"특정 파일 경로 (생략 시 변경된 모든 파일)\")\n .option(\"--color\", \"컬러 출력\", true)\n .action(async (targetPath: string | undefined, options) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n try {\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n timeoutMs: config.advanced.timeoutMs,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = await orchestrator.status();\n\n const changes = targetPath\n ? status.localChanges.filter((c) => c.path === targetPath || c.path.startsWith(targetPath))\n : status.localChanges;\n\n if (changes.length === 0) {\n console.log(\"변경사항 없음\");\n return;\n }\n\n for (const change of changes) {\n const record = stateDb.getByPath(change.path);\n\n if (change.type === \"created\") {\n const content = await vaultFs.readFile(change.path);\n const patch = createTwoFilesPatch(`/dev/null`, `b/${change.path}`, \"\", content);\n printPatch(patch, options.color);\n } else if (change.type === \"deleted\") {\n if (record?.baseSnapshot) {\n const oldContent = record.baseSnapshot.toString(\"utf-8\");\n const patch = createTwoFilesPatch(`a/${change.path}`, `/dev/null`, oldContent, \"\");\n printPatch(patch, options.color);\n } else {\n console.log(`--- a/${change.path}`);\n console.log(`+++ /dev/null`);\n console.log(`(이전 스냅샷 없음)`);\n }\n } else if (change.type === \"modified\" && record?.baseSnapshot) {\n const currentContent = await vaultFs.readFile(change.path);\n const baseContent = record.baseSnapshot.toString(\"utf-8\");\n const patch = createTwoFilesPatch(\n `a/${change.path}`,\n `b/${change.path}`,\n baseContent,\n currentContent,\n );\n printPatch(patch, options.color);\n }\n\n console.log(\"\");\n }\n } finally {\n stateDb.close();\n }\n });\n\nfunction printPatch(patch: string, color: boolean): void {\n for (const line of patch.split(\"\\n\")) {\n if (!color) {\n console.log(line);\n continue;\n }\n\n if (line.startsWith(\"+++\") || line.startsWith(\"---\")) {\n console.log(`\\x1b[1m${line}\\x1b[0m`);\n } else if (line.startsWith(\"+\")) {\n console.log(`\\x1b[32m${line}\\x1b[0m`);\n } else if (line.startsWith(\"-\")) {\n console.log(`\\x1b[31m${line}\\x1b[0m`);\n } else if (line.startsWith(\"@@\")) {\n console.log(`\\x1b[36m${line}\\x1b[0m`);\n } else {\n console.log(line);\n }\n }\n}\n","import { Command } from \"commander\";\nimport { select, confirm } from \"@inquirer/prompts\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n ConflictResolver,\n} from \"@im-nobsidian/core\";\nimport type { Conflict, ResolutionChoice } from \"@im-nobsidian/core\";\n\nexport const resolveCommand = new Command(\"resolve\")\n .description(\"충돌을 해결합니다\")\n .option(\"-s, --strategy <strategy>\", \"자동 해결 전략 (local-first, remote-first, duplicate)\")\n .action(async (options: { strategy?: string }) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n\n try {\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: 3,\n timeoutMs: 30000,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n const resolver = new ConflictResolver(stateDb, vaultFs);\n\n await orchestrator.status();\n const conflictRecords = stateDb.getByStatus(\"conflict\");\n\n if (conflictRecords.length === 0) {\n console.log(\"충돌이 없습니다.\");\n stateDb.close();\n return;\n }\n\n console.log(`\\n충돌 ${conflictRecords.length}건 발견\\n`);\n\n if (options.strategy) {\n const strategy = options.strategy as \"local-first\" | \"remote-first\" | \"duplicate\";\n const shouldProceed = await confirm({\n message: `${conflictRecords.length}건의 충돌을 '${strategy}' 전략으로 일괄 해결할까요?`,\n });\n\n if (!shouldProceed) {\n stateDb.close();\n return;\n }\n\n const pullResult = await orchestrator.pull();\n const results = await resolver.resolveAll(pullResult.conflicts, strategy);\n\n for (const result of results) {\n const icon = result.success ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[31m✗\\x1b[0m\";\n console.log(` ${icon} ${result.path} → ${result.choice}`);\n }\n } else {\n const pullResult = await orchestrator.pull();\n\n for (const conflict of pullResult.conflicts) {\n await resolveInteractive(conflict, resolver);\n }\n }\n\n stateDb.close();\n console.log(\"\\n충돌 해결 완료\");\n } catch (error) {\n console.error(\"오류:\", error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n });\n\nasync function resolveInteractive(conflict: Conflict, resolver: ConflictResolver): Promise<void> {\n const path = conflict.syncRecord.obsidianPath;\n\n console.log(`\\n${\"─\".repeat(60)}`);\n console.log(`파일: \\x1b[1m${path}\\x1b[0m`);\n console.log(`${\"─\".repeat(60)}`);\n\n const diff = resolver.generateDiff(conflict);\n const diffLines = diff.split(\"\\n\");\n for (const line of diffLines) {\n if (line.startsWith(\"- \")) {\n console.log(`\\x1b[31m${line}\\x1b[0m`);\n } else if (line.startsWith(\"+ \")) {\n console.log(`\\x1b[32m${line}\\x1b[0m`);\n } else if (line.startsWith(\"---\") || line.startsWith(\"+++\")) {\n console.log(`\\x1b[1m${line}\\x1b[0m`);\n } else {\n console.log(line);\n }\n }\n\n const choice = await select<ResolutionChoice>({\n message: \"어떻게 해결할까요?\",\n choices: [\n { name: \"로컬 유지 (Obsidian 버전 유지)\", value: \"local\" },\n { name: \"원격 유지 (Notion 버전으로 덮어쓰기)\", value: \"remote\" },\n { name: \"자동 병합 (3-way merge)\", value: \"merge\" },\n { name: \"복제 (원본 유지 + .conflict 파일 생성)\", value: \"duplicate\" },\n ],\n });\n\n const result = await resolver.resolve(conflict, choice);\n\n if (result.success) {\n console.log(`\\x1b[32m✓ ${path} → ${choice}로 해결\\x1b[0m`);\n } else if (result.mergeHadConflicts) {\n console.log(`\\x1b[33m⚠ ${path} → 자동 병합 완료 (수동 확인 필요한 충돌 마커 있음)\\x1b[0m`);\n } else {\n console.log(`\\x1b[31m✗ ${path} → 해결 실패\\x1b[0m`);\n }\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n WatchSyncService,\n} from \"@im-nobsidian/core\";\nimport type { SyncResult } from \"@im-nobsidian/core\";\n\nfunction formatResult(result: SyncResult): string {\n const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;\n const push = `Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}`;\n const parts = [pull, push];\n\n if (result.conflicts.length > 0) {\n parts.push(`충돌 ${result.conflicts.length}건`);\n }\n\n return parts.join(\" | \");\n}\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString(\"ko-KR\", { hour12: false });\n}\n\nexport const watchCommand = new Command(\"watch\")\n .description(\"파일 변경 감지 + 자동 동기화\")\n .option(\"-d, --debounce <ms>\", \"디바운스 대기 시간(ms)\", \"2000\")\n .option(\"-i, --interval <seconds>\", \"풀 동기화 주기(초, 0이면 비활성)\", \"0\")\n .action(async (options: { debounce: string; interval: string }) => {\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n const config = await configManager.load();\n const stateDb = StateDB.open(configManager.dbPath);\n\n const client = new NotionClient({\n token: config.notion.token,\n concurrency: config.advanced.concurrency,\n timeoutMs: config.advanced.timeoutMs,\n });\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const debounceMs = parseInt(options.debounce, 10);\n const intervalSec = parseInt(options.interval, 10);\n\n const service = new WatchSyncService(cwd, orchestrator, {\n debounceMs,\n onFileChange: (event, path) => {\n console.log(`[${timestamp()}] ${event}: ${path}`);\n },\n onSyncStart: () => {\n console.log(`[${timestamp()}] 동기화 시작...`);\n },\n onSyncComplete: (result) => {\n console.log(`[${timestamp()}] 동기화 완료 — ${formatResult(result)}`);\n },\n onSyncError: (error) => {\n console.error(`[${timestamp()}] 동기화 실패: ${error.message}`);\n },\n });\n\n let pullTimer: ReturnType<typeof setInterval> | null = null;\n\n const shutdown = async (): Promise<void> => {\n console.log(`\\n[${timestamp()}] 감시 종료 중...`);\n if (pullTimer) clearInterval(pullTimer);\n await service.stop();\n stateDb.close();\n console.log(`[${timestamp()}] 종료 완료`);\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => void shutdown());\n process.on(\"SIGTERM\", () => void shutdown());\n\n service.start();\n console.log(`[${timestamp()}] 파일 감시 시작 (debounce: ${debounceMs}ms)`);\n\n if (intervalSec > 0) {\n console.log(`[${timestamp()}] 풀 동기화 주기: ${intervalSec}초`);\n pullTimer = setInterval(async () => {\n if (service.isSyncing()) return;\n try {\n console.log(`[${timestamp()}] 주기적 동기화 시작...`);\n const result = await orchestrator.sync();\n console.log(`[${timestamp()}] 주기적 동기화 완료 — ${formatResult(result)}`);\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[${timestamp()}] 주기적 동기화 실패: ${msg}`);\n }\n }, intervalSec * 1000);\n }\n\n await new Promise(() => {});\n });\n"],"mappings":"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","names":["Command","spinner","Command","ConfigManager","NotionClient","ora","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","ora","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","ora","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","confirm","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "im-nobsidian",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for Obsidian ↔ Notion bidirectional sync",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nobsi": "./dist/index.js",
|
|
8
|
+
"im-nobsidian": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@inquirer/prompts": "^7.0.0",
|
|
15
|
+
"commander": "^12.0.0",
|
|
16
|
+
"diff": "^9.0.0",
|
|
17
|
+
"ora": "^8.0.0",
|
|
18
|
+
"@im-nobsidian/core": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/diff": "^8.0.0",
|
|
22
|
+
"tsup": "^8.0.0",
|
|
23
|
+
"typescript": "^5.6.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/JaylenAI/Im-Nobsidian",
|
|
28
|
+
"directory": "packages/cli"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"obsidian",
|
|
32
|
+
"notion",
|
|
33
|
+
"sync",
|
|
34
|
+
"cli"
|
|
35
|
+
],
|
|
36
|
+
"author": "hanseungheon",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/JaylenAI/Im-Nobsidian/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/JaylenAI/Im-Nobsidian#readme",
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest",
|
|
50
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
51
|
+
}
|
|
52
|
+
}
|