im-nobsidian 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { createRequire } from "module";
5
- import { Command as Command10 } from "commander";
5
+ import { Command as Command11 } from "commander";
6
6
 
7
7
  // src/commands/init.ts
8
8
  import { Command } from "commander";
@@ -52,6 +52,12 @@ function summary(created, updated, deleted) {
52
52
  if (parts.length === 0) parts.push(chalk.dim("no changes"));
53
53
  return parts.join(" ");
54
54
  }
55
+ function completionHeader(label, failedCount) {
56
+ return failedCount > 0 ? header(chalk.yellow(`${label} finished with errors`)) : header(chalk.green(`${label} complete`));
57
+ }
58
+ function failedCountText(count) {
59
+ return count > 0 ? chalk.red(`${count} failed`) : chalk.dim("0 failed");
60
+ }
55
61
  function duration(ms) {
56
62
  return chalk.dim(`Done in ${(ms / 1e3).toFixed(1)}s`);
57
63
  }
@@ -177,7 +183,6 @@ import {
177
183
  SyncOrchestrator,
178
184
  NodeVaultFS
179
185
  } from "@im-nobsidian/core";
180
- import chalk3 from "chalk";
181
186
  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) => {
182
187
  const cwd = process.cwd();
183
188
  const configManager = new ConfigManager2(cwd);
@@ -201,17 +206,19 @@ ${header(" Pushing to Notion...")}`);
201
206
  console.log(` ${icon} ${item.path} ${prog} ${label}`);
202
207
  }
203
208
  });
209
+ const failedCount = result.failed.length;
204
210
  console.log(`
205
- ${header(chalk3.green("Push complete"))}`);
211
+ ${completionHeader("Push", failedCount)}`);
206
212
  console.log(
207
- ` ${summary(result.created, result.updated, result.deleted)} ${dimText(`${result.failed.length} failed`)}`
213
+ ` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`
208
214
  );
209
215
  console.log(` ${duration(result.duration)}`);
210
- if (result.failed.length > 0) {
216
+ if (failedCount > 0) {
211
217
  console.log("");
212
218
  for (const f of result.failed) {
213
219
  failedItem(f.path, f.error);
214
220
  }
221
+ process.exitCode = 1;
215
222
  }
216
223
  } finally {
217
224
  stateDb.close();
@@ -227,7 +234,7 @@ import {
227
234
  SyncOrchestrator as SyncOrchestrator2,
228
235
  NodeVaultFS as NodeVaultFS2
229
236
  } from "@im-nobsidian/core";
230
- import chalk4 from "chalk";
237
+ import chalk3 from "chalk";
231
238
  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").option(
232
239
  "--force",
233
240
  "\uC99D\uBD84 \uAC10\uC9C0\uB97C \uAC74\uB108\uB6F0\uACE0 \uC804\uCCB4 \uC2A4\uCE94 (search \uC778\uB371\uC2F1 \uC9C0\uC5F0\uC73C\uB85C \uB204\uB77D\uB41C \uC2E0\uADDC \uD398\uC774\uC9C0\xB7DB \uBCF5\uAD6C)"
@@ -267,23 +274,28 @@ ${header(" Pulling from Notion...")}`);
267
274
  if (result.linkCount > 0) {
268
275
  console.log(` ${icons.success} ${result.linkCount} links resolved`);
269
276
  }
277
+ const failedCount = result.failed.length;
270
278
  console.log(`
271
- ${header(chalk4.green("Pull complete"))}`);
279
+ ${completionHeader("Pull", failedCount)}`);
272
280
  console.log(
273
- ` ${summary(result.created, result.updated, result.deleted)} ${dimText(`${result.failed.length} failed`)}`
281
+ ` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`
274
282
  );
283
+ if (result.restored > 0) {
284
+ console.log(` ${icons.success} ${chalk3.yellow(`${result.restored} restored`)}`);
285
+ }
275
286
  console.log(` ${duration(result.duration)}`);
276
287
  if (result.conflicts.length > 0) {
277
288
  console.log(
278
289
  `
279
- ${icons.conflict} ${chalk4.magenta(`${result.conflicts.length} conflicts`)} \u2014 run ${chalk4.cyan("nobsi resolve")}`
290
+ ${icons.conflict} ${chalk3.magenta(`${result.conflicts.length} conflicts`)} \u2014 run ${chalk3.cyan("nobsi resolve")}`
280
291
  );
281
292
  }
282
- if (result.failed.length > 0) {
293
+ if (failedCount > 0) {
283
294
  console.log("");
284
295
  for (const f of result.failed) {
285
296
  failedItem(f.path, f.error);
286
297
  }
298
+ process.exitCode = 1;
287
299
  }
288
300
  } finally {
289
301
  stateDb.close();
@@ -299,7 +311,7 @@ import {
299
311
  SyncOrchestrator as SyncOrchestrator3,
300
312
  NodeVaultFS as NodeVaultFS3
301
313
  } from "@im-nobsidian/core";
302
- import chalk5 from "chalk";
314
+ import chalk4 from "chalk";
303
315
  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) => {
304
316
  const cwd = process.cwd();
305
317
  const configManager = new ConfigManager4(cwd);
@@ -314,7 +326,7 @@ ${header(" Bidirectional Sync")}`);
314
326
  console.log(` ${separator()}`);
315
327
  if (options.dryRun) console.log(dimText(" (dry-run mode)"));
316
328
  console.log(`
317
- ${header(chalk5.blue("\u25BC Pull"))} ${dimText("(Notion \u2192 Obsidian)")}`);
329
+ ${header(chalk4.blue("\u25BC Pull"))} ${dimText("(Notion \u2192 Obsidian)")}`);
318
330
  const pullResult = await orchestrator.pull({
319
331
  dryRun: options.dryRun,
320
332
  onProgress: (_current, _total, item) => {
@@ -330,7 +342,7 @@ ${header(" Bidirectional Sync")}`);
330
342
  console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);
331
343
  }
332
344
  console.log(`
333
- ${header(chalk5.magenta("\u25B2 Push"))} ${dimText("(Obsidian \u2192 Notion)")}`);
345
+ ${header(chalk4.magenta("\u25B2 Push"))} ${dimText("(Obsidian \u2192 Notion)")}`);
334
346
  const pushResult = await orchestrator.push({
335
347
  dryRun: options.dryRun,
336
348
  onProgress: (_current, _total, item) => {
@@ -340,21 +352,30 @@ ${header(" Bidirectional Sync")}`);
340
352
  }
341
353
  });
342
354
  const totalDuration = pullResult.duration + pushResult.duration;
355
+ const pullFailed = pullResult.failed.length;
356
+ const pushFailed = pushResult.failed.length;
343
357
  console.log(`
344
- ${header(chalk5.green("Sync complete"))}`);
358
+ ${completionHeader("Sync", pullFailed + pushFailed)}`);
345
359
  console.log(
346
- ` ${chalk5.blue("Pull:")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}`
360
+ ` ${chalk4.blue("Pull:")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}` + // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.
361
+ (pullResult.restored > 0 ? ` ${chalk4.yellow(`${pullResult.restored} restored`)}` : "") + (pullFailed > 0 ? ` ${chalk4.red(`${pullFailed} failed`)}` : "")
347
362
  );
348
363
  console.log(
349
- ` ${chalk5.magenta("Push:")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted)}`
364
+ ` ${chalk4.magenta("Push:")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted)}` + (pushFailed > 0 ? ` ${chalk4.red(`${pushFailed} failed`)}` : "")
350
365
  );
351
366
  console.log(` ${duration(totalDuration)}`);
352
367
  if (pullResult.conflicts.length > 0) {
353
368
  console.log(
354
369
  `
355
- ${icons.conflict} ${chalk5.magenta(`${pullResult.conflicts.length} conflicts`)} \u2014 run ${chalk5.cyan("nobsi resolve")}`
370
+ ${icons.conflict} ${chalk4.magenta(`${pullResult.conflicts.length} conflicts`)} \u2014 run ${chalk4.cyan("nobsi resolve")}`
356
371
  );
357
372
  }
373
+ if (pullFailed + pushFailed > 0) {
374
+ console.log("");
375
+ for (const f of pullResult.failed) failedItem(`\u25BC ${f.path}`, f.error);
376
+ for (const f of pushResult.failed) failedItem(`\u25B2 ${f.path}`, f.error);
377
+ process.exitCode = 1;
378
+ }
358
379
  } finally {
359
380
  stateDb.close();
360
381
  }
@@ -369,7 +390,7 @@ import {
369
390
  SyncOrchestrator as SyncOrchestrator4,
370
391
  NodeVaultFS as NodeVaultFS4
371
392
  } from "@im-nobsidian/core";
372
- import chalk6 from "chalk";
393
+ import chalk5 from "chalk";
373
394
  var statusCommand = new Command5("status").description("\uB3D9\uAE30\uD654 \uC0C1\uD0DC \uD655\uC778 (--full: \uC6D0\uACA9 \uBCC0\uACBD \uD3EC\uD568)").option("--full", "Notion \uC6D0\uACA9 \uBCC0\uACBD\uAE4C\uC9C0 \uC591\uBC29\uD5A5 \uD655\uC778 (API \uD638\uCD9C, \uB290\uB9BC)").action(async (opts) => {
374
395
  const cwd = process.cwd();
375
396
  const configManager = new ConfigManager5(cwd);
@@ -392,35 +413,48 @@ ${header(" Sync Status")}`);
392
413
  const iso = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0") + " " + String(date.getHours()).padStart(2, "0") + ":" + String(date.getMinutes()).padStart(2, "0") + ":" + String(date.getSeconds()).padStart(2, "0");
393
414
  console.log(` ${dimText("Last sync:")} ${iso}`);
394
415
  } else {
395
- console.log(` ${dimText("Last sync:")} ${chalk6.yellow("never")}`);
416
+ console.log(` ${dimText("Last sync:")} ${chalk5.yellow("never")}`);
396
417
  }
397
418
  const allRecords = stateDb.getAll();
398
419
  const total = allRecords.length;
399
- const synced = allRecords.filter((r) => r.status === "synced").length;
400
420
  const conflictCount = status.conflictRecords.length;
401
421
  const createdChanges = status.localChanges.filter((c) => c.type === "created");
402
422
  const modifiedChanges = status.localChanges.filter((c) => c.type === "modified");
423
+ const deletedChanges = status.localChanges.filter((c) => c.type === "deleted");
424
+ const dirtyPaths = /* @__PURE__ */ new Set([
425
+ ...modifiedChanges.map((c) => c.path),
426
+ ...deletedChanges.map((c) => c.path),
427
+ ...status.conflictRecords.map((r) => r.obsidianPath)
428
+ ]);
429
+ const synced = allRecords.filter(
430
+ (r) => r.status === "synced" && !dirtyPaths.has(r.obsidianPath)
431
+ ).length;
403
432
  const remoteCreated = status.remoteChanges.filter((c) => c.type === "created");
404
433
  const remoteModified = status.remoteChanges.filter((c) => c.type === "modified");
405
434
  const remoteDeleted = status.remoteChanges.filter((c) => c.type === "deleted");
406
435
  console.log(`
407
436
  ${header(`Tracked files: ${total}`)}`);
408
- console.log(` ${icons.synced} ${chalk6.green("synced")} ${synced}`);
437
+ console.log(` ${icons.synced} ${chalk5.green("synced")} ${synced}`);
409
438
  if (modifiedChanges.length > 0) {
410
- console.log(` ${icons.modified} ${chalk6.yellow("modified")} ${modifiedChanges.length}`);
439
+ console.log(` ${icons.modified} ${chalk5.yellow("modified")} ${modifiedChanges.length}`);
411
440
  }
412
- if (createdChanges.length > 0) {
413
- console.log(` ${icons.newFile} ${chalk6.cyan("new")} ${createdChanges.length}`);
441
+ if (deletedChanges.length > 0) {
442
+ console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${deletedChanges.length}`);
414
443
  }
415
444
  if (conflictCount > 0) {
416
- console.log(` ${icons.conflictDot} ${chalk6.magenta("conflict")} ${conflictCount}`);
445
+ console.log(` ${icons.conflictDot} ${chalk5.magenta("conflict")} ${conflictCount}`);
446
+ }
447
+ if (createdChanges.length > 0) {
448
+ console.log(
449
+ ` ${icons.newFile} ${chalk5.cyan("new")} ${createdChanges.length} ${dimText("(untracked)")}`
450
+ );
417
451
  }
418
452
  if (modifiedChanges.length > 0) {
419
453
  console.log(`
420
454
  ${header("Modified files:")}`);
421
455
  for (const c of modifiedChanges.slice(0, 10)) {
422
456
  console.log(
423
- ` ${chalk6.yellow("~")} ${c.path.padEnd(30)} ${dimText("(local changed)")}`
457
+ ` ${chalk5.yellow("~")} ${c.path.padEnd(30)} ${dimText("(local changed)")}`
424
458
  );
425
459
  }
426
460
  if (modifiedChanges.length > 10) {
@@ -431,7 +465,7 @@ ${header(" Sync Status")}`);
431
465
  console.log(`
432
466
  ${header("New files:")}`);
433
467
  for (const c of createdChanges.slice(0, 10)) {
434
- console.log(` ${chalk6.cyan("+")} ${c.path.padEnd(30)} ${dimText("(untracked)")}`);
468
+ console.log(` ${chalk5.cyan("+")} ${c.path.padEnd(30)} ${dimText("(untracked)")}`);
435
469
  }
436
470
  if (createdChanges.length > 10) {
437
471
  console.log(dimText(` ... and ${createdChanges.length - 10} more`));
@@ -441,18 +475,18 @@ ${header(" Sync Status")}`);
441
475
  console.log(`
442
476
  ${header(`Remote changes (Notion): ${status.remoteChanges.length}`)}`);
443
477
  if (remoteCreated.length > 0) {
444
- console.log(` ${chalk6.cyan("+")} ${chalk6.cyan("new")} ${remoteCreated.length}`);
478
+ console.log(` ${chalk5.cyan("+")} ${chalk5.cyan("new")} ${remoteCreated.length}`);
445
479
  }
446
480
  if (remoteModified.length > 0) {
447
481
  console.log(
448
- ` ${chalk6.yellow("~")} ${chalk6.yellow("modified")} ${remoteModified.length}`
482
+ ` ${chalk5.yellow("~")} ${chalk5.yellow("modified")} ${remoteModified.length}`
449
483
  );
450
484
  }
451
485
  if (remoteDeleted.length > 0) {
452
- console.log(` ${chalk6.red("-")} ${chalk6.red("deleted")} ${remoteDeleted.length}`);
486
+ console.log(` ${chalk5.red("-")} ${chalk5.red("deleted")} ${remoteDeleted.length}`);
453
487
  }
454
488
  for (const c of status.remoteChanges.slice(0, 10)) {
455
- const icon = c.type === "created" ? chalk6.cyan("+") : c.type === "modified" ? chalk6.yellow("~") : chalk6.red("-");
489
+ const icon = c.type === "created" ? chalk5.cyan("+") : c.type === "modified" ? chalk5.yellow("~") : chalk5.red("-");
456
490
  console.log(` ${icon} ${c.pageId.slice(0, 8)}... ${dimText(`(${c.type})`)}`);
457
491
  }
458
492
  if (status.remoteChanges.length > 10) {
@@ -461,7 +495,7 @@ ${header(" Sync Status")}`);
461
495
  }
462
496
  if (conflictCount > 0) {
463
497
  console.log(`
464
- ${header(chalk6.magenta("Conflicts:"))}`);
498
+ ${header(chalk5.magenta("Conflicts:"))}`);
465
499
  for (const r of status.conflictRecords) {
466
500
  console.log(
467
501
  ` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText("(both sides changed)")}`
@@ -473,20 +507,20 @@ ${header(" Sync Status")}`);
473
507
  console.log("");
474
508
  if (conflictCount > 0) {
475
509
  console.log(
476
- ` ${dimText("Run")} ${chalk6.cyan("nobsi resolve")} ${dimText("to resolve conflicts")}`
510
+ ` ${dimText("Run")} ${chalk5.cyan("nobsi resolve")} ${dimText("to resolve conflicts")}`
477
511
  );
478
512
  }
479
513
  console.log(
480
- ` ${dimText("Run")} ${chalk6.cyan("nobsi sync")} ${dimText("to push/pull changes")}`
514
+ ` ${dimText("Run")} ${chalk5.cyan("nobsi sync")} ${dimText("to push/pull changes")}`
481
515
  );
482
516
  } else {
483
517
  console.log(`
484
- ${chalk6.green("Everything up to date")} \u2713`);
518
+ ${chalk5.green("Everything up to date")} \u2713`);
485
519
  }
486
520
  if (!opts.full) {
487
521
  console.log(
488
522
  `
489
- ${dimText("Run")} ${chalk6.cyan("nobsi status --full")} ${dimText("to check Notion remote changes")}`
523
+ ${dimText("Run")} ${chalk5.cyan("nobsi status --full")} ${dimText("to check Notion remote changes")}`
490
524
  );
491
525
  }
492
526
  } finally {
@@ -501,10 +535,13 @@ import {
501
535
  StateDB as StateDB5,
502
536
  NotionClient as NotionClient6,
503
537
  SyncOrchestrator as SyncOrchestrator5,
504
- NodeVaultFS as NodeVaultFS5
538
+ NodeVaultFS as NodeVaultFS5,
539
+ matchesPathScope
505
540
  } from "@im-nobsidian/core";
506
541
  import { createTwoFilesPatch } from "diff";
507
- 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) => {
542
+ var diffCommand = new Command6("diff").description(
543
+ "\uB85C\uCEEC \uBCC0\uACBD\uC0AC\uD56D \uD45C\uC2DC (\uAE30\uBCF8: \uB9C8\uC9C0\uB9C9 \uB3D9\uAE30\uD654 \uC2DC\uC810\uACFC \uBE44\uAD50, --remote: Notion \uD604\uC7AC\uBCF8\uACFC \uBE44\uAD50)"
544
+ ).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).option("--remote", "Notion \uD604\uC7AC \uBCF8\uBB38\uACFC \uBE44\uAD50 (API \uD638\uCD9C, \uB290\uB9BC)").action(async (targetPath, options) => {
508
545
  const cwd = process.cwd();
509
546
  const configManager = new ConfigManager6(cwd);
510
547
  const config = await configManager.load();
@@ -514,13 +551,33 @@ var diffCommand = new Command6("diff").description("\uB85C\uCEEC\uACFC Notion \u
514
551
  const vaultFs = new NodeVaultFS5(cwd, config.paths);
515
552
  const orchestrator = new SyncOrchestrator5(config, stateDb, client, vaultFs);
516
553
  const status = await orchestrator.statusLocal();
517
- const changes = targetPath ? status.localChanges.filter((c) => c.path === targetPath || c.path.startsWith(targetPath)) : status.localChanges;
554
+ const changes = targetPath ? status.localChanges.filter((c) => matchesPathScope(c.path, targetPath)) : status.localChanges;
518
555
  if (changes.length === 0) {
519
556
  console.log("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C");
520
557
  return;
521
558
  }
522
559
  for (const change of changes) {
523
560
  const record = stateDb.getByPath(change.path);
561
+ if (options.remote && change.type !== "deleted") {
562
+ const remote = await orchestrator.renderRemoteSnapshot(change.path);
563
+ if (remote === null) {
564
+ console.log(`--- (\uC6D0\uACA9 \uC5C6\uC74C) ${change.path}`);
565
+ console.log(`+++ b/${change.path}`);
566
+ console.log(`(\uC544\uC9C1 Notion \uC5D0 \uC5C6\uB294 \uD30C\uC77C\uC785\uB2C8\uB2E4)`);
567
+ console.log("");
568
+ continue;
569
+ }
570
+ const currentContent = await vaultFs.readFile(change.path);
571
+ const patch = createTwoFilesPatch(
572
+ `notion/${change.path}`,
573
+ `b/${change.path}`,
574
+ remote,
575
+ currentContent
576
+ );
577
+ printPatch(patch, options.color, `notion/${change.path}`, `b/${change.path}`);
578
+ console.log("");
579
+ continue;
580
+ }
524
581
  if (change.type === "created") {
525
582
  const content = await vaultFs.readFile(change.path);
526
583
  const patch = createTwoFilesPatch("/dev/null", `b/${change.path}`, "", content);
@@ -589,7 +646,7 @@ import {
589
646
  SyncOrchestrator as SyncOrchestrator6,
590
647
  NodeVaultFS as NodeVaultFS6
591
648
  } from "@im-nobsidian/core";
592
- import chalk7 from "chalk";
649
+ import chalk6 from "chalk";
593
650
  var fetchCommand = new Command7("fetch").description("Notion \uB9AC\uBAA8\uD2B8 \uC0C1\uD0DC \uC2A4\uCE94 (\uC0AD\uC81C \uAC10\uC9C0 \uD3EC\uD568)").action(async () => {
594
651
  const cwd = process.cwd();
595
652
  const configManager = new ConfigManager7(cwd);
@@ -605,21 +662,21 @@ var fetchCommand = new Command7("fetch").description("Notion \uB9AC\uBAA8\uD2B8
605
662
  ${header(" Remote Scan")}`);
606
663
  console.log(` ${separator(40)}`);
607
664
  if (result.newPages > 0) {
608
- console.log(` ${chalk7.cyan("+")} New pages: ${result.newPages}`);
665
+ console.log(` ${chalk6.cyan("+")} New pages: ${result.newPages}`);
609
666
  }
610
667
  if (result.modifiedPages > 0) {
611
- console.log(` ${chalk7.yellow("~")} Modified pages: ${result.modifiedPages}`);
668
+ console.log(` ${chalk6.yellow("~")} Modified pages: ${result.modifiedPages}`);
612
669
  }
613
670
  if (result.deletedPages > 0) {
614
- console.log(` ${chalk7.red("-")} Deleted pages: ${result.deletedPages}`);
671
+ console.log(` ${chalk6.red("-")} Deleted pages: ${result.deletedPages}`);
615
672
  }
616
673
  const total = result.newPages + result.modifiedPages + result.deletedPages;
617
674
  if (total === 0) {
618
- console.log(` ${chalk7.green("Remote is up to date")} \u2713`);
675
+ console.log(` ${chalk6.green("Remote is up to date")} \u2713`);
619
676
  } else {
620
677
  console.log(
621
678
  `
622
- ${dimText("Run")} ${chalk7.cyan("nobsi pull")} ${dimText("to apply remote changes")}`
679
+ ${dimText("Run")} ${chalk6.cyan("nobsi pull")} ${dimText("to apply remote changes")}`
623
680
  );
624
681
  }
625
682
  console.log(dimText(`
@@ -639,53 +696,99 @@ import {
639
696
  SyncOrchestrator as SyncOrchestrator7,
640
697
  NodeVaultFS as NodeVaultFS7
641
698
  } from "@im-nobsidian/core";
642
- var resolveCommand = new Command8("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) => {
699
+ var STRATEGIES = ["local-first", "remote-first", "duplicate"];
700
+ function isInteractive() {
701
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
702
+ }
703
+ var resolveCommand = new Command8("resolve").description("\uCDA9\uB3CC\uC744 \uD574\uACB0\uD569\uB2C8\uB2E4").option("-s, --strategy <strategy>", `\uC790\uB3D9 \uD574\uACB0 \uC804\uB7B5 (${STRATEGIES.join(" | ")})`).option("-y, --yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC9C4\uD589 (\uBE44\uB300\uD654\uD615/CI)").action(async (options) => {
704
+ if (options.strategy && !STRATEGIES.includes(options.strategy)) {
705
+ console.error(`\uC54C \uC218 \uC5C6\uB294 \uC804\uB7B5 '${options.strategy}' \u2014 \uC0AC\uC6A9 \uAC00\uB2A5: ${STRATEGIES.join(", ")}`);
706
+ process.exitCode = 1;
707
+ return;
708
+ }
643
709
  const cwd = process.cwd();
644
710
  const configManager = new ConfigManager8(cwd);
711
+ let stateDb;
645
712
  try {
646
713
  const config = await configManager.load();
647
- const stateDb = StateDB7.open(configManager.dbPath);
714
+ stateDb = StateDB7.open(configManager.dbPath);
648
715
  const client = NotionClient8.fromConfig(config);
649
716
  const vaultFs = new NodeVaultFS7(cwd, config.paths);
650
717
  const orchestrator = new SyncOrchestrator7(config, stateDb, client, vaultFs);
651
- await orchestrator.status();
652
- const conflictRecords = stateDb.getByStatus("conflict");
653
- if (conflictRecords.length === 0) {
718
+ const conflicts = await orchestrator.listConflicts();
719
+ if (conflicts.length === 0) {
654
720
  console.log("\uCDA9\uB3CC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
655
- stateDb.close();
721
+ return;
722
+ }
723
+ const cleared = new Set(orchestrator.clearStaleConflicts(conflicts));
724
+ for (const path of cleared) {
725
+ console.log(` \x1B[32m\u2713\x1B[0m ${path} \u2192 \uC591\uCABD \uB3D9\uC77C, \uCDA9\uB3CC \uD45C\uC2DC\uB9CC \uD574\uC81C`);
726
+ }
727
+ const pending = conflicts.filter((c) => !cleared.has(c.syncRecord.obsidianPath));
728
+ if (pending.length === 0) {
729
+ console.log(`
730
+ ${cleared.size}\uAC74 \uBAA8\uB450 \uC774\uBBF8 \uD574\uC18C\uB41C \uC0C1\uD0DC\uC600\uC2B5\uB2C8\uB2E4 \u2014 \uC0C1\uD0DC\uB9CC \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.`);
656
731
  return;
657
732
  }
658
733
  console.log(`
659
- \uCDA9\uB3CC ${conflictRecords.length}\uAC74 \uBC1C\uACAC
734
+ \uCDA9\uB3CC ${pending.length}\uAC74 \uBC1C\uACAC
660
735
  `);
661
736
  if (options.strategy) {
662
737
  const strategy = options.strategy;
663
- const shouldProceed = await confirm2({
664
- message: `${conflictRecords.length}\uAC74\uC758 \uCDA9\uB3CC\uC744 '${strategy}' \uC804\uB7B5\uC73C\uB85C \uC77C\uAD04 \uD574\uACB0\uD560\uAE4C\uC694?`
665
- });
666
- if (!shouldProceed) {
667
- stateDb.close();
668
- return;
738
+ if (!options.yes) {
739
+ if (!isInteractive()) {
740
+ console.error(
741
+ `\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC785\uB2C8\uB2E4 \u2014 '${strategy}' \uC804\uB7B5\uC73C\uB85C ${pending.length}\uAC74\uC744 \uC77C\uAD04 \uD574\uACB0\uD558\uB824\uBA74 --yes \uB97C \uD568\uAED8 \uC9C0\uC815\uD558\uC138\uC694.`
742
+ );
743
+ process.exitCode = 1;
744
+ return;
745
+ }
746
+ const shouldProceed = await confirm2({
747
+ message: `${pending.length}\uAC74\uC758 \uCDA9\uB3CC\uC744 '${strategy}' \uC804\uB7B5\uC73C\uB85C \uC77C\uAD04 \uD574\uACB0\uD560\uAE4C\uC694?`
748
+ });
749
+ if (!shouldProceed) return;
669
750
  }
670
- const pullResult = await orchestrator.pull();
671
- const results = await orchestrator.resolveAllConflicts(pullResult.conflicts, strategy);
751
+ const results = await orchestrator.resolveAllConflicts(pending, strategy);
752
+ let failed2 = 0;
672
753
  for (const result of results) {
754
+ if (!result.success) failed2++;
673
755
  const icon = result.success ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
674
756
  console.log(` ${icon} ${result.path} \u2192 ${result.choice}`);
675
757
  }
676
- } else {
677
- const pullResult = await orchestrator.pull();
678
- for (const conflict of pullResult.conflicts) {
679
- await resolveInteractive(conflict, orchestrator);
680
- }
758
+ report(results.length - failed2, failed2);
759
+ return;
681
760
  }
682
- stateDb.close();
683
- console.log("\n\uCDA9\uB3CC \uD574\uACB0 \uC644\uB8CC");
761
+ if (!isInteractive()) {
762
+ console.error(
763
+ "\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C\uB294 \uB300\uD654\uD615 \uD574\uACB0\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 --strategy <local-first|remote-first|duplicate> --yes \uB97C \uC9C0\uC815\uD558\uC138\uC694."
764
+ );
765
+ process.exitCode = 1;
766
+ return;
767
+ }
768
+ let resolved = 0;
769
+ let failed = 0;
770
+ for (const conflict of pending) {
771
+ if (await resolveInteractive(conflict, orchestrator)) resolved++;
772
+ else failed++;
773
+ }
774
+ report(resolved, failed);
684
775
  } catch (error) {
685
776
  console.error("\uC624\uB958:", error instanceof Error ? error.message : String(error));
686
- process.exit(1);
777
+ process.exitCode = 1;
778
+ } finally {
779
+ stateDb?.close();
687
780
  }
688
781
  });
782
+ function report(resolved, failed) {
783
+ if (failed === 0) {
784
+ console.log(`
785
+ \uCDA9\uB3CC \uD574\uACB0 \uC644\uB8CC \u2014 ${resolved}\uAC74`);
786
+ return;
787
+ }
788
+ console.log(`
789
+ \x1B[33m\uCDA9\uB3CC ${resolved}\uAC74 \uD574\uACB0 \xB7 ${failed}\uAC74 \uBBF8\uD574\uACB0\x1B[0m`);
790
+ process.exitCode = 1;
791
+ }
689
792
  async function resolveInteractive(conflict, orchestrator) {
690
793
  const path = conflict.syncRecord.obsidianPath;
691
794
  console.log(`
@@ -717,11 +820,14 @@ ${"\u2500".repeat(60)}`);
717
820
  const result = await orchestrator.resolveConflict(conflict, choice);
718
821
  if (result.success) {
719
822
  console.log(`\x1B[32m\u2713 ${path} \u2192 ${choice}\uB85C \uD574\uACB0\x1B[0m`);
720
- } else if (result.mergeHadConflicts) {
823
+ return true;
824
+ }
825
+ if (result.mergeHadConflicts) {
721
826
  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`);
722
- } else {
723
- console.log(`\x1B[31m\u2717 ${path} \u2192 \uD574\uACB0 \uC2E4\uD328\x1B[0m`);
827
+ return false;
724
828
  }
829
+ console.log(`\x1B[31m\u2717 ${path} \u2192 \uD574\uACB0 \uC2E4\uD328\x1B[0m`);
830
+ return false;
725
831
  }
726
832
 
727
833
  // src/commands/watch.ts
@@ -803,10 +909,142 @@ var watchCommand = new Command9("watch").description("\uD30C\uC77C \uBCC0\uACBD
803
909
  });
804
910
  });
805
911
 
912
+ // src/commands/verify.ts
913
+ import { Command as Command10 } from "commander";
914
+ import {
915
+ ConfigManager as ConfigManager10,
916
+ StateDB as StateDB9,
917
+ NotionClient as NotionClient10,
918
+ SyncOrchestrator as SyncOrchestrator9,
919
+ NodeVaultFS as NodeVaultFS9
920
+ } from "@im-nobsidian/core";
921
+ import chalk7 from "chalk";
922
+ var SAMPLE_LIMIT = 10;
923
+ function printIdSample(label, ids, color) {
924
+ console.log(` ${color(label)} ${ids.length}`);
925
+ for (const id of ids.slice(0, SAMPLE_LIMIT)) {
926
+ console.log(` ${dimText(id)}`);
927
+ }
928
+ if (ids.length > SAMPLE_LIMIT) {
929
+ console.log(dimText(` ... and ${ids.length - SAMPLE_LIMIT} more`));
930
+ }
931
+ }
932
+ function printDatabaseSection(report2) {
933
+ console.log(`
934
+ ${header(" DB Completeness")}`);
935
+ console.log(` ${separator(50)}`);
936
+ console.log(` ${dimText("Databases:")} ${report2.databases.length}`);
937
+ console.log(` ${dimText("Remote rows:")} ${report2.remoteTotal}`);
938
+ console.log(` ${dimText("Vault rows:")} ${report2.localTotal}`);
939
+ const broken = report2.databases.filter((d) => d.missingIds.length > 0 || d.extraIds.length > 0);
940
+ if (broken.length > 0) {
941
+ console.log(`
942
+ ${header(chalk7.red(`Incomplete databases: ${broken.length}`))}`);
943
+ for (const db of broken) {
944
+ console.log(
945
+ ` ${icons.fail} ${db.databaseId} ${dimText(`remote ${db.remoteRows} / vault ${db.localRows}`)}`
946
+ );
947
+ if (db.missingIds.length > 0) printIdSample("missing", db.missingIds, chalk7.red);
948
+ if (db.extraIds.length > 0) printIdSample("extra", db.extraIds, chalk7.yellow);
949
+ }
950
+ }
951
+ if (report2.failures.length > 0) {
952
+ console.log(`
953
+ ${header(chalk7.yellow(`Query failures: ${report2.failures.length}`))}`);
954
+ for (const failure of report2.failures) {
955
+ console.log(` ${icons.fail} ${failure.databaseId} ${dimText(failure.error)}`);
956
+ }
957
+ }
958
+ if (report2.complete) {
959
+ console.log(`
960
+ ${chalk7.green("All database rows present in vault")} ${icons.success}`);
961
+ }
962
+ }
963
+ function printPageSection(report2) {
964
+ console.log(`
965
+ ${header(" Page Completeness")}`);
966
+ console.log(` ${separator(50)}`);
967
+ console.log(` ${dimText("Remote pages:")} ${report2.remotePages}`);
968
+ console.log(` ${dimText("Vault pages:")} ${report2.vaultPages}`);
969
+ if (report2.error) {
970
+ console.log(`
971
+ ${icons.fail} ${chalk7.yellow("Page enumeration failed")}`);
972
+ console.log(` ${dimText(report2.error)}`);
973
+ return;
974
+ }
975
+ if (report2.missingIds.length > 0) {
976
+ console.log(
977
+ `
978
+ ${header(chalk7.red(`Pages missing from vault: ${report2.missingIds.length}`))}`
979
+ );
980
+ printIdSample("missing", report2.missingIds, chalk7.red);
981
+ }
982
+ if (report2.localOnlyIds.length > 0) {
983
+ console.log(
984
+ `
985
+ ${dimText(`local-only pages: ${report2.localOnlyIds.length} (informational)`)}`
986
+ );
987
+ printIdSample("local-only", report2.localOnlyIds, dimText);
988
+ }
989
+ if (report2.complete) {
990
+ console.log(`
991
+ ${chalk7.green("All remote pages present in vault")} ${icons.success}`);
992
+ }
993
+ }
994
+ function printReport(report2) {
995
+ printDatabaseSection(report2.databases);
996
+ if (report2.pages) {
997
+ printPageSection(report2.pages);
998
+ } else {
999
+ console.log(`
1000
+ ${dimText("Page completeness: skipped (database mode)")}`);
1001
+ }
1002
+ if (!report2.complete) {
1003
+ console.log(
1004
+ `
1005
+ ${dimText("Run")} ${chalk7.cyan("nobsi pull")} ${dimText("to fetch what is missing")}`
1006
+ );
1007
+ }
1008
+ }
1009
+ var verifyCommand = new Command10("verify").description("\uC644\uACB0\uC131 \uAC80\uC99D \u2014 \uC6D0\uACA9 DB \uD589\xB7\uD398\uC774\uC9C0\uAC00 \uBCFC\uD2B8\uC5D0 \uBE60\uC9D0\uC5C6\uC774 \uC788\uB294\uC9C0 \uB300\uC870 (API \uD638\uCD9C)").option("--json", "\uACB0\uACFC\uB97C JSON \uC73C\uB85C \uCD9C\uB825").action(async (opts) => {
1010
+ const cwd = process.cwd();
1011
+ const configManager = new ConfigManager10(cwd);
1012
+ const config = await configManager.load();
1013
+ const stateDb = StateDB9.open(configManager.dbPath);
1014
+ try {
1015
+ const client = NotionClient10.fromConfig(config);
1016
+ const vaultFs = new NodeVaultFS9(cwd, config.paths);
1017
+ const orchestrator = new SyncOrchestrator9(config, stateDb, client, vaultFs);
1018
+ const report2 = await orchestrator.verifyCompleteness();
1019
+ if (opts.json) {
1020
+ console.log(JSON.stringify(report2, null, 2));
1021
+ } else {
1022
+ printReport(report2);
1023
+ }
1024
+ if (!report2.complete) process.exitCode = 1;
1025
+ } finally {
1026
+ stateDb.close();
1027
+ }
1028
+ });
1029
+
1030
+ // src/utils/exit.ts
1031
+ var FORCED_EXIT_DELAY_MS = 100;
1032
+ function resolveExitCode(exitCode) {
1033
+ if (typeof exitCode === "number") return exitCode;
1034
+ if (typeof exitCode === "string") {
1035
+ const parsed = Number(exitCode);
1036
+ return Number.isFinite(parsed) ? parsed : 0;
1037
+ }
1038
+ return 0;
1039
+ }
1040
+ function scheduleForcedExit(proc, schedule = setTimeout) {
1041
+ schedule(() => proc.exit(resolveExitCode(proc.exitCode)), FORCED_EXIT_DELAY_MS);
1042
+ }
1043
+
806
1044
  // src/index.ts
807
1045
  var requireJson = createRequire(import.meta.url);
808
1046
  var { version } = requireJson("../package.json");
809
- var program = new Command10();
1047
+ var program = new Command11();
810
1048
  program.name("im-nobsidian").description("Im-Nobsidian: Obsidian \u2194 Notion \uC591\uBC29\uD5A5 \uB3D9\uAE30\uD654 CLI").version(version).option("--verbose", "\uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825");
811
1049
  program.addCommand(initCommand);
812
1050
  program.addCommand(pushCommand);
@@ -817,7 +1055,8 @@ program.addCommand(diffCommand);
817
1055
  program.addCommand(fetchCommand);
818
1056
  program.addCommand(resolveCommand);
819
1057
  program.addCommand(watchCommand);
1058
+ program.addCommand(verifyCommand);
820
1059
  program.parseAsync().then(() => {
821
- setTimeout(() => process.exit(0), 100);
1060
+ scheduleForcedExit(process);
822
1061
  });
823
1062
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/format.ts","../src/commands/push.ts","../src/commands/pull.ts","../src/commands/sync.ts","../src/commands/status.ts","../src/commands/diff.ts","../src/commands/fetch.ts","../src/commands/resolve.ts","../src/commands/watch.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { 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 { fetchCommand } from \"./commands/fetch.js\";\nimport { resolveCommand } from \"./commands/resolve.js\";\nimport { watchCommand } from \"./commands/watch.js\";\n\nconst requireJson = createRequire(import.meta.url);\nconst { version } = requireJson(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"im-nobsidian\")\n .description(\"Im-Nobsidian: Obsidian ↔ Notion 양방향 동기화 CLI\")\n .version(version)\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(fetchCommand);\nprogram.addCommand(resolveCommand);\nprogram.addCommand(watchCommand);\n\nprogram.parseAsync().then(() => {\n setTimeout(() => process.exit(0), 100);\n});\n","import { Command } from \"commander\";\nimport { ConfigManager, NotionClient } from \"@im-nobsidian/core\";\nimport { input, confirm } from \"@inquirer/prompts\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { header, separator, icons, dimText } from \"../utils/format.js\";\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 console.log(`\\n${header(chalk.cyan(\" Im-Nobsidian Setup\"))}`);\n console.log(` ${separator()}`);\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.stop();\n console.log(` ${icons.success} Token validated`);\n\n await configManager.init({ token: options.token, rootPageId: options.rootPageId });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\n console.log(` ${icons.success} State DB initialized`);\n console.log(\n `\\n ${header(chalk.green(\"Ready!\"))} Run ${chalk.cyan(\"nobsi sync\")} to start syncing.`,\n );\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 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.stop();\n console.log(` ${icons.success} Token validated`);\n\n if (result.results.length === 0) {\n console.log(\n `\\n ${chalk.yellow(\"⚠\")} 접근 가능한 페이지가 없습니다. Integration에 페이지를 공유해주세요.`,\n );\n return;\n }\n\n console.log(`\\n Select root page:`);\n for (const [i, page] of result.results.entries()) {\n const title = extractPageTitle(page);\n console.log(` ${dimText(`${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 ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\n console.log(` ${icons.success} State DB initialized`);\n console.log(\n `\\n ${header(chalk.green(\"Ready!\"))} Run ${chalk.cyan(\"nobsi sync\")} to start syncing.`,\n );\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 chalk from \"chalk\";\n\nexport const icons = {\n create: chalk.green(\"+\"),\n update: chalk.yellow(\"~\"),\n delete: chalk.red(\"-\"),\n success: chalk.green(\"✓\"),\n fail: chalk.red(\"✕\"),\n conflict: chalk.magenta(\"!\"),\n synced: chalk.green(\"●\"),\n modified: chalk.yellow(\"●\"),\n newFile: chalk.cyan(\"●\"),\n conflictDot: chalk.magenta(\"●\"),\n} as const;\n\nexport function header(text: string): string {\n return chalk.bold(text);\n}\n\nexport function separator(width = 35): string {\n return chalk.dim(\"━\".repeat(width));\n}\n\nexport function operationIcon(op: \"create\" | \"update\" | \"delete\"): string {\n return icons[op];\n}\n\nexport function operationLabel(op: \"create\" | \"update\" | \"delete\"): string {\n switch (op) {\n case \"create\":\n return chalk.green(\"created\");\n case \"update\":\n return chalk.yellow(\"updated\");\n case \"delete\":\n return chalk.red(\"deleted\");\n }\n}\n\nexport function summary(created: number, updated: number, deleted: number): string {\n const parts: string[] = [];\n if (created > 0) parts.push(chalk.green(`${created} created`));\n if (updated > 0) parts.push(chalk.yellow(`${updated} updated`));\n if (deleted > 0) parts.push(chalk.red(`${deleted} deleted`));\n if (parts.length === 0) parts.push(chalk.dim(\"no changes\"));\n return parts.join(\" \");\n}\n\nexport function duration(ms: number): string {\n return chalk.dim(`Done in ${(ms / 1000).toFixed(1)}s`);\n}\n\nexport function progress(current: number, total: number): string {\n return chalk.dim(`[${current}/${total}]`);\n}\n\nexport function dimText(text: string): string {\n return chalk.dim(text);\n}\n\nexport function failedItem(path: string, error: string): void {\n console.log(` ${icons.fail} ${path}: ${chalk.red(error)}`);\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport {\n header,\n operationIcon,\n operationLabel,\n progress,\n summary,\n duration,\n failedItem,\n dimText,\n} from \"../utils/format.js\";\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Pushing to Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n\n const result = await orchestrator.push({\n dryRun: options.dryRun,\n paths: options.path,\n onProgress: (current, total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n const prog = progress(current, total);\n console.log(` ${icon} ${item.path} ${prog} ${label}`);\n },\n });\n\n console.log(`\\n ${header(chalk.green(\"Push complete\"))}`);\n console.log(\n ` ${summary(result.created, result.updated, result.deleted)} ${dimText(`${result.failed.length} failed`)}`,\n );\n console.log(` ${duration(result.duration)}`);\n\n if (result.failed.length > 0) {\n console.log(\"\");\n for (const f of result.failed) {\n failedItem(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 chalk from \"chalk\";\nimport {\n header,\n operationIcon,\n operationLabel,\n progress,\n summary,\n duration,\n icons,\n failedItem,\n dimText,\n} from \"../utils/format.js\";\n\nexport const pullCommand = new Command(\"pull\")\n .description(\"Notion 변경사항을 로컬에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 pull\")\n .option(\n \"--force\",\n \"증분 감지를 건너뛰고 전체 스캔 (search 인덱싱 지연으로 누락된 신규 페이지·DB 복구)\",\n )\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Pulling from Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n\n const result = await orchestrator.pull({\n dryRun: options.dryRun,\n paths: options.path,\n force: options.force,\n onProgress: (current, total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n const prog = progress(current, total);\n console.log(` ${icon} ${item.path} ${prog} ${label}`);\n },\n });\n\n if (result.imageCount > 0) {\n console.log(\n `\\n ${icons.success} ${result.imageCount} images → ${dimText(\"attachments/\")}`,\n );\n }\n if (result.fileCount > 0) {\n console.log(` ${icons.success} ${result.fileCount} files → ${dimText(\"attachments/\")}`);\n }\n if (result.linkCount > 0) {\n console.log(` ${icons.success} ${result.linkCount} links resolved`);\n }\n\n console.log(`\\n ${header(chalk.green(\"Pull complete\"))}`);\n console.log(\n ` ${summary(result.created, result.updated, result.deleted)} ${dimText(`${result.failed.length} failed`)}`,\n );\n console.log(` ${duration(result.duration)}`);\n\n if (result.conflicts.length > 0) {\n console.log(\n `\\n ${icons.conflict} ${chalk.magenta(`${result.conflicts.length} conflicts`)} — run ${chalk.cyan(\"nobsi resolve\")}`,\n );\n }\n\n if (result.failed.length > 0) {\n console.log(\"\");\n for (const f of result.failed) {\n failedItem(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 chalk from \"chalk\";\nimport {\n header,\n separator,\n operationIcon,\n operationLabel,\n summary,\n duration,\n icons,\n dimText,\n} from \"../utils/format.js\";\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Bidirectional Sync\")}`);\n console.log(` ${separator()}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n\n // Pull phase\n console.log(`\\n ${header(chalk.blue(\"▼ Pull\"))} ${dimText(\"(Notion → Obsidian)\")}`);\n\n const pullResult = await orchestrator.pull({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n console.log(` ${icon} ${item.path} ${label}`);\n },\n });\n\n if (pullResult.imageCount > 0) {\n console.log(` ${icons.success} ${pullResult.imageCount} images downloaded`);\n }\n if (pullResult.linkCount > 0) {\n console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);\n }\n\n // Push phase\n console.log(`\\n ${header(chalk.magenta(\"▲ Push\"))} ${dimText(\"(Obsidian → Notion)\")}`);\n\n const pushResult = await orchestrator.push({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n console.log(` ${icon} ${item.path} ${label}`);\n },\n });\n\n // Summary\n const totalDuration = pullResult.duration + pushResult.duration;\n console.log(`\\n ${header(chalk.green(\"Sync complete\"))}`);\n console.log(\n ` ${chalk.blue(\"Pull:\")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}`,\n );\n console.log(\n ` ${chalk.magenta(\"Push:\")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted)}`,\n );\n console.log(` ${duration(totalDuration)}`);\n\n if (pullResult.conflicts.length > 0) {\n console.log(\n `\\n ${icons.conflict} ${chalk.magenta(`${pullResult.conflicts.length} conflicts`)} — run ${chalk.cyan(\"nobsi resolve\")}`,\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 chalk from \"chalk\";\nimport { header, separator, icons, dimText } from \"../utils/format.js\";\n\nexport const statusCommand = new Command(\"status\")\n .description(\"동기화 상태 확인 (--full: 원격 변경 포함)\")\n .option(\"--full\", \"Notion 원격 변경까지 양방향 확인 (API 호출, 느림)\")\n .action(async (opts: { full?: boolean }) => {\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = opts.full ? await orchestrator.status() : await orchestrator.statusLocal();\n\n console.log(`\\n${header(\" Sync Status\")}`);\n console.log(` ${separator(50)}`);\n\n const rootId = config.notion.rootPageId;\n const direction = config.sync.direction === \"both\" ? \"bidirectional\" : config.sync.direction;\n console.log(` ${dimText(\"Root page:\")} ${rootId.slice(0, 8)}...`);\n console.log(` ${dimText(\"Direction:\")} ${direction}`);\n if (status.lastSyncAt) {\n const date = new Date(status.lastSyncAt);\n const iso =\n date.getFullYear() +\n \"-\" +\n String(date.getMonth() + 1).padStart(2, \"0\") +\n \"-\" +\n String(date.getDate()).padStart(2, \"0\") +\n \" \" +\n String(date.getHours()).padStart(2, \"0\") +\n \":\" +\n String(date.getMinutes()).padStart(2, \"0\") +\n \":\" +\n String(date.getSeconds()).padStart(2, \"0\");\n console.log(` ${dimText(\"Last sync:\")} ${iso}`);\n } else {\n console.log(` ${dimText(\"Last sync:\")} ${chalk.yellow(\"never\")}`);\n }\n\n // Tracked files summary\n const allRecords = stateDb.getAll();\n const total = allRecords.length;\n const synced = allRecords.filter((r) => r.status === \"synced\").length;\n const conflictCount = status.conflictRecords.length;\n const createdChanges = status.localChanges.filter((c) => c.type === \"created\");\n const modifiedChanges = status.localChanges.filter((c) => c.type === \"modified\");\n const remoteCreated = status.remoteChanges.filter((c) => c.type === \"created\");\n const remoteModified = status.remoteChanges.filter((c) => c.type === \"modified\");\n const remoteDeleted = status.remoteChanges.filter((c) => c.type === \"deleted\");\n\n console.log(`\\n ${header(`Tracked files: ${total}`)}`);\n console.log(` ${icons.synced} ${chalk.green(\"synced\")} ${synced}`);\n if (modifiedChanges.length > 0) {\n console.log(` ${icons.modified} ${chalk.yellow(\"modified\")} ${modifiedChanges.length}`);\n }\n if (createdChanges.length > 0) {\n console.log(` ${icons.newFile} ${chalk.cyan(\"new\")} ${createdChanges.length}`);\n }\n if (conflictCount > 0) {\n console.log(` ${icons.conflictDot} ${chalk.magenta(\"conflict\")} ${conflictCount}`);\n }\n\n // Modified files\n if (modifiedChanges.length > 0) {\n console.log(`\\n ${header(\"Modified files:\")}`);\n for (const c of modifiedChanges.slice(0, 10)) {\n console.log(\n ` ${chalk.yellow(\"~\")} ${c.path.padEnd(30)} ${dimText(\"(local changed)\")}`,\n );\n }\n if (modifiedChanges.length > 10) {\n console.log(dimText(` ... and ${modifiedChanges.length - 10} more`));\n }\n }\n\n // New files\n if (createdChanges.length > 0) {\n console.log(`\\n ${header(\"New files:\")}`);\n for (const c of createdChanges.slice(0, 10)) {\n console.log(` ${chalk.cyan(\"+\")} ${c.path.padEnd(30)} ${dimText(\"(untracked)\")}`);\n }\n if (createdChanges.length > 10) {\n console.log(dimText(` ... and ${createdChanges.length - 10} more`));\n }\n }\n\n // Remote changes (Notion-side)\n if (status.remoteChanges.length > 0) {\n console.log(`\\n ${header(`Remote changes (Notion): ${status.remoteChanges.length}`)}`);\n if (remoteCreated.length > 0) {\n console.log(` ${chalk.cyan(\"+\")} ${chalk.cyan(\"new\")} ${remoteCreated.length}`);\n }\n if (remoteModified.length > 0) {\n console.log(\n ` ${chalk.yellow(\"~\")} ${chalk.yellow(\"modified\")} ${remoteModified.length}`,\n );\n }\n if (remoteDeleted.length > 0) {\n console.log(` ${chalk.red(\"-\")} ${chalk.red(\"deleted\")} ${remoteDeleted.length}`);\n }\n for (const c of status.remoteChanges.slice(0, 10)) {\n const icon =\n c.type === \"created\"\n ? chalk.cyan(\"+\")\n : c.type === \"modified\"\n ? chalk.yellow(\"~\")\n : chalk.red(\"-\");\n console.log(` ${icon} ${c.pageId.slice(0, 8)}... ${dimText(`(${c.type})`)}`);\n }\n if (status.remoteChanges.length > 10) {\n console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));\n }\n }\n\n // Conflicts\n if (conflictCount > 0) {\n console.log(`\\n ${header(chalk.magenta(\"Conflicts:\"))}`);\n for (const r of status.conflictRecords) {\n console.log(\n ` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(\"(both sides changed)\")}`,\n );\n }\n }\n\n // Helpful commands\n const hasChanges =\n status.localChanges.length > 0 || status.remoteChanges.length > 0 || conflictCount > 0;\n if (hasChanges) {\n console.log(\"\");\n if (conflictCount > 0) {\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi resolve\")} ${dimText(\"to resolve conflicts\")}`,\n );\n }\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi sync\")} ${dimText(\"to push/pull changes\")}`,\n );\n } else {\n console.log(`\\n ${chalk.green(\"Everything up to date\")} ✓`);\n }\n if (!opts.full) {\n console.log(\n `\\n ${dimText(\"Run\")} ${chalk.cyan(\"nobsi status --full\")} ${dimText(\"to check Notion remote changes\")}`,\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = await orchestrator.statusLocal();\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, \"/dev/null\", `b/${change.path}`);\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, `a/${change.path}`, \"/dev/null\");\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, `a/${change.path}`, `b/${change.path}`);\n }\n\n console.log(\"\");\n }\n } finally {\n stateDb.close();\n }\n });\n\nfunction printPatch(patch: string, color: boolean, oldPath: string, newPath: string): void {\n for (const line of patch.split(\"\\n\")) {\n let output = line;\n if (line.startsWith(\"===\")) {\n output = \"===================================================================\";\n } else if (line.startsWith(\"--- \")) {\n output = `--- ${oldPath}`;\n } else if (line.startsWith(\"+++ \")) {\n output = `+++ ${newPath}`;\n }\n\n if (!color) {\n console.log(output);\n continue;\n }\n\n if (output.startsWith(\"+++\") || output.startsWith(\"---\")) {\n console.log(`\\x1b[1m${output}\\x1b[0m`);\n } else if (output.startsWith(\"+\")) {\n console.log(`\\x1b[32m${output}\\x1b[0m`);\n } else if (output.startsWith(\"-\")) {\n console.log(`\\x1b[31m${output}\\x1b[0m`);\n } else if (output.startsWith(\"@@\")) {\n console.log(`\\x1b[36m${output}\\x1b[0m`);\n } else {\n console.log(output);\n }\n }\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { header, separator, dimText } from \"../utils/format.js\";\n\nexport const fetchCommand = new Command(\"fetch\")\n .description(\"Notion 리모트 상태 스캔 (삭제 감지 포함)\")\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(dimText(\" Scanning remote pages...\"));\n\n const result = await orchestrator.fetch();\n\n console.log(`\\n${header(\" Remote Scan\")}`);\n console.log(` ${separator(40)}`);\n\n if (result.newPages > 0) {\n console.log(` ${chalk.cyan(\"+\")} New pages: ${result.newPages}`);\n }\n if (result.modifiedPages > 0) {\n console.log(` ${chalk.yellow(\"~\")} Modified pages: ${result.modifiedPages}`);\n }\n if (result.deletedPages > 0) {\n console.log(` ${chalk.red(\"-\")} Deleted pages: ${result.deletedPages}`);\n }\n\n const total = result.newPages + result.modifiedPages + result.deletedPages;\n if (total === 0) {\n console.log(` ${chalk.green(\"Remote is up to date\")} ✓`);\n } else {\n console.log(\n `\\n ${dimText(\"Run\")} ${chalk.cyan(\"nobsi pull\")} ${dimText(\"to apply remote changes\")}`,\n );\n }\n\n console.log(dimText(`\\n Completed in ${(result.duration / 1000).toFixed(1)}s`));\n } finally {\n stateDb.close();\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} 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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n\n const orchestrator = new SyncOrchestrator(config, stateDb, client, 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 orchestrator.resolveAllConflicts(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, orchestrator);\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(\n conflict: Conflict,\n orchestrator: SyncOrchestrator,\n): 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 = orchestrator.generateConflictDiff(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 orchestrator.resolveConflict(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 = NotionClient.fromConfig(config);\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","chalk","chalk","spinner","Command","ConfigManager","NotionClient","chalk","Command","ConfigManager","NotionClient","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","confirm","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/format.ts","../src/commands/push.ts","../src/commands/pull.ts","../src/commands/sync.ts","../src/commands/status.ts","../src/commands/diff.ts","../src/commands/fetch.ts","../src/commands/resolve.ts","../src/commands/watch.ts","../src/commands/verify.ts","../src/utils/exit.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { 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 { fetchCommand } from \"./commands/fetch.js\";\nimport { resolveCommand } from \"./commands/resolve.js\";\nimport { watchCommand } from \"./commands/watch.js\";\nimport { verifyCommand } from \"./commands/verify.js\";\nimport { scheduleForcedExit } from \"./utils/exit.js\";\n\nconst requireJson = createRequire(import.meta.url);\nconst { version } = requireJson(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"im-nobsidian\")\n .description(\"Im-Nobsidian: Obsidian ↔ Notion 양방향 동기화 CLI\")\n .version(version)\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(fetchCommand);\nprogram.addCommand(resolveCommand);\nprogram.addCommand(watchCommand);\nprogram.addCommand(verifyCommand);\n\n// 강제 종료하되 명령이 정한 종료 코드를 보존한다 — 근거는 utils/exit.ts 참조(R11-C).\nprogram.parseAsync().then(() => {\n scheduleForcedExit(process);\n});\n","import { Command } from \"commander\";\nimport { ConfigManager, NotionClient } from \"@im-nobsidian/core\";\nimport { input, confirm } from \"@inquirer/prompts\";\nimport ora from \"ora\";\nimport chalk from \"chalk\";\nimport { header, separator, icons, dimText } from \"../utils/format.js\";\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 console.log(`\\n${header(chalk.cyan(\" Im-Nobsidian Setup\"))}`);\n console.log(` ${separator()}`);\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.stop();\n console.log(` ${icons.success} Token validated`);\n\n await configManager.init({ token: options.token, rootPageId: options.rootPageId });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\n console.log(` ${icons.success} State DB initialized`);\n console.log(\n `\\n ${header(chalk.green(\"Ready!\"))} Run ${chalk.cyan(\"nobsi sync\")} to start syncing.`,\n );\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 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.stop();\n console.log(` ${icons.success} Token validated`);\n\n if (result.results.length === 0) {\n console.log(\n `\\n ${chalk.yellow(\"⚠\")} 접근 가능한 페이지가 없습니다. Integration에 페이지를 공유해주세요.`,\n );\n return;\n }\n\n console.log(`\\n Select root page:`);\n for (const [i, page] of result.results.entries()) {\n const title = extractPageTitle(page);\n console.log(` ${dimText(`${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 ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\n console.log(` ${icons.success} State DB initialized`);\n console.log(\n `\\n ${header(chalk.green(\"Ready!\"))} Run ${chalk.cyan(\"nobsi sync\")} to start syncing.`,\n );\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 chalk from \"chalk\";\n\nexport const icons = {\n create: chalk.green(\"+\"),\n update: chalk.yellow(\"~\"),\n delete: chalk.red(\"-\"),\n success: chalk.green(\"✓\"),\n fail: chalk.red(\"✕\"),\n conflict: chalk.magenta(\"!\"),\n synced: chalk.green(\"●\"),\n modified: chalk.yellow(\"●\"),\n newFile: chalk.cyan(\"●\"),\n conflictDot: chalk.magenta(\"●\"),\n} as const;\n\nexport function header(text: string): string {\n return chalk.bold(text);\n}\n\nexport function separator(width = 35): string {\n return chalk.dim(\"━\".repeat(width));\n}\n\nexport function operationIcon(op: \"create\" | \"update\" | \"delete\"): string {\n return icons[op];\n}\n\nexport function operationLabel(op: \"create\" | \"update\" | \"delete\"): string {\n switch (op) {\n case \"create\":\n return chalk.green(\"created\");\n case \"update\":\n return chalk.yellow(\"updated\");\n case \"delete\":\n return chalk.red(\"deleted\");\n }\n}\n\nexport function summary(created: number, updated: number, deleted: number): string {\n const parts: string[] = [];\n if (created > 0) parts.push(chalk.green(`${created} created`));\n if (updated > 0) parts.push(chalk.yellow(`${updated} updated`));\n if (deleted > 0) parts.push(chalk.red(`${deleted} deleted`));\n if (parts.length === 0) parts.push(chalk.dim(\"no changes\"));\n return parts.join(\" \");\n}\n\n/**\n * 완료 헤더 — 실패가 하나라도 있으면 초록 \"complete\" 로 끝내지 않는다.\n *\n * pull/push/sync 가 모두 결과와 무관하게 초록 `... complete` 를 찍었다. 수십 건이 실패해도\n * 화면 맨 아래는 성공으로 읽혀, 사람은 물론 로그를 훑는 스크립트까지 넘어갔다.\n * 헤더 문구·색을 결과에서 파생시켜 세 명령이 같은 판정을 공유한다.\n */\nexport function completionHeader(label: string, failedCount: number): string {\n return failedCount > 0\n ? header(chalk.yellow(`${label} finished with errors`))\n : header(chalk.green(`${label} complete`));\n}\n\n/** 실패 건수 표기. dim 은 \"없는 셈\" 으로 읽히므로 0 이 아닐 때만 dim 을 벗는다. */\nexport function failedCountText(count: number): string {\n return count > 0 ? chalk.red(`${count} failed`) : chalk.dim(\"0 failed\");\n}\n\nexport function duration(ms: number): string {\n return chalk.dim(`Done in ${(ms / 1000).toFixed(1)}s`);\n}\n\nexport function progress(current: number, total: number): string {\n return chalk.dim(`[${current}/${total}]`);\n}\n\nexport function dimText(text: string): string {\n return chalk.dim(text);\n}\n\nexport function failedItem(path: string, error: string): void {\n console.log(` ${icons.fail} ${path}: ${chalk.red(error)}`);\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport {\n header,\n operationIcon,\n operationLabel,\n progress,\n summary,\n duration,\n failedItem,\n dimText,\n completionHeader,\n failedCountText,\n} from \"../utils/format.js\";\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Pushing to Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n\n const result = await orchestrator.push({\n dryRun: options.dryRun,\n paths: options.path,\n onProgress: (current, total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n const prog = progress(current, total);\n console.log(` ${icon} ${item.path} ${prog} ${label}`);\n },\n });\n\n const failedCount = result.failed.length;\n console.log(`\\n ${completionHeader(\"Push\", failedCount)}`);\n console.log(\n ` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`,\n );\n console.log(` ${duration(result.duration)}`);\n\n if (failedCount > 0) {\n console.log(\"\");\n for (const f of result.failed) {\n failedItem(f.path, f.error);\n }\n // 종료 코드까지 실패로 남긴다 — push 가 반쯤 실패한 걸 자동화가 성공으로 보면 안 된다.\n process.exitCode = 1;\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 chalk from \"chalk\";\nimport {\n header,\n operationIcon,\n operationLabel,\n progress,\n summary,\n duration,\n icons,\n failedItem,\n dimText,\n completionHeader,\n failedCountText,\n} from \"../utils/format.js\";\n\nexport const pullCommand = new Command(\"pull\")\n .description(\"Notion 변경사항을 로컬에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 pull\")\n .option(\n \"--force\",\n \"증분 감지를 건너뛰고 전체 스캔 (search 인덱싱 지연으로 누락된 신규 페이지·DB 복구)\",\n )\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Pulling from Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n\n const result = await orchestrator.pull({\n dryRun: options.dryRun,\n paths: options.path,\n force: options.force,\n onProgress: (current, total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n const prog = progress(current, total);\n console.log(` ${icon} ${item.path} ${prog} ${label}`);\n },\n });\n\n if (result.imageCount > 0) {\n console.log(\n `\\n ${icons.success} ${result.imageCount} images → ${dimText(\"attachments/\")}`,\n );\n }\n if (result.fileCount > 0) {\n console.log(` ${icons.success} ${result.fileCount} files → ${dimText(\"attachments/\")}`);\n }\n if (result.linkCount > 0) {\n console.log(` ${icons.success} ${result.linkCount} links resolved`);\n }\n\n const failedCount = result.failed.length;\n console.log(`\\n ${completionHeader(\"Pull\", failedCount)}`);\n console.log(\n ` ${summary(result.created, result.updated, result.deleted)} ${failedCountText(failedCount)}`,\n );\n // 복원은 별도 줄로 알린다 — 볼트에서 파일이 사라졌었다는 사실은 조용히 넘길 일이 아니다.\n if (result.restored > 0) {\n console.log(` ${icons.success} ${chalk.yellow(`${result.restored} restored`)}`);\n }\n console.log(` ${duration(result.duration)}`);\n\n if (result.conflicts.length > 0) {\n console.log(\n `\\n ${icons.conflict} ${chalk.magenta(`${result.conflicts.length} conflicts`)} — run ${chalk.cyan(\"nobsi resolve\")}`,\n );\n }\n\n if (failedCount > 0) {\n console.log(\"\");\n for (const f of result.failed) {\n failedItem(f.path, f.error);\n }\n // 종료 코드까지 실패로 남긴다 — 자동화가 pull 실패를 성공으로 집계하면 안 된다.\n process.exitCode = 1;\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 chalk from \"chalk\";\nimport {\n header,\n separator,\n operationIcon,\n operationLabel,\n summary,\n duration,\n icons,\n dimText,\n completionHeader,\n failedItem,\n} from \"../utils/format.js\";\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(`\\n${header(\" Bidirectional Sync\")}`);\n console.log(` ${separator()}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n\n // Pull phase\n console.log(`\\n ${header(chalk.blue(\"▼ Pull\"))} ${dimText(\"(Notion → Obsidian)\")}`);\n\n const pullResult = await orchestrator.pull({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n console.log(` ${icon} ${item.path} ${label}`);\n },\n });\n\n if (pullResult.imageCount > 0) {\n console.log(` ${icons.success} ${pullResult.imageCount} images downloaded`);\n }\n if (pullResult.linkCount > 0) {\n console.log(` ${icons.success} ${pullResult.linkCount} links resolved`);\n }\n\n // Push phase\n console.log(`\\n ${header(chalk.magenta(\"▲ Push\"))} ${dimText(\"(Obsidian → Notion)\")}`);\n\n const pushResult = await orchestrator.push({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n console.log(` ${icon} ${item.path} ${label}`);\n },\n });\n\n // Summary\n // 예전엔 양쪽 failed 를 아예 찍지 않아, 전 건 실패한 sync 도 화면 끝은 초록 \"complete\"\n // 였다 — 실패를 헤더와 목록 양쪽에 드러낸다.\n const totalDuration = pullResult.duration + pushResult.duration;\n const pullFailed = pullResult.failed.length;\n const pushFailed = pushResult.failed.length;\n console.log(`\\n ${completionHeader(\"Sync\", pullFailed + pushFailed)}`);\n console.log(\n ` ${chalk.blue(\"Pull:\")} ${summary(pullResult.created, pullResult.updated, pullResult.deleted)}` +\n // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.\n (pullResult.restored > 0 ? ` ${chalk.yellow(`${pullResult.restored} restored`)}` : \"\") +\n (pullFailed > 0 ? ` ${chalk.red(`${pullFailed} failed`)}` : \"\"),\n );\n console.log(\n ` ${chalk.magenta(\"Push:\")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted)}` +\n (pushFailed > 0 ? ` ${chalk.red(`${pushFailed} failed`)}` : \"\"),\n );\n console.log(` ${duration(totalDuration)}`);\n\n if (pullResult.conflicts.length > 0) {\n console.log(\n `\\n ${icons.conflict} ${chalk.magenta(`${pullResult.conflicts.length} conflicts`)} — run ${chalk.cyan(\"nobsi resolve\")}`,\n );\n }\n\n if (pullFailed + pushFailed > 0) {\n console.log(\"\");\n for (const f of pullResult.failed) failedItem(`▼ ${f.path}`, f.error);\n for (const f of pushResult.failed) failedItem(`▲ ${f.path}`, f.error);\n process.exitCode = 1;\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 chalk from \"chalk\";\nimport { header, separator, icons, dimText } from \"../utils/format.js\";\n\nexport const statusCommand = new Command(\"status\")\n .description(\"동기화 상태 확인 (--full: 원격 변경 포함)\")\n .option(\"--full\", \"Notion 원격 변경까지 양방향 확인 (API 호출, 느림)\")\n .action(async (opts: { full?: boolean }) => {\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = opts.full ? await orchestrator.status() : await orchestrator.statusLocal();\n\n console.log(`\\n${header(\" Sync Status\")}`);\n console.log(` ${separator(50)}`);\n\n const rootId = config.notion.rootPageId;\n const direction = config.sync.direction === \"both\" ? \"bidirectional\" : config.sync.direction;\n console.log(` ${dimText(\"Root page:\")} ${rootId.slice(0, 8)}...`);\n console.log(` ${dimText(\"Direction:\")} ${direction}`);\n if (status.lastSyncAt) {\n const date = new Date(status.lastSyncAt);\n const iso =\n date.getFullYear() +\n \"-\" +\n String(date.getMonth() + 1).padStart(2, \"0\") +\n \"-\" +\n String(date.getDate()).padStart(2, \"0\") +\n \" \" +\n String(date.getHours()).padStart(2, \"0\") +\n \":\" +\n String(date.getMinutes()).padStart(2, \"0\") +\n \":\" +\n String(date.getSeconds()).padStart(2, \"0\");\n console.log(` ${dimText(\"Last sync:\")} ${iso}`);\n } else {\n console.log(` ${dimText(\"Last sync:\")} ${chalk.yellow(\"never\")}`);\n }\n\n // Tracked files summary\n const allRecords = stateDb.getAll();\n const total = allRecords.length;\n const conflictCount = status.conflictRecords.length;\n const createdChanges = status.localChanges.filter((c) => c.type === \"created\");\n const modifiedChanges = status.localChanges.filter((c) => c.type === \"modified\");\n const deletedChanges = status.localChanges.filter((c) => c.type === \"deleted\");\n\n // 추적 파일 내역은 서로 겹치지 않아야 한다. DB 의 status 는 push/pull 시점에만 갱신되므로\n // 방금 고친 파일도 레코드상으론 여전히 'synced' 다 — 그대로 세면 같은 파일이 synced 와\n // modified 양쪽에 잡혀 내역 합이 총계를 넘는 표가 나온다(무엇이 밀렸는지 못 읽는다).\n // 실제 변경이 걸린 경로를 먼저 덜어 내고 남은 것만 synced 로 센다.\n const dirtyPaths = new Set([\n ...modifiedChanges.map((c) => c.path),\n ...deletedChanges.map((c) => c.path),\n ...status.conflictRecords.map((r) => r.obsidianPath),\n ]);\n const synced = allRecords.filter(\n (r) => r.status === \"synced\" && !dirtyPaths.has(r.obsidianPath),\n ).length;\n const remoteCreated = status.remoteChanges.filter((c) => c.type === \"created\");\n const remoteModified = status.remoteChanges.filter((c) => c.type === \"modified\");\n const remoteDeleted = status.remoteChanges.filter((c) => c.type === \"deleted\");\n\n console.log(`\\n ${header(`Tracked files: ${total}`)}`);\n console.log(` ${icons.synced} ${chalk.green(\"synced\")} ${synced}`);\n if (modifiedChanges.length > 0) {\n console.log(` ${icons.modified} ${chalk.yellow(\"modified\")} ${modifiedChanges.length}`);\n }\n if (deletedChanges.length > 0) {\n console.log(` ${chalk.red(\"-\")} ${chalk.red(\"deleted\")} ${deletedChanges.length}`);\n }\n if (conflictCount > 0) {\n console.log(` ${icons.conflictDot} ${chalk.magenta(\"conflict\")} ${conflictCount}`);\n }\n // new 는 아직 추적 레코드가 없는 파일이라 위 총계(Tracked files)에 들어가지 않는다.\n // 같은 블록에 숫자만 늘어놓으면 합이 안 맞아 보이므로 그 사실을 함께 적는다.\n if (createdChanges.length > 0) {\n console.log(\n ` ${icons.newFile} ${chalk.cyan(\"new\")} ${createdChanges.length} ${dimText(\"(untracked)\")}`,\n );\n }\n\n // Modified files\n if (modifiedChanges.length > 0) {\n console.log(`\\n ${header(\"Modified files:\")}`);\n for (const c of modifiedChanges.slice(0, 10)) {\n console.log(\n ` ${chalk.yellow(\"~\")} ${c.path.padEnd(30)} ${dimText(\"(local changed)\")}`,\n );\n }\n if (modifiedChanges.length > 10) {\n console.log(dimText(` ... and ${modifiedChanges.length - 10} more`));\n }\n }\n\n // New files\n if (createdChanges.length > 0) {\n console.log(`\\n ${header(\"New files:\")}`);\n for (const c of createdChanges.slice(0, 10)) {\n console.log(` ${chalk.cyan(\"+\")} ${c.path.padEnd(30)} ${dimText(\"(untracked)\")}`);\n }\n if (createdChanges.length > 10) {\n console.log(dimText(` ... and ${createdChanges.length - 10} more`));\n }\n }\n\n // Remote changes (Notion-side)\n if (status.remoteChanges.length > 0) {\n console.log(`\\n ${header(`Remote changes (Notion): ${status.remoteChanges.length}`)}`);\n if (remoteCreated.length > 0) {\n console.log(` ${chalk.cyan(\"+\")} ${chalk.cyan(\"new\")} ${remoteCreated.length}`);\n }\n if (remoteModified.length > 0) {\n console.log(\n ` ${chalk.yellow(\"~\")} ${chalk.yellow(\"modified\")} ${remoteModified.length}`,\n );\n }\n if (remoteDeleted.length > 0) {\n console.log(` ${chalk.red(\"-\")} ${chalk.red(\"deleted\")} ${remoteDeleted.length}`);\n }\n for (const c of status.remoteChanges.slice(0, 10)) {\n const icon =\n c.type === \"created\"\n ? chalk.cyan(\"+\")\n : c.type === \"modified\"\n ? chalk.yellow(\"~\")\n : chalk.red(\"-\");\n console.log(` ${icon} ${c.pageId.slice(0, 8)}... ${dimText(`(${c.type})`)}`);\n }\n if (status.remoteChanges.length > 10) {\n console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));\n }\n }\n\n // Conflicts\n if (conflictCount > 0) {\n console.log(`\\n ${header(chalk.magenta(\"Conflicts:\"))}`);\n for (const r of status.conflictRecords) {\n console.log(\n ` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(\"(both sides changed)\")}`,\n );\n }\n }\n\n // Helpful commands\n const hasChanges =\n status.localChanges.length > 0 || status.remoteChanges.length > 0 || conflictCount > 0;\n if (hasChanges) {\n console.log(\"\");\n if (conflictCount > 0) {\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi resolve\")} ${dimText(\"to resolve conflicts\")}`,\n );\n }\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi sync\")} ${dimText(\"to push/pull changes\")}`,\n );\n } else {\n console.log(`\\n ${chalk.green(\"Everything up to date\")} ✓`);\n }\n if (!opts.full) {\n console.log(\n `\\n ${dimText(\"Run\")} ${chalk.cyan(\"nobsi status --full\")} ${dimText(\"to check Notion remote changes\")}`,\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 matchesPathScope,\n} from \"@im-nobsidian/core\";\nimport { createTwoFilesPatch } from \"diff\";\n\n// 기본 비교 대상은 **마지막 동기화 스냅샷**이다(로컬에서 무엇을 고쳤는지). Notion 현재\n// 본문과의 비교는 API 호출이 필요하므로 `--remote` 로 명시할 때만 한다 — 예전 설명은\n// \"로컬과 Notion 간의 차이\"라고만 적어 두고 실제로는 원격을 한 번도 읽지 않았다.\nexport const diffCommand = new Command(\"diff\")\n .description(\n \"로컬 변경사항 표시 (기본: 마지막 동기화 시점과 비교, --remote: Notion 현재본과 비교)\",\n )\n .argument(\"[path]\", \"특정 파일 경로 (생략 시 변경된 모든 파일)\")\n .option(\"--color\", \"컬러 출력\", true)\n .option(\"--remote\", \"Notion 현재 본문과 비교 (API 호출, 느림)\")\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const status = await orchestrator.statusLocal();\n\n const changes = targetPath\n ? status.localChanges.filter((c) => matchesPathScope(c.path, 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 // --remote: 마지막 스냅샷이 아니라 Notion 의 현재 본문을 기준선으로 삼는다.\n // 원격이 그 사이 바뀌었을 때 \"내가 뭘 덮어쓰게 되는지\"는 이 비교로만 보인다.\n if (options.remote && change.type !== \"deleted\") {\n const remote = await orchestrator.renderRemoteSnapshot(change.path);\n if (remote === null) {\n console.log(`--- (원격 없음) ${change.path}`);\n console.log(`+++ b/${change.path}`);\n console.log(`(아직 Notion 에 없는 파일입니다)`);\n console.log(\"\");\n continue;\n }\n const currentContent = await vaultFs.readFile(change.path);\n const patch = createTwoFilesPatch(\n `notion/${change.path}`,\n `b/${change.path}`,\n remote,\n currentContent,\n );\n printPatch(patch, options.color, `notion/${change.path}`, `b/${change.path}`);\n console.log(\"\");\n continue;\n }\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, \"/dev/null\", `b/${change.path}`);\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, `a/${change.path}`, \"/dev/null\");\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, `a/${change.path}`, `b/${change.path}`);\n }\n\n console.log(\"\");\n }\n } finally {\n stateDb.close();\n }\n });\n\nfunction printPatch(patch: string, color: boolean, oldPath: string, newPath: string): void {\n for (const line of patch.split(\"\\n\")) {\n let output = line;\n if (line.startsWith(\"===\")) {\n output = \"===================================================================\";\n } else if (line.startsWith(\"--- \")) {\n output = `--- ${oldPath}`;\n } else if (line.startsWith(\"+++ \")) {\n output = `+++ ${newPath}`;\n }\n\n if (!color) {\n console.log(output);\n continue;\n }\n\n if (output.startsWith(\"+++\") || output.startsWith(\"---\")) {\n console.log(`\\x1b[1m${output}\\x1b[0m`);\n } else if (output.startsWith(\"+\")) {\n console.log(`\\x1b[32m${output}\\x1b[0m`);\n } else if (output.startsWith(\"-\")) {\n console.log(`\\x1b[31m${output}\\x1b[0m`);\n } else if (output.startsWith(\"@@\")) {\n console.log(`\\x1b[36m${output}\\x1b[0m`);\n } else {\n console.log(output);\n }\n }\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { header, separator, dimText } from \"../utils/format.js\";\n\nexport const fetchCommand = new Command(\"fetch\")\n .description(\"Notion 리모트 상태 스캔 (삭제 감지 포함)\")\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n console.log(dimText(\" Scanning remote pages...\"));\n\n const result = await orchestrator.fetch();\n\n console.log(`\\n${header(\" Remote Scan\")}`);\n console.log(` ${separator(40)}`);\n\n if (result.newPages > 0) {\n console.log(` ${chalk.cyan(\"+\")} New pages: ${result.newPages}`);\n }\n if (result.modifiedPages > 0) {\n console.log(` ${chalk.yellow(\"~\")} Modified pages: ${result.modifiedPages}`);\n }\n if (result.deletedPages > 0) {\n console.log(` ${chalk.red(\"-\")} Deleted pages: ${result.deletedPages}`);\n }\n\n const total = result.newPages + result.modifiedPages + result.deletedPages;\n if (total === 0) {\n console.log(` ${chalk.green(\"Remote is up to date\")} ✓`);\n } else {\n console.log(\n `\\n ${dimText(\"Run\")} ${chalk.cyan(\"nobsi pull\")} ${dimText(\"to apply remote changes\")}`,\n );\n }\n\n console.log(dimText(`\\n Completed in ${(result.duration / 1000).toFixed(1)}s`));\n } finally {\n stateDb.close();\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} from \"@im-nobsidian/core\";\nimport type { Conflict, ResolutionChoice } from \"@im-nobsidian/core\";\n\n/** 허용 전략 — 오타를 조용히 흘리지 않도록 실행 전에 한 번 검증한다. */\nconst STRATEGIES = [\"local-first\", \"remote-first\", \"duplicate\"] as const;\ntype Strategy = (typeof STRATEGIES)[number];\n\n/**\n * 대화형 프롬프트를 띄울 수 있는 환경인지.\n *\n * 파이프·cron·CI 처럼 TTY 가 없는 곳에서 @inquirer 프롬프트를 띄우면 원인을 알 수 없는\n * 오류로 죽거나 입력을 기다리며 멎는다. 먼저 판정해 무엇을 어떻게 하라고 말해 준다.\n */\nfunction isInteractive(): boolean {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\nexport const resolveCommand = new Command(\"resolve\")\n .description(\"충돌을 해결합니다\")\n .option(\"-s, --strategy <strategy>\", `자동 해결 전략 (${STRATEGIES.join(\" | \")})`)\n .option(\"-y, --yes\", \"확인 프롬프트 없이 진행 (비대화형/CI)\")\n .action(async (options: { strategy?: string; yes?: boolean }) => {\n if (options.strategy && !STRATEGIES.includes(options.strategy as Strategy)) {\n console.error(`알 수 없는 전략 '${options.strategy}' — 사용 가능: ${STRATEGIES.join(\", \")}`);\n process.exitCode = 1;\n return;\n }\n\n const cwd = process.cwd();\n const configManager = new ConfigManager(cwd);\n let stateDb: StateDB | undefined;\n\n try {\n const config = await configManager.load();\n stateDb = StateDB.open(configManager.dbPath);\n const client = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n // 충돌 목록은 **충돌로 표시된 페이지만** 읽어 온다. 예전엔 목록을 얻겠다고 전체 pull 을\n // 돌렸는데, 그건 해소하기도 전에 볼트를 원격으로 덮어쓰는 짓이라 순서가 거꾸로였다.\n const conflicts = await orchestrator.listConflicts();\n\n if (conflicts.length === 0) {\n console.log(\"충돌이 없습니다.\");\n return;\n }\n\n // 양쪽이 이미 같아진 항목은 물어볼 게 없다. 그대로 두면 push 가 그 파일을 영영\n // 건너뛰어(충돌 상태는 push 제외) 이후 편집이 조용히 정체하므로 먼저 걷어낸다.\n const cleared = new Set(orchestrator.clearStaleConflicts(conflicts));\n for (const path of cleared) {\n console.log(` \\x1b[32m✓\\x1b[0m ${path} → 양쪽 동일, 충돌 표시만 해제`);\n }\n\n const pending = conflicts.filter((c) => !cleared.has(c.syncRecord.obsidianPath));\n if (pending.length === 0) {\n console.log(`\\n${cleared.size}건 모두 이미 해소된 상태였습니다 — 상태만 정리했습니다.`);\n return;\n }\n\n console.log(`\\n충돌 ${pending.length}건 발견\\n`);\n\n if (options.strategy) {\n const strategy = options.strategy as Strategy;\n\n if (!options.yes) {\n if (!isInteractive()) {\n console.error(\n `비대화형 환경입니다 — '${strategy}' 전략으로 ${pending.length}건을 일괄 해결하려면 --yes 를 함께 지정하세요.`,\n );\n process.exitCode = 1;\n return;\n }\n const shouldProceed = await confirm({\n message: `${pending.length}건의 충돌을 '${strategy}' 전략으로 일괄 해결할까요?`,\n });\n if (!shouldProceed) return;\n }\n\n const results = await orchestrator.resolveAllConflicts(pending, strategy);\n let failed = 0;\n for (const result of results) {\n if (!result.success) failed++;\n const icon = result.success ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[31m✗\\x1b[0m\";\n console.log(` ${icon} ${result.path} → ${result.choice}`);\n }\n report(results.length - failed, failed);\n return;\n }\n\n if (!isInteractive()) {\n console.error(\n \"비대화형 환경에서는 대화형 해결을 쓸 수 없습니다 — --strategy <local-first|remote-first|duplicate> --yes 를 지정하세요.\",\n );\n process.exitCode = 1;\n return;\n }\n\n let resolved = 0;\n let failed = 0;\n for (const conflict of pending) {\n if (await resolveInteractive(conflict, orchestrator)) resolved++;\n else failed++;\n }\n report(resolved, failed);\n } catch (error) {\n console.error(\"오류:\", error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n } finally {\n // 예전엔 분기마다 close 를 흩뿌려 두고 catch 경로에선 아예 닫지 않았다 — 잠금이 남는다.\n stateDb?.close();\n }\n });\n\n/** 결과를 사실대로 알린다 — 실패가 하나라도 있으면 \"완료\"라고 말하지 않는다. */\nfunction report(resolved: number, failed: number): void {\n if (failed === 0) {\n console.log(`\\n충돌 해결 완료 — ${resolved}건`);\n return;\n }\n console.log(`\\n\\x1b[33m충돌 ${resolved}건 해결 · ${failed}건 미해결\\x1b[0m`);\n process.exitCode = 1;\n}\n\n/** @returns 해소에 성공했으면 true. 병합 마커가 남은 경우는 미해결로 센다. */\nasync function resolveInteractive(\n conflict: Conflict,\n orchestrator: SyncOrchestrator,\n): Promise<boolean> {\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 = orchestrator.generateConflictDiff(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 orchestrator.resolveConflict(conflict, choice);\n\n if (result.success) {\n console.log(`\\x1b[32m✓ ${path} → ${choice}로 해결\\x1b[0m`);\n return true;\n }\n if (result.mergeHadConflicts) {\n console.log(`\\x1b[33m⚠ ${path} → 자동 병합 완료 (수동 확인 필요한 충돌 마커 있음)\\x1b[0m`);\n return false;\n }\n console.log(`\\x1b[31m✗ ${path} → 해결 실패\\x1b[0m`);\n return false;\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 = NotionClient.fromConfig(config);\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","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n NodeVaultFS,\n} from \"@im-nobsidian/core\";\nimport type {\n CompletenessReport,\n PageCompletenessReport,\n VaultCompletenessReport,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { header, separator, icons, dimText } from \"../utils/format.js\";\n\n/** 화면에 늘어놓을 id 최대 개수 — 나머지는 건수로 접는다. */\nconst SAMPLE_LIMIT = 10;\n\nfunction printIdSample(label: string, ids: readonly string[], color: (s: string) => string): void {\n console.log(` ${color(label)} ${ids.length}`);\n for (const id of ids.slice(0, SAMPLE_LIMIT)) {\n console.log(` ${dimText(id)}`);\n }\n if (ids.length > SAMPLE_LIMIT) {\n console.log(dimText(` ... and ${ids.length - SAMPLE_LIMIT} more`));\n }\n}\n\nfunction printDatabaseSection(report: CompletenessReport): void {\n console.log(`\\n${header(\" DB Completeness\")}`);\n console.log(` ${separator(50)}`);\n console.log(` ${dimText(\"Databases:\")} ${report.databases.length}`);\n console.log(` ${dimText(\"Remote rows:\")} ${report.remoteTotal}`);\n console.log(` ${dimText(\"Vault rows:\")} ${report.localTotal}`);\n\n const broken = report.databases.filter((d) => d.missingIds.length > 0 || d.extraIds.length > 0);\n\n if (broken.length > 0) {\n console.log(`\\n ${header(chalk.red(`Incomplete databases: ${broken.length}`))}`);\n for (const db of broken) {\n console.log(\n ` ${icons.fail} ${db.databaseId} ${dimText(`remote ${db.remoteRows} / vault ${db.localRows}`)}`,\n );\n if (db.missingIds.length > 0) printIdSample(\"missing\", db.missingIds, chalk.red);\n if (db.extraIds.length > 0) printIdSample(\"extra\", db.extraIds, chalk.yellow);\n }\n }\n\n if (report.failures.length > 0) {\n console.log(`\\n ${header(chalk.yellow(`Query failures: ${report.failures.length}`))}`);\n for (const failure of report.failures) {\n console.log(` ${icons.fail} ${failure.databaseId} ${dimText(failure.error)}`);\n }\n }\n\n if (report.complete) {\n console.log(`\\n ${chalk.green(\"All database rows present in vault\")} ${icons.success}`);\n }\n}\n\n/**\n * 페이지 대조 출력 (R12-C).\n *\n * `local-only` 는 실패가 아니라 정보다 — 아직 push 하지 않은 로컬 노트·root 페이지 자신·\n * search 색인 지연이 전부 여기에 정상적으로 들어온다. 색을 달리 줘서 \"고쳐야 할 것\"과\n * \"그냥 알아 둘 것\"이 화면에서 섞이지 않게 한다.\n */\nfunction printPageSection(report: PageCompletenessReport): void {\n console.log(`\\n${header(\" Page Completeness\")}`);\n console.log(` ${separator(50)}`);\n console.log(` ${dimText(\"Remote pages:\")} ${report.remotePages}`);\n console.log(` ${dimText(\"Vault pages:\")} ${report.vaultPages}`);\n\n if (report.error) {\n console.log(`\\n ${icons.fail} ${chalk.yellow(\"Page enumeration failed\")}`);\n console.log(` ${dimText(report.error)}`);\n return;\n }\n\n if (report.missingIds.length > 0) {\n console.log(\n `\\n ${header(chalk.red(`Pages missing from vault: ${report.missingIds.length}`))}`,\n );\n printIdSample(\"missing\", report.missingIds, chalk.red);\n }\n\n if (report.localOnlyIds.length > 0) {\n console.log(\n `\\n ${dimText(`local-only pages: ${report.localOnlyIds.length} (informational)`)}`,\n );\n printIdSample(\"local-only\", report.localOnlyIds, dimText);\n }\n\n if (report.complete) {\n console.log(`\\n ${chalk.green(\"All remote pages present in vault\")} ${icons.success}`);\n }\n}\n\nfunction printReport(report: VaultCompletenessReport): void {\n printDatabaseSection(report.databases);\n\n if (report.pages) {\n printPageSection(report.pages);\n } else {\n // DB 모드에는 root 서브트리가 없어 대조할 페이지 자체가 없다 — 생략을 명시한다.\n console.log(`\\n ${dimText(\"Page completeness: skipped (database mode)\")}`);\n }\n\n if (!report.complete) {\n console.log(\n `\\n ${dimText(\"Run\")} ${chalk.cyan(\"nobsi pull\")} ${dimText(\"to fetch what is missing\")}`,\n );\n }\n}\n\nexport const verifyCommand = new Command(\"verify\")\n .description(\"완결성 검증 — 원격 DB 행·페이지가 볼트에 빠짐없이 있는지 대조 (API 호출)\")\n .option(\"--json\", \"결과를 JSON 으로 출력\")\n .action(async (opts: { json?: boolean }) => {\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 = NotionClient.fromConfig(config);\n const vaultFs = new NodeVaultFS(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const report = await orchestrator.verifyCompleteness();\n\n if (opts.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printReport(report);\n }\n\n // 미발견·잔재·조회 실패가 있으면 실패 종료한다 — CI·E2E 하니스가 이 코드로 판정한다.\n if (!report.complete) process.exitCode = 1;\n } finally {\n stateDb.close();\n }\n });\n","/**\n * CLI 종료 처리 — 남은 핸들 때문에 강제 종료하되, **명령이 정한 종료 코드는 보존한다**.\n *\n * better-sqlite3 핸들과 rate limiter 타이머가 이벤트 루프를 붙잡고 있어 명령이 끝나도\n * 프로세스가 자연 종료되지 않는다. 그래서 짧은 지연 뒤 강제 종료하는데, 여기서 코드를\n * 0 으로 못박으면 각 명령이 실패마다 세워 둔 `process.exitCode = 1`\n * (init·pull·push·sync·resolve·verify)이 **전부 지워진다** — 실패한 실행도 셸과 CI 에는\n * 성공으로 보이고, 종료 코드로 판정하는 스크립트는 아무것도 못 잡는다(R11-C).\n *\n * 종료 코드 결정을 이 함수 하나에 두어, \"강제 종료한다\"와 \"성공으로 종료한다\"가 다시\n * 한 줄에 섞이지 않게 한다.\n */\n\n/** 강제 종료까지의 지연 — 마지막 stdout flush 여유. */\nexport const FORCED_EXIT_DELAY_MS = 100;\n\n/**\n * {@link scheduleForcedExit} 가 쓰는 프로세스 표면(테스트 대역 주입용).\n *\n * `exitCode` 는 Node 의 `process.exitCode` 를 그대로 받는다 — 명시적으로 지운 경우\n * `null` 이 될 수 있어 그 유니온까지 포함해야 실제 `process` 를 넘길 수 있다.\n */\nexport interface ExitableProcess {\n exitCode?: number | string | null | undefined;\n exit(code?: number): void;\n}\n\n/** `process.exitCode` 를 종료 코드로 정규화한다. 숫자가 아니면 성공(0). */\nexport function resolveExitCode(exitCode: number | string | null | undefined): number {\n if (typeof exitCode === \"number\") return exitCode;\n if (typeof exitCode === \"string\") {\n const parsed = Number(exitCode);\n return Number.isFinite(parsed) ? parsed : 0;\n }\n return 0;\n}\n\n/** 지연 후 강제 종료를 예약한다 — 종료 코드는 명령이 세운 값을 그대로 쓴다. */\nexport function scheduleForcedExit(\n proc: ExitableProcess,\n schedule: (fn: () => void, ms: number) => unknown = setTimeout,\n): void {\n schedule(() => proc.exit(resolveExitCode(proc.exitCode)), FORCED_EXIT_DELAY_MS);\n}\n"],"mappings":"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","names":["Command","chalk","chalk","spinner","Command","ConfigManager","NotionClient","Command","ConfigManager","NotionClient","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","chalk","Command","confirm","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","failed","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","NodeVaultFS","chalk","report","chalk","Command","ConfigManager","StateDB","NotionClient","NodeVaultFS","SyncOrchestrator","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-nobsidian",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "CLI tool for Obsidian ↔ Notion bidirectional sync",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "commander": "^15.0.0",
17
17
  "diff": "^9.0.0",
18
18
  "ora": "^9.4.1",
19
- "@im-nobsidian/core": "0.3.0"
19
+ "@im-nobsidian/core": "0.3.2"
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.0.0",