im-nobsidian 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -310,7 +310,10 @@ function pullJson(result) {
310
310
  restored: result.restored,
311
311
  moved: result.moved?.length ?? 0,
312
312
  moveSkipped: result.moveSkipped?.length ?? 0,
313
- churn: result.created + result.updated + result.deleted + result.restored + (result.moved?.length ?? 0),
313
+ renamed: result.moved?.filter((move) => move.renamed).length ?? 0,
314
+ relinked: result.relinked?.length ?? 0,
315
+ excluded: result.excluded?.length ?? 0,
316
+ churn: result.created + result.updated + result.deleted + result.restored + (result.moved?.length ?? 0) + (result.relinked?.length ?? 0),
314
317
  conflicts: result.conflicts.length,
315
318
  failed: result.failed,
316
319
  writtenPaths: result.writtenPaths.length,
@@ -383,7 +386,13 @@ ${header(" Pushing to Notion...")}`);
383
386
 
384
387
  // src/commands/pull.ts
385
388
  import { Command as Command3, Option } from "commander";
386
- import { ConfigManager as ConfigManager4, StateDB as StateDB3, NotionClient as NotionClient3, SyncOrchestrator as SyncOrchestrator2 } from "@im-nobsidian/core";
389
+ import {
390
+ ConfigManager as ConfigManager4,
391
+ StateDB as StateDB3,
392
+ NotionClient as NotionClient3,
393
+ SyncOrchestrator as SyncOrchestrator2,
394
+ IGNORE_FILE
395
+ } from "@im-nobsidian/core";
387
396
  import chalk3 from "chalk";
388
397
  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(
389
398
  "--force",
@@ -452,8 +461,22 @@ ${header(" Pulling from Notion...")}`);
452
461
  console.log(` ${icons.success} ${chalk3.yellow(`${result.restored} restored`)}`);
453
462
  }
454
463
  for (const move of result.moved ?? []) {
464
+ const verb = move.renamed ? "rename" : "move";
465
+ console.log(
466
+ ` ${icons.move} ${move.from} \u2192 ${move.to} ${dimText(options.dryRun ? `(would ${verb})` : `(${verb}d)`)}`
467
+ );
468
+ }
469
+ const excluded = result.excluded ?? [];
470
+ if (excluded.length > 0) {
471
+ console.log(
472
+ ` ${icons.conflict} ${dimText(`${excluded.length} new in Notion ${options.dryRun ? "would not be" : "not"} pulled \u2014 excluded by paths / ${IGNORE_FILE}:`)}`
473
+ );
474
+ for (const path of excluded.slice(0, 10)) console.log(` ${dimText(`\xB7 ${path}`)}`);
475
+ if (excluded.length > 10) console.log(dimText(` ... and ${excluded.length - 10} more`));
476
+ }
477
+ if ((result.relinked?.length ?? 0) > 0) {
455
478
  console.log(
456
- ` ${icons.move} ${move.from} \u2192 ${move.to} ${dimText(options.dryRun ? "(would move)" : "(moved)")}`
479
+ ` ${icons.success} ${dimText(`links updated in ${result.relinked.length} notes`)}`
457
480
  );
458
481
  }
459
482
  for (const skip of result.moveSkipped ?? []) {
@@ -581,6 +604,7 @@ import {
581
604
  StateDB as StateDB5,
582
605
  NotionClient as NotionClient5,
583
606
  SyncOrchestrator as SyncOrchestrator4,
607
+ IGNORE_FILE as IGNORE_FILE2,
584
608
  isRemoteDeletion
585
609
  } from "@im-nobsidian/core";
586
610
  import chalk5 from "chalk";
@@ -726,6 +750,38 @@ ${header(" Sync Status")}`);
726
750
  console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));
727
751
  }
728
752
  }
753
+ const remoteMoves = status.remoteMoves ?? [];
754
+ const remoteMoveSkipped = status.remoteMoveSkipped ?? [];
755
+ if (remoteMoves.length + remoteMoveSkipped.length > 0) {
756
+ console.log(`
757
+ ${header("Moved in Notion (next pull):")}`);
758
+ for (const move of remoteMoves.slice(0, 10)) {
759
+ console.log(
760
+ ` ${icons.move} ${move.from} \u2192 ${move.to} ${dimText(move.renamed ? "(will rename)" : "(will move)")}`
761
+ );
762
+ }
763
+ if (remoteMoves.length > 10) {
764
+ console.log(dimText(` ... and ${remoteMoves.length - 10} more`));
765
+ }
766
+ for (const skip of remoteMoveSkipped) {
767
+ console.log(
768
+ ` ${icons.conflict} ${skip.path} ${dimText(`(won't move: ${skip.reason})`)}`
769
+ );
770
+ }
771
+ }
772
+ const remoteExcluded = status.remoteExcluded ?? [];
773
+ if (remoteExcluded.length > 0) {
774
+ console.log(
775
+ `
776
+ ${header(`New in Notion, not pulled (excluded by paths / ${IGNORE_FILE2}): ${remoteExcluded.length}`)}`
777
+ );
778
+ for (const path of remoteExcluded.slice(0, 10)) {
779
+ console.log(` ${dimText("\xB7")} ${path}`);
780
+ }
781
+ if (remoteExcluded.length > 10) {
782
+ console.log(dimText(` ... and ${remoteExcluded.length - 10} more`));
783
+ }
784
+ }
729
785
  if (conflictCount > 0) {
730
786
  console.log(`
731
787
  ${header(chalk5.magenta("Conflicts:"))}`);
@@ -737,7 +793,7 @@ ${header(" Sync Status")}`);
737
793
  console.log(` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(reason)}`);
738
794
  }
739
795
  }
740
- const hasChanges = status.localChanges.length > 0 || folderMoves.length > 0 || status.remoteChanges.length > 0 || conflictCount > 0;
796
+ const hasChanges = status.localChanges.length > 0 || folderMoves.length > 0 || status.remoteChanges.length > 0 || remoteMoves.length > 0 || conflictCount > 0;
741
797
  if (hasChanges) {
742
798
  console.log("");
743
799
  if (conflictCount > 0) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/format.ts","../src/utils/vault.ts","../src/commands/push.ts","../src/utils/json-output.ts","../src/commands/pull.ts","../src/commands/sync.ts","../src/commands/status.ts","../src/commands/diff.ts","../src/utils/diff-output.ts","../src/commands/discard.ts","../src/commands/fetch.ts","../src/commands/resolve.ts","../src/utils/state-db-failure.ts","../src/commands/watch.ts","../src/commands/verify.ts","../src/commands/prepare-db.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 { discardCommand } from \"./commands/discard.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 { prepareDbCommand } from \"./commands/prepare-db.js\";\nimport { scheduleForcedExit } from \"./utils/exit.js\";\nimport { reportStateDbFailure } from \"./utils/state-db-failure.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(discardCommand);\nprogram.addCommand(fetchCommand);\nprogram.addCommand(resolveCommand);\nprogram.addCommand(watchCommand);\nprogram.addCommand(verifyCommand);\nprogram.addCommand(prepareDbCommand);\n\n// 강제 종료하되 명령이 정한 종료 코드를 보존한다 — 근거는 utils/exit.ts 참조(R11-C).\n// 상태 DB 를 쓸 수 없어 명령이 던졌으면 이유와 할 일을 보이고 1 로 끝낸다 — utils/state-db-failure.ts.\nprogram.parseAsync().then(\n () => {\n scheduleForcedExit(process);\n },\n (error: unknown) => {\n reportStateDbFailure(error, process);\n scheduleForcedExit(process);\n },\n);\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n NOTION_TOKEN_PREFIX,\n NotionClient,\n notionIdFromInput,\n} 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\";\nimport { outerConnectionTracking } from \"../utils/vault.js\";\n\n/** 루트 페이지에서 ID 를 못 찾았을 때 — 주소를 그대로 붙여 넣어도 된다고 말한다. */\nconst ROOT_PAGE_UNREADABLE =\n \"루트 페이지에서 Notion 페이지 ID(32자리)를 찾지 못했습니다 — 페이지 주소나 ID 를 넣으세요.\";\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 // 바깥 연결이 이미 이 폴더를 동기화하면 따로 연결하지 않는다(ADR-038) — 바깥 연결이 멈춘다.\n const outer = outerConnectionTracking(cwd);\n if (outer) {\n console.error(\n `이 폴더는 이미 ${outer.folder} 의 연결이 노트 ${outer.notes}개를 동기화하고 있어 따로 연결할 수 없습니다.`,\n );\n process.exitCode = 1;\n return;\n }\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(NOTION_TOKEN_PREFIX)) {\n console.error(`토큰은 ${NOTION_TOKEN_PREFIX}으로 시작해야 합니다.`);\n process.exitCode = 1;\n return;\n }\n\n const rootPageId = notionIdFromInput(options.rootPageId);\n if (rootPageId === null) {\n console.error(ROOT_PAGE_UNREADABLE);\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 });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\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) =>\n v.startsWith(NOTION_TOKEN_PREFIX) ||\n `${NOTION_TOKEN_PREFIX}으로 시작하는 토큰을 입력하세요`,\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 rootPageInput = await input({\n message: \"루트 페이지 ID 또는 주소 (위에서 선택하거나 직접 입력):\",\n default: result.results[0]?.id,\n validate: (value) => notionIdFromInput(value) !== null || ROOT_PAGE_UNREADABLE,\n });\n const rootPageId = notionIdFromInput(rootPageInput)!;\n\n await configManager.init({ token, rootPageId });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\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\";\nimport type { FullScanReason, ProgressItem, RemoteScanInfo } from \"@im-nobsidian/core\";\n\nexport const icons = {\n create: chalk.green(\"+\"),\n update: chalk.yellow(\"~\"),\n delete: chalk.red(\"-\"),\n move: chalk.cyan(\"→\"),\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: ProgressItem[\"operation\"]): string {\n return icons[op];\n}\n\nexport function operationLabel(op: ProgressItem[\"operation\"]): 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 case \"move\":\n return chalk.cyan(\"moved\");\n }\n}\n\n/** 결과 수 한 줄. `moved` 는 push 만 센다 — 옮긴 노트 · 폴더(본문을 함께 고친 것도). */\nexport function summary(created: number, updated: number, deleted: number, moved = 0): 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 (moved > 0) parts.push(chalk.cyan(`${moved} moved`));\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\n/** 로컬 시각 `YYYY-MM-DD HH:MM:SS`. 읽을 수 없는 값은 그대로 보인다 — NaN 으로 바꾸지 않는다. */\nexport function localDateTime(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return iso;\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n );\n}\n\n/** 전체 대조한 이유. 코어의 이유 코드가 늘면 여기가 컴파일되지 않는다 — 빠진 말을 조용히 두지 않는다. */\nconst FULL_SCAN_REASON: Record<FullScanReason, string> = {\n first: \"first pull\",\n forced: \"--force\",\n \"database-mode\": \"database mode\",\n \"every-pull\": \"every pull\",\n due: \"scheduled\",\n};\n\n/**\n * 이번에 원격을 얼마나 훑었나 (ADR-027). 바뀐 것만 찾았으면 Notion 에서 지운 것은 아직 볼트에 반영되지\n * 않았을 수 있다 — 그 사실과 언제 반영되는지를 함께 적는다. 적지 않으면 «Notion 에서 지웠는데 볼트에\n * 남았다» 를 결함으로 읽는다. 원격 삭제를 반영하지 않는 설정(`sync.deleteSync` 꺼짐)이면 적지 않는다.\n */\nexport function remoteScanLines(scan: RemoteScanInfo): string[] {\n if (scan.kind === \"full\") {\n return [dimText(scan.reason ? `Full scan (${FULL_SCAN_REASON[scan.reason]})` : \"Full scan\")];\n }\n const skipped = scan.skippedDatabases ?? 0;\n const skippedText =\n skipped > 0\n ? ` · ${skipped} unchanged ${skipped === 1 ? \"database\" : \"databases\"} skipped`\n : \"\";\n const lines = [dimText(`Changes-only scan${skippedText}`)];\n if (scan.deletionsDeferred) lines.push(remoteDeletionHint(scan));\n return lines;\n}\n\n/** 미룬 원격 삭제가 언제 반영되나 — {@link RemoteScanInfo.deletionsDeferred} 일 때 쓴다. */\nexport function remoteDeletionHint(scan: RemoteScanInfo): string {\n const when = scan.nextFullAt\n ? `the next full scan (${localDateTime(scan.nextFullAt)})`\n : \"the next pull (full scan due)\";\n return `${dimText(`Deletions in Notion apply at ${when} — or run`)} ${chalk.cyan(\"nobsi pull --force\")}`;\n}\n\n/** 마지막 전체 대조 시각 — 한 번도 없으면 never. */\nexport function lastFullScanText(lastFullScanAt: string | null): string {\n return lastFullScanAt ? localDateTime(lastFullScanAt) : chalk.yellow(\"never\");\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\n/**\n * 실패 한 줄. 볼트 최상위의 실패(DB 자동 발견 등)는 빈 경로로 오므로 `/` 로 보인다. `direction` 은\n * sync 가 받기(▼) · 올리기(▲)를 가를 때 쓴다.\n */\nexport function failedItem(path: string, error: string, direction?: string): void {\n const where = `${direction ? `${direction} ` : \"\"}${path || \"/\"}`;\n console.log(` ${icons.fail} ${where}: ${chalk.red(error)}`);\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\nimport {\n ConfigManager,\n NodeVaultFS,\n StateDB,\n matchesPathScope,\n ScopedVaultFS,\n findConnectionFolders,\n OBSIDIAN_CONFIG_DIR,\n type PathFilterConfig,\n type VaultFS,\n} from \"@im-nobsidian/core\";\n\n/** 이 폴더 안쪽의 다른 연결 폴더(ADR-038) — 이 폴더의 동기화가 비켜 간다. */\nexport async function nestedConnections(cwd: string): Promise<string[]> {\n return (await findConnectionFolders(cwd)).filter((folder) => folder !== \"\");\n}\n\n/**\n * 현재 폴더를 볼트로 다루는 VaultFS. 안쪽에 다른 연결 폴더가 있으면 그 폴더는 올리지도 덮어쓰지도 않는다 —\n * 그 폴더는 자기 설정 · Notion 으로 따로 동기화한다(`cd <폴더> && nobsi sync`).\n */\nexport async function openVaultFs(cwd: string, paths: PathFilterConfig): Promise<VaultFS> {\n const base = new NodeVaultFS(cwd, paths);\n const nested = await nestedConnections(cwd);\n return nested.length === 0 ? base : new ScopedVaultFS(base, \"\", [\"\", ...nested]);\n}\n\n/**\n * 명령줄에 받은 볼트 경로 → 볼트 경로(`/`). Windows 에서는 `00 Me\\a.md` 처럼 `\\` 로 쳐도 같은 노트다(이슈 #8) —\n * 예전에는 `-p \"00 Me\\\"` 가 어느 노트에도 맞지 않아 「변경 없음」 이었다. 다른 OS 에서 `\\` 는 이름의 글자다.\n */\nexport function vaultArgPath(path: string, platform: NodeJS.Platform = process.platform): string {\n return platform === \"win32\" ? path.replace(/\\\\/g, \"/\") : path;\n}\n\n/**\n * Obsidian 설정 폴더까지의 상대 경로 — 연결 폴더가 볼트 안쪽이면 위로 올라가 찾는다. 못 찾으면 `.obsidian`\n * (없으면 속성 타입을 값으로 정한다).\n */\nexport function obsidianConfigDir(cwd: string): string {\n for (let dir = cwd; ; dir = dirname(dir)) {\n if (existsSync(join(dir, OBSIDIAN_CONFIG_DIR))) {\n return relative(cwd, join(dir, OBSIDIAN_CONFIG_DIR)).split(\"\\\\\").join(\"/\");\n }\n if (dirname(dir) === dir) return OBSIDIAN_CONFIG_DIR;\n }\n}\n\n/**\n * 이 폴더를 이미 동기화하는 바깥 연결 — 그 연결 폴더와 이 폴더 안에서 추적하는 노트 수. 없으면 null.\n *\n * 이미 동기화하던 폴더를 따로 연결하면 바깥 연결이 멈춘다(ConnectionScopeError) — init 이 먼저 막는다.\n */\nexport function outerConnectionTracking(cwd: string): { folder: string; notes: number } | null {\n for (let dir = dirname(cwd); ; dir = dirname(dir)) {\n const manager = new ConfigManager(dir);\n if (existsSync(manager.dbPath)) {\n const scope = relative(dir, cwd).split(\"\\\\\").join(\"/\");\n const db = StateDB.open(manager.dbPath);\n try {\n // 폴더 자신의 레코드(폴더 페이지)는 빼고 그 안의 것만 센다.\n const notes = db\n .getAll()\n .filter(\n (r) => r.obsidianPath !== scope && matchesPathScope(r.obsidianPath, scope),\n ).length;\n return notes > 0 ? { folder: dir, notes } : null;\n } finally {\n db.close();\n }\n }\n if (dirname(dir) === dir) return null;\n }\n}\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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\";\nimport { enableJsonMode, printJson, pushJson } from \"../utils/json-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\nexport const pushCommand = new Command(\"push\")\n .description(\"로컬 변경사항을 Notion에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 push\")\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n if (!json) {\n console.log(`\\n${header(\" Pushing to Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n }\n\n const result = await orchestrator.push({\n dryRun: options.dryRun,\n paths: options.path?.map((path: string) => vaultArgPath(path)),\n onProgress: json\n ? undefined\n : (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 (json) {\n printJson(pushJson(result));\n if (result.failed.length > 0) process.exitCode = 1;\n return;\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, result.moved)} ${failedCountText(failedCount)}`,\n );\n if (result.createdDatabases) {\n console.log(` ${options.dryRun ? \"만들 DB\" : \"만든 DB\"} ${result.createdDatabases}`);\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 { setLogger } from \"@im-nobsidian/core\";\nimport type { FailedOperation, PullResult, PushResult, RemoteScanInfo } from \"@im-nobsidian/core\";\n\n/**\n * `--json` 모드 — 자동화(E2E 하니스 · CI)가 사람용 문구를 grep 하지 않고 결과를 읽게 한다.\n *\n * 문구 grep 은 조용히 틀렸다. sync 로그에 Pull 쪽 \"no changes\" 가 한 줄만 있어도 Push 쪽\n * 변경과 무관하게 churn 0 으로 판정했고, restored · deleted 는 아예 세지 않았다.\n *\n * 계약: stdout 에는 JSON 한 줄만 쓴다. 코어 로그는 전부 stderr 로 돌린다.\n */\nexport function enableJsonMode(): void {\n const toStderr = (message: string, ...args: unknown[]): void => console.error(message, ...args);\n setLogger({ warn: toStderr, error: toStderr, info: toStderr, debug: () => {} });\n}\n\nexport interface PushJson {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n /** 옮김을 반영한 노트 · 폴더 수 — updated 와 겹치지 않는다. */\n readonly moved: number;\n /** 원격에 실제로 쓴(또는 dry-run 이면 쓸) 항목 수. 멱등 판정의 기준값. */\n readonly churn: number;\n /** 사이드카로 만든(dry-run 은 만들) Notion DB 수 — churn 에도 든다. */\n readonly createdDatabases: number;\n readonly failed: readonly FailedOperation[];\n readonly durationMs: number;\n}\n\nexport interface PullJson {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n readonly restored: number;\n /** Notion 에서 옮긴 페이지를 볼트에서 옮긴 수(ADR-039) · 옮기지 않고 둔 수. */\n readonly moved: number;\n readonly moveSkipped: number;\n /** 볼트에 실제로 쓴 항목 수 — 복원 · 옮기기도 쓰기다. 멱등 판정의 기준값. */\n readonly churn: number;\n readonly conflicts: number;\n readonly failed: readonly FailedOperation[];\n readonly writtenPaths: number;\n readonly images: number;\n readonly files: number;\n readonly links: number;\n readonly durationMs: number;\n /** 원격을 얼마나 훑었나 — 바뀐 것만 찾았으면 Notion 에서 지운 것은 다음 전체 대조가 반영한다. */\n readonly remoteScan: RemoteScanInfo | null;\n}\n\nexport function pushJson(result: PushResult): PushJson {\n return {\n created: result.created,\n updated: result.updated,\n deleted: result.deleted,\n moved: result.moved,\n churn:\n result.created +\n result.updated +\n result.deleted +\n result.moved +\n (result.createdDatabases ?? 0),\n createdDatabases: result.createdDatabases ?? 0,\n failed: result.failed,\n durationMs: result.duration,\n };\n}\n\nexport function pullJson(result: PullResult): PullJson {\n return {\n created: result.created,\n updated: result.updated,\n deleted: result.deleted,\n restored: result.restored,\n moved: result.moved?.length ?? 0,\n moveSkipped: result.moveSkipped?.length ?? 0,\n churn:\n result.created +\n result.updated +\n result.deleted +\n result.restored +\n (result.moved?.length ?? 0),\n conflicts: result.conflicts.length,\n failed: result.failed,\n writtenPaths: result.writtenPaths.length,\n images: result.imageCount,\n files: result.fileCount,\n links: result.linkCount,\n durationMs: result.duration,\n remoteScan: result.remoteScan ?? null,\n };\n}\n\nexport function printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value)}\\n`);\n}\n","import { Command, Option } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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 remoteScanLines,\n} from \"../utils/format.js\";\nimport { enableJsonMode, printJson, pullJson } from \"../utils/json-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.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 // 다시 그려 봐야 바뀌는지 안다 — 미리 보기로는 보일 것이 없다(B14).\n .addOption(\n new Option(\n \"--rerender\",\n \"Notion 에서 바뀌지 않은 받아 둔 노트도 지금 변환기로 다시 그림 (볼트에서 고친 노트는 그대로)\",\n ).conflicts(\"dryRun\"),\n )\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n if (!json) {\n console.log(`\\n${header(\" Pulling from Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n }\n\n const result = await orchestrator.pull({\n dryRun: options.dryRun,\n paths: options.path?.map((path: string) => vaultArgPath(path)),\n force: options.force,\n rerender: options.rerender,\n onProgress: json\n ? undefined\n : (current, total, item) => {\n // 받아 봤지만 바뀌지 않은 노트 — 다시 그리기는 받아 둔 노트를 모두 받아 본다(B14).\n if (item.unchanged) return;\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 (json) {\n printJson(pullJson(result));\n if (result.failed.length > 0) process.exitCode = 1;\n return;\n }\n\n if (result.imageCount > 0) {\n console.log(\n `\\n ${icons.success} ${result.imageCount} images → ${dimText(`${config.paths.attachments}/`)}`,\n );\n }\n if (result.fileCount > 0) {\n console.log(\n ` ${icons.success} ${result.fileCount} files → ${dimText(`${config.paths.attachments}/`)}`,\n );\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, result.moved?.length)} ${failedCountText(failedCount)}`,\n );\n // 복원은 별도 줄로 알린다 — 볼트에서 파일이 사라졌었다는 사실은 조용히 넘길 일이 아니다.\n if (result.restored > 0) {\n console.log(` ${icons.success} ${chalk.yellow(`${result.restored} restored`)}`);\n }\n // Notion 에서 옮긴 페이지 — 볼트의 노트가 어디로 갔는지 경로로 알린다(ADR-039).\n for (const move of result.moved ?? []) {\n console.log(\n ` ${icons.move} ${move.from} → ${move.to} ${dimText(options.dryRun ? \"(would move)\" : \"(moved)\")}`,\n );\n }\n for (const skip of result.moveSkipped ?? []) {\n console.log(` ${icons.conflict} ${skip.path} ${dimText(`(not moved: ${skip.reason})`)}`);\n }\n console.log(` ${duration(result.duration)}`);\n if (result.remoteScan) {\n for (const line of remoteScanLines(result.remoteScan)) console.log(` ${line}`);\n }\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 { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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 remoteScanLines,\n} from \"../utils/format.js\";\nimport { enableJsonMode, printJson, pullJson, pushJson } from \"../utils/json-output.js\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\nexport const syncCommand = new Command(\"sync\")\n .description(\"양방향 동기화 (Pull → Push)\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\n // 진행 로그는 사람용이다 — JSON 모드에서는 stdout 계약(JSON 한 줄)을 지키려고 끈다.\n const log = json ? () => {} : (line: string) => console.log(line);\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n log(`\\n${header(\" Bidirectional Sync\")}`);\n log(` ${separator()}`);\n if (options.dryRun) log(dimText(\" (dry-run mode)\"));\n\n // 받기 · 올리기를 한 번에 돈다 — 플러그인 · watch 와 같은 sync 다. push 는 pull 이 방금 쓴 노트를\n // 다시 올리지 않는다(F-j). 예전에는 pull · push 를 따로 불러 두 «sync» 가 따로 자랐다.\n log(`\\n ${header(chalk.blue(\"▼ Pull\"))} ${dimText(\"(Notion → Obsidian)\")}`);\n let pushShown = false;\n const showPush = (): void => {\n if (pushShown) return;\n pushShown = true;\n log(`\\n ${header(chalk.magenta(\"▲ Push\"))} ${dimText(\"(Obsidian → Notion)\")}`);\n };\n\n const result = await orchestrator.sync({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n if (item.direction === \"push\") showPush();\n if (item.unchanged) return;\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n log(` ${icon} ${item.path} ${label}`);\n },\n });\n showPush();\n const { pull: pullResult, push: pushResult } = result;\n\n if (json) {\n const pull = pullJson(pullResult);\n const push = pushJson(pushResult);\n printJson({\n pull,\n push,\n churn: pull.churn + push.churn,\n conflicts: pull.conflicts,\n durationMs: result.duration,\n });\n if (pullResult.failed.length + pushResult.failed.length > 0) process.exitCode = 1;\n return;\n }\n\n // Summary\n // 예전엔 양쪽 failed 를 아예 찍지 않아, 전 건 실패한 sync 도 화면 끝은 초록 \"complete\"\n // 였다 — 실패를 헤더와 목록 양쪽에 드러낸다.\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, pullResult.moved?.length)}` +\n // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.\n (pullResult.restored > 0 ? ` ${chalk.yellow(`${pullResult.restored} restored`)}` : \"\") +\n (pullFailed > 0 ? ` ${chalk.red(`${pullFailed} failed`)}` : \"\"),\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 console.log(\n ` ${chalk.magenta(\"Push:\")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted, pushResult.moved)}` +\n (pushFailed > 0 ? ` ${chalk.red(`${pushFailed} failed`)}` : \"\"),\n );\n console.log(` ${duration(result.duration)}`);\n if (pullResult.remoteScan) {\n for (const line of remoteScanLines(pullResult.remoteScan)) console.log(` ${line}`);\n }\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 (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 isRemoteDeletion,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport {\n header,\n separator,\n icons,\n dimText,\n localDateTime,\n lastFullScanText,\n remoteDeletionHint,\n} from \"../utils/format.js\";\nimport { nestedConnections, openVaultFs } from \"../utils/vault.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 = await openVaultFs(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 console.log(` ${dimText(\"Last sync:\")} ${localDateTime(status.lastSyncAt)}`);\n } else {\n console.log(` ${dimText(\"Last sync:\")} ${chalk.yellow(\"never\")}`);\n }\n // Notion 에서 지운 노트는 전체 대조 때 볼트에 반영된다 — 그때를 함께 보인다.\n console.log(` ${dimText(\"Full scan:\")} ${lastFullScanText(status.lastFullScanAt)}`);\n // 안쪽 연결 폴더(ADR-038)는 여기서 보지 않는다 — 그 폴더에서 따로 본다.\n for (const folder of await nestedConnections(cwd)) {\n console.log(\n ` ${dimText(\"Connection:\")} ${folder}/ ${dimText(`(separate — cd ${folder} && nobsi status)`)}`,\n );\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 const movedChanges = status.localChanges.filter((c) => c.type === \"moved\");\n\n // 추적 파일 내역은 서로 겹치지 않아야 한다. DB 의 status 는 push/pull 시점에만 갱신되므로\n // 방금 고친 파일도 레코드상으론 여전히 'synced' 다 — 그대로 세면 같은 파일이 synced 와\n // modified 양쪽에 잡혀 내역 합이 총계를 넘는 표가 나온다(무엇이 밀렸는지 못 읽는다).\n // 실제 변경이 걸린 경로를 먼저 덜어 내고 남은 것만 synced 로 센다.\n // 옮긴 노트의 레코드는 아직 옛 경로(movedFrom)를 적고 있다 — 두 경로를 다 덜어 낸다.\n const dirtyPaths = new Set([\n ...modifiedChanges.map((c) => c.path),\n ...deletedChanges.map((c) => c.path),\n ...movedChanges.flatMap((c) => [c.path, c.movedFrom ?? 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 const remoteMoved = status.remoteChanges.filter((c) => c.type === \"moved\");\n const { folderMoves } = status;\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 (movedChanges.length > 0) {\n console.log(` ${icons.move} ${chalk.cyan(\"moved\")} ${movedChanges.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 // Moved — 폴더를 먼저 보인다. 그 안의 노트는 아래에 노트마다 따로 있다.\n if (folderMoves.length + movedChanges.length > 0) {\n console.log(`\\n ${header(\"Moved:\")}`);\n for (const move of folderMoves) {\n console.log(` ${icons.move} ${move.from}/ → ${move.to}/ ${dimText(\"(folder)\")}`);\n }\n for (const c of movedChanges.slice(0, 10)) {\n console.log(` ${icons.move} ${c.movedFrom ?? \"?\"} → ${c.path}`);\n }\n if (movedChanges.length > 10) {\n console.log(dimText(` ... and ${movedChanges.length - 10} more`));\n }\n }\n\n // Deleted files — 수만 보이면 무엇이 Notion 에서 지워질지 모른다.\n if (deletedChanges.length > 0) {\n console.log(`\\n ${header(\"Deleted files:\")}`);\n for (const c of deletedChanges.slice(0, 10)) {\n console.log(` ${chalk.red(\"-\")} ${c.path}`);\n }\n if (deletedChanges.length > 10) {\n console.log(dimText(` ... and ${deletedChanges.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 (remoteMoved.length > 0) {\n console.log(` ${icons.move} ${chalk.cyan(\"moved\")} ${remoteMoved.length}`);\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 : c.type === \"moved\"\n ? icons.move\n : chalk.red(\"-\");\n // 볼트에 있는 노트는 경로, 아직 없는 새 페이지는 Notion 제목 — 내부 id 는 둘 다 없을 때만.\n const name = c.path ?? (c.title ? `${c.title} ${dimText(\"(new page)\")}` : undefined);\n console.log(\n ` ${icon} ${name ?? `${c.pageId.slice(0, 8)}...`} ${dimText(`(${c.type})`)}`,\n );\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 // 원격을 읽은 상태(--full)만 Notion 에서 지운 노트를 가른다.\n const remoteDeleted = new Set(\n status.conflicts.filter(isRemoteDeletion).map((c) => c.syncRecord.obsidianPath),\n );\n for (const r of status.conflictRecords) {\n const reason = remoteDeleted.has(r.obsidianPath)\n ? \"(deleted in Notion, local edits not pushed)\"\n : \"(both sides changed)\";\n console.log(` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(reason)}`);\n }\n }\n\n // Helpful commands\n const hasChanges =\n status.localChanges.length > 0 ||\n folderMoves.length > 0 ||\n status.remoteChanges.length > 0 ||\n 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 // 되돌릴 수 있는 것(고친 · 지운 노트)이 있을 때만 — git status 의 `git restore` 안내와 같다.\n if (modifiedChanges.length + deletedChanges.length > 0) {\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi discard <path>\")} ${dimText(\"to discard local edits\")}`,\n );\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 } else if (status.remoteScan?.deletionsDeferred) {\n // 바뀐 것만 찾았다 — 위 원격 변경에는 Notion 에서 지운 것이 아직 없다.\n console.log(`\\n ${remoteDeletionHint(status.remoteScan)}`);\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n formatUnifiedDiff,\n lineDiff,\n matchesPathScope,\n} from \"@im-nobsidian/core\";\nimport type { ChangeDiff } from \"@im-nobsidian/core\";\nimport { printUnifiedDiff } from \"../utils/diff-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\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 = await openVaultFs(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, vaultArgPath(targetPath)))\n : status.localChanges;\n\n if (changes.length === 0) {\n console.log(\"변경사항 없음\");\n return;\n }\n\n let failed = 0;\n for (const change of changes) {\n // --remote: 마지막 스냅샷이 아니라 Notion 의 현재 본문을 기준선으로 삼는다.\n // 원격이 그 사이 바뀌었을 때 \"내가 뭘 덮어쓰게 되는지\"는 이 비교로만 보인다.\n if (options.remote && change.type !== \"deleted\") {\n // 옮긴 노트는 Notion 에 반영하기 전까지 추적 기록이 옛 자리에 있다 — Notion 쪽 이름도 그 자리다.\n let remotePath = change.path;\n let remote = await orchestrator.renderRemoteSnapshot(remotePath);\n if (remote === null && change.movedFrom) {\n remotePath = change.movedFrom;\n remote = await orchestrator.renderRemoteSnapshot(remotePath);\n }\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 hunks = lineDiff(remote, currentContent);\n const lines = formatUnifiedDiff(hunks, `notion/${remotePath}`, `b/${change.path}`);\n // 이름 줄만 찍고 끝나면 같은 것인지 못 읽은 것인지 알 수 없다 — 같다고 적는다.\n if (hunks.length === 0) lines.push(\"(Notion 과 내용이 같습니다)\");\n printUnifiedDiff(lines, options.color);\n console.log(\"\");\n continue;\n }\n\n // 지난 동기화 때의 글과 지금 글 — 플러그인 변경 패널의 비교와 같은 두 글이다.\n let diff: ChangeDiff;\n try {\n diff = await orchestrator.localChangeDiff(change);\n } catch (error) {\n failed++;\n console.log(`--- a/${change.path}`);\n console.log(`(${error instanceof Error ? error.message : String(error)})`);\n console.log(\"\");\n continue;\n }\n printChangeDiff(diff, options.color);\n console.log(\"\");\n }\n // 보이지 못한 비교가 있으면 실패로 끝낸다 — 스크립트가 빠진 것을 알 수 있게.\n if (failed > 0) process.exitCode = 1;\n } finally {\n stateDb.close();\n }\n });\n\n/** 옮긴 노트는 Git 처럼 옛 자리 · 새 자리를 먼저 적는다. 내용이 같으면 비교 대신 그렇다고 알린다. */\nfunction printChangeDiff(diff: ChangeDiff, color: boolean): void {\n const oldPath = diff.before === null ? \"/dev/null\" : `a/${diff.movedFrom ?? diff.path}`;\n const newPath = diff.after === null ? \"/dev/null\" : `b/${diff.path}`;\n if (diff.movedFrom) {\n console.log(`rename from ${diff.movedFrom}`);\n console.log(`rename to ${diff.path}`);\n if (diff.before === diff.after) {\n console.log(\"(내용은 그대로입니다 — 자리만 옮겼습니다)\");\n return;\n }\n }\n printUnifiedDiff(\n formatUnifiedDiff(lineDiff(diff.before ?? \"\", diff.after ?? \"\"), oldPath, newPath),\n color,\n );\n}\n","import { DIFF_SIGN } from \"@im-nobsidian/core\";\n\nconst BOLD = \"\\x1b[1m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst CYAN = \"\\x1b[36m\";\nconst RESET = \"\\x1b[0m\";\n\n/** 줄 첫 글자마다의 색 — 더한 줄 · 지운 줄의 부호는 core 의 것을 따른다. `@` 는 묶음 머리. */\nconst LINE_COLOR: Readonly<Record<string, string>> = {\n [DIFF_SIGN.added]: GREEN,\n [DIFF_SIGN.removed]: RED,\n \"@\": CYAN,\n};\n\n/**\n * 통합 diff 줄(core `formatUnifiedDiff`)을 찍는다 — `diff` · `resolve` 가 같은 모양 · 같은 색으로 보인다.\n *\n * 옛 글 · 새 글 이름 줄은 첫 글자가 아니라 자리(앞 두 줄)로 가른다 — 구분선(`---`)을 지운 줄은 `----` 로\n * 시작해 첫 글자로는 이름 줄과 구별되지 않는다.\n */\nexport function printUnifiedDiff(lines: readonly string[], color: boolean): void {\n lines.forEach((line, index) => {\n const code = index < 2 ? BOLD : LINE_COLOR[line.charAt(0)];\n console.log(color && code ? `${code}${line}${RESET}` : line);\n });\n}\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport { icons, failedItem } from \"../utils/format.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\n// 변경 목록(status)에서 고른 노트를 지난 동기화 때의 글로 되돌린다 — Git 의 `restore` 와 같다.\n// 플러그인 변경 패널의 ↺ 와 같은 동작(orchestrator.discardLocalChange)이고 Notion 은 건드리지 않는다.\n// 하나를 되돌리지 못해도 나머지는 되돌린다 — 무엇이 왜 안 됐는지 경로마다 알리고 종료 코드로 남긴다.\nexport const discardCommand = new Command(\"discard\")\n .description(\"로컬 변경을 지난 동기화 때의 글로 되돌리기 (Notion 은 그대로)\")\n .argument(\"<paths...>\", \"되돌릴 노트 경로 — status 가 보인 볼트 기준 경로\")\n .action(async (paths: 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 try {\n const client = NotionClient.fromConfig(config);\n const vaultFs = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n let failed = 0;\n for (const path of paths) {\n try {\n await orchestrator.discardLocalChange(vaultArgPath(path));\n console.log(` ${icons.success} ${path}`);\n } catch (error) {\n failed++;\n failedItem(path, error instanceof Error ? error.message : String(error));\n }\n }\n if (failed > 0) process.exitCode = 1;\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { header, separator, dimText } from \"../utils/format.js\";\nimport { openVaultFs } from \"../utils/vault.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 = await openVaultFs(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 applicableChoices,\n isRemoteDeletion,\n} from \"@im-nobsidian/core\";\nimport type { Conflict, ResolutionChoice } from \"@im-nobsidian/core\";\nimport { printUnifiedDiff } from \"../utils/diff-output.js\";\nimport { describeFailure } from \"../utils/state-db-failure.js\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\n/** 허용 전략 — 오타를 조용히 흘리지 않도록 실행 전에 한 번 검증한다. */\nconst STRATEGIES = [\"local-first\", \"remote-first\", \"duplicate\"] as const;\ntype Strategy = (typeof STRATEGIES)[number];\n\nconst CHOICE_NAMES: Record<ResolutionChoice, string> = {\n local: \"로컬 유지 (Obsidian 버전 유지)\",\n remote: \"원격 유지 (Notion 버전으로 덮어쓰기)\",\n merge: \"자동 병합 (3-way merge)\",\n duplicate: \"복제 (원본 유지 + 올리지 않는 .conflict 사본 생성)\",\n};\n\n/** Notion 에서 지운 노트는 같은 선택이 다른 일을 한다 — 로컬 유지는 다시 만들고, 원격 유지는 지운다. */\nconst REMOTE_DELETION_CHOICE_NAMES: Partial<Record<ResolutionChoice, string>> = {\n local: \"로컬 유지 (Notion 에 새 페이지로 다시 만들기)\",\n remote: \"삭제 따르기 (Obsidian 파일도 지우기)\",\n};\n\n/** 받기 전용(direction: pull)은 해결 결과를 Notion 에 올리지 않는다 — 다시 만들지도 않는다. */\nconst PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES: Partial<Record<ResolutionChoice, string>> = {\n local: \"로컬 유지 (Obsidian 파일만 두기 — 받기 전용이라 Notion 에 만들지 않음)\",\n};\n\nfunction choiceName(conflict: Conflict, choice: ResolutionChoice, uploads: boolean): string {\n const deleted = isRemoteDeletion(conflict);\n return (\n (deleted && !uploads ? PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES[choice] : undefined) ??\n (deleted ? REMOTE_DELETION_CHOICE_NAMES[choice] : undefined) ??\n CHOICE_NAMES[choice] ??\n choice\n );\n}\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 = await openVaultFs(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 const uploads = config.sync.direction !== \"pull\";\n if (!uploads) {\n console.log(\n \" 받기 전용(direction: pull) — 해결 결과는 Obsidian 에만 두고 Notion 에는 올리지 않습니다\\n\",\n );\n }\n\n if (options.strategy) {\n const strategy = options.strategy as Strategy;\n // 원격에서 지운 노트는 전략이 파일을 지우거나 페이지를 새로 만든다 — 묻기 전에 알린다.\n const deletions = pending.filter(isRemoteDeletion).length;\n if (deletions > 0) {\n console.log(\n ` Notion 에서 삭제된 노트 ${deletions}건 — ${\n strategy === \"remote-first\"\n ? \"Obsidian 파일도 지웁니다\"\n : uploads\n ? \"Notion 에 새 페이지로 다시 만듭니다\"\n : \"Obsidian 파일을 그대로 둡니다\"\n }`,\n );\n }\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 [index, result] of results.entries()) {\n if (!result.success) failed++;\n const icon = result.success ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[31m✗\\x1b[0m\";\n const reason = result.error ? ` — ${result.error}` : \"\";\n console.log(\n ` ${icon} ${result.path} → ${choiceName(pending[index]!, result.choice, uploads)}${reason}`,\n );\n }\n report(results.length - failed, failed);\n return;\n }\n\n if (!isInteractive()) {\n console.error(\n `비대화형 환경에서는 대화형 해결을 쓸 수 없습니다 — --strategy <${STRATEGIES.join(\"|\")}> --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, uploads)) resolved++;\n else failed++;\n }\n report(resolved, failed);\n } catch (error) {\n console.error(\"오류:\", describeFailure(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 uploads: boolean,\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 if (isRemoteDeletion(conflict)) {\n // 견줄 원격 본문이 없다 — 줄 비교는 로컬 전부를 지운 것으로 보여 준다.\n console.log(\n \"\\x1b[33mNotion 에서 삭제된 노트입니다 — Notion 에 올리지 않은 로컬 편집이 남아 있습니다.\\x1b[0m\",\n );\n } else {\n printUnifiedDiff(orchestrator.generateConflictDiff(conflict).split(\"\\n\"), true);\n }\n\n const choice = await select<ResolutionChoice>({\n message: \"어떻게 해결할까요?\",\n choices: applicableChoices(conflict).map((value) => ({\n name: choiceName(conflict, value, uploads),\n value,\n })),\n });\n\n const result = await orchestrator.resolveConflict(conflict, choice);\n\n if (result.success) {\n console.log(`\\x1b[32m✓ ${path} → ${choiceName(conflict, choice, uploads)}\\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","/**\n * 상태 DB 를 쓸 수 없을 때의 CLI 출력 — 이유 뒤에 할 일을 붙인다.\n *\n * 명령은 처음에 상태 DB 를 연다(`StateDB.open`). 파일이 비었거나 잘렸거나(`SavedStateDbError`), 플러그인이 같은\n * 볼트를 쓰는 중이면(`StateDbLockedError`) 던지는데, 명령 밖으로 나오면 Node 가 코드 위치(스택)와 함께 이유만\n * 보였다 — 사용자가 할 일은 어디에도 없었다.\n */\nimport { StateDbUnavailableError } from \"@im-nobsidian/core\";\nimport type { ExitableProcess } from \"./exit.js\";\n\n/** 실패를 보일 말 — 상태 DB 를 쓸 수 없는 것이면 할 일을 붙이고, 다른 실패는 그 말 그대로다. */\nexport function describeFailure(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (!(error instanceof StateDbUnavailableError)) return message;\n return `${message} — ${error.guidance(\"명령을 다시 실행하세요\")}`;\n}\n\n/**\n * 명령 밖으로 나온 실패를 알린다. 상태 DB 를 쓸 수 없는 것은 사용자가 풀 일이라 이유와 할 일만 보이고 실패(1)로\n * 끝낸다. 다른 실패는 그대로 다시 던진다 — 지금까지처럼 Node 가 스택과 함께 보이고 1 로 끝난다.\n */\nexport function reportStateDbFailure(error: unknown, proc: ExitableProcess): void {\n if (!(error instanceof StateDbUnavailableError)) throw error;\n console.error(`오류: ${describeFailure(error)}`);\n proc.exitCode = 1;\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n WatchSyncService,\n DEFAULT_WATCH_DEBOUNCE_MS,\n} from \"@im-nobsidian/core\";\nimport type { SyncResult, WatchSyncScope } from \"@im-nobsidian/core\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\nfunction formatResult(result: SyncResult): string {\n const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;\n const moved = result.push.moved > 0 ? ` →${result.push.moved}` : \"\";\n const push = `Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}${moved}`;\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\n/** 로그의 sync 이름 — 주기 sync 는 볼트 전체를 본다. */\nfunction syncLabel(scope: WatchSyncScope): string {\n return scope === \"full\" ? \"주기적 동기화\" : \"동기화\";\n}\n\nexport const watchCommand = new Command(\"watch\")\n .description(\"파일 변경 감지 + 자동 동기화\")\n .option(\"-d, --debounce <ms>\", \"디바운스 대기 시간(ms)\", String(DEFAULT_WATCH_DEBOUNCE_MS))\n .option(\"-i, --interval <seconds>\", \"풀 동기화 주기(초, 0이면 비활성)\", \"0\")\n .action(async (options: { debounce: string; interval: string }) => {\n // 숫자가 아니면 디바운스가 0 이 되거나 주기 동기화가 말없이 꺼진다 — 시작하기 전에 거절한다.\n const debounceMs = Number(options.debounce);\n const intervalSec = Number(options.interval);\n if (!Number.isInteger(debounceMs) || debounceMs < 0) {\n console.error(`--debounce 는 0 이상의 정수(ms)여야 합니다: ${options.debounce}`);\n process.exitCode = 1;\n return;\n }\n if (!Number.isInteger(intervalSec) || intervalSec < 0) {\n console.error(`--interval 은 0 이상의 정수(초)여야 합니다: ${options.interval}`);\n process.exitCode = 1;\n return;\n }\n\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const service = new WatchSyncService(cwd, orchestrator, {\n debounceMs,\n onFileChange: (event, path) => {\n console.log(`[${timestamp()}] ${event}: ${path}`);\n },\n onSyncStart: (scope) => {\n console.log(`[${timestamp()}] ${syncLabel(scope)} 시작...`);\n },\n onSyncComplete: (result, scope) => {\n console.log(`[${timestamp()}] ${syncLabel(scope)} 완료 — ${formatResult(result)}`);\n },\n onSyncCancelled: (result, scope) => {\n console.log(\n `[${timestamp()}] ${syncLabel(scope)} 취소 — 멈추기 전까지 ${formatResult(result)}`,\n );\n },\n onSyncError: (error, scope) => {\n console.error(`[${timestamp()}] ${syncLabel(scope)} 실패: ${error.message}`);\n },\n });\n\n let pullTimer: ReturnType<typeof setInterval> | null = null;\n let shuttingDown = false;\n\n const shutdown = async (): Promise<void> => {\n // 도는 sync 를 기다리는 중에 한 번 더 누르면 기다리지 않고 끝낸다.\n if (shuttingDown) {\n console.log(`[${timestamp()}] 강제 종료`);\n return process.exit(1);\n }\n shuttingDown = true;\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 // 파일 변경 sync 와 같은 줄에 세운다 — 도는 sync 가 있으면 끝난 뒤 한 번 돈다(S-09).\n pullTimer = setInterval(() => service.requestFullSync(), intervalSec * 1000);\n }\n\n await new Promise(() => {});\n });\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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\";\nimport { openVaultFs } from \"../utils/vault.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 = await openVaultFs(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","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { icons, failedItem } from \"../utils/format.js\";\nimport { obsidianConfigDir, openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\n// 볼트에서 만든 노트 폴더를 Notion DB 로 올릴 준비(#75) — frontmatter · `.base` 로 사이드카를 써 둔다.\n// Notion 에는 쓰지 않는다. 사이드카는 JSON 이라 push 전에 고칠 수 있다 — 무엇을 왜 그렇게 정했는지 보인다.\nexport const prepareDbCommand = new Command(\"prepare-db\")\n .description(\"볼트의 노트 폴더를 다음 push 에서 Notion DB 로 만들도록 준비 (Notion 은 그대로)\")\n .argument(\"<folder>\", \"DB 로 만들 폴더 — 볼트 기준 경로. 바로 아래 노트가 행이 된다\")\n .action(async (folder: 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 try {\n const client = NotionClient.fromConfig(config);\n const vaultFs = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n const report = await orchestrator.prepareFolderDatabase(\n vaultArgPath(folder),\n obsidianConfigDir(cwd),\n );\n\n console.log(` ${icons.success} ${report.sidecarPath} — 행 ${report.rows}개`);\n for (const property of report.properties) {\n console.log(` ${property.name}: ${property.type} ${chalk.dim(`(${property.reason})`)}`);\n }\n if (report.views.length > 0) console.log(` 보기: ${report.views.join(\", \")}`);\n for (const skip of report.skipped) {\n console.log(chalk.dim(` 옮기지 않음 — ${skip.what}: ${skip.reason}`));\n }\n console.log(chalk.dim(\" 타입을 바꾸려면 사이드카를 고친 뒤 push 하세요.\"));\n } catch (error) {\n failedItem(folder, error instanceof Error ? error.message : String(error));\n 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","ConfigManager","chalk","ConfigManager","chalk","spinner","Command","ConfigManager","StateDB","NotionClient","Command","ConfigManager","StateDB","NotionClient","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","remoteDeleted","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","matchesPathScope","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","matchesPathScope","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","confirm","ConfigManager","StateDB","NotionClient","SyncOrchestrator","isRemoteDeletion","isRemoteDeletion","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","confirm","failed","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","report","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","report","chalk","Command"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/format.ts","../src/utils/vault.ts","../src/commands/push.ts","../src/utils/json-output.ts","../src/commands/pull.ts","../src/commands/sync.ts","../src/commands/status.ts","../src/commands/diff.ts","../src/utils/diff-output.ts","../src/commands/discard.ts","../src/commands/fetch.ts","../src/commands/resolve.ts","../src/utils/state-db-failure.ts","../src/commands/watch.ts","../src/commands/verify.ts","../src/commands/prepare-db.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 { discardCommand } from \"./commands/discard.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 { prepareDbCommand } from \"./commands/prepare-db.js\";\nimport { scheduleForcedExit } from \"./utils/exit.js\";\nimport { reportStateDbFailure } from \"./utils/state-db-failure.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(discardCommand);\nprogram.addCommand(fetchCommand);\nprogram.addCommand(resolveCommand);\nprogram.addCommand(watchCommand);\nprogram.addCommand(verifyCommand);\nprogram.addCommand(prepareDbCommand);\n\n// 강제 종료하되 명령이 정한 종료 코드를 보존한다 — 근거는 utils/exit.ts 참조(R11-C).\n// 상태 DB 를 쓸 수 없어 명령이 던졌으면 이유와 할 일을 보이고 1 로 끝낸다 — utils/state-db-failure.ts.\nprogram.parseAsync().then(\n () => {\n scheduleForcedExit(process);\n },\n (error: unknown) => {\n reportStateDbFailure(error, process);\n scheduleForcedExit(process);\n },\n);\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n NOTION_TOKEN_PREFIX,\n NotionClient,\n notionIdFromInput,\n} 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\";\nimport { outerConnectionTracking } from \"../utils/vault.js\";\n\n/** 루트 페이지에서 ID 를 못 찾았을 때 — 주소를 그대로 붙여 넣어도 된다고 말한다. */\nconst ROOT_PAGE_UNREADABLE =\n \"루트 페이지에서 Notion 페이지 ID(32자리)를 찾지 못했습니다 — 페이지 주소나 ID 를 넣으세요.\";\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 // 바깥 연결이 이미 이 폴더를 동기화하면 따로 연결하지 않는다(ADR-038) — 바깥 연결이 멈춘다.\n const outer = outerConnectionTracking(cwd);\n if (outer) {\n console.error(\n `이 폴더는 이미 ${outer.folder} 의 연결이 노트 ${outer.notes}개를 동기화하고 있어 따로 연결할 수 없습니다.`,\n );\n process.exitCode = 1;\n return;\n }\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(NOTION_TOKEN_PREFIX)) {\n console.error(`토큰은 ${NOTION_TOKEN_PREFIX}으로 시작해야 합니다.`);\n process.exitCode = 1;\n return;\n }\n\n const rootPageId = notionIdFromInput(options.rootPageId);\n if (rootPageId === null) {\n console.error(ROOT_PAGE_UNREADABLE);\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 });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\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) =>\n v.startsWith(NOTION_TOKEN_PREFIX) ||\n `${NOTION_TOKEN_PREFIX}으로 시작하는 토큰을 입력하세요`,\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 rootPageInput = await input({\n message: \"루트 페이지 ID 또는 주소 (위에서 선택하거나 직접 입력):\",\n default: result.results[0]?.id,\n validate: (value) => notionIdFromInput(value) !== null || ROOT_PAGE_UNREADABLE,\n });\n const rootPageId = notionIdFromInput(rootPageInput)!;\n\n await configManager.init({ token, rootPageId });\n console.log(`\\n ${icons.success} Config saved to ${dimText(configManager.configPath)}`);\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\";\nimport type { FullScanReason, ProgressItem, RemoteScanInfo } from \"@im-nobsidian/core\";\n\nexport const icons = {\n create: chalk.green(\"+\"),\n update: chalk.yellow(\"~\"),\n delete: chalk.red(\"-\"),\n move: chalk.cyan(\"→\"),\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: ProgressItem[\"operation\"]): string {\n return icons[op];\n}\n\nexport function operationLabel(op: ProgressItem[\"operation\"]): 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 case \"move\":\n return chalk.cyan(\"moved\");\n }\n}\n\n/** 결과 수 한 줄. `moved` 는 push 만 센다 — 옮긴 노트 · 폴더(본문을 함께 고친 것도). */\nexport function summary(created: number, updated: number, deleted: number, moved = 0): 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 (moved > 0) parts.push(chalk.cyan(`${moved} moved`));\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\n/** 로컬 시각 `YYYY-MM-DD HH:MM:SS`. 읽을 수 없는 값은 그대로 보인다 — NaN 으로 바꾸지 않는다. */\nexport function localDateTime(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return iso;\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n );\n}\n\n/** 전체 대조한 이유. 코어의 이유 코드가 늘면 여기가 컴파일되지 않는다 — 빠진 말을 조용히 두지 않는다. */\nconst FULL_SCAN_REASON: Record<FullScanReason, string> = {\n first: \"first pull\",\n forced: \"--force\",\n \"database-mode\": \"database mode\",\n \"every-pull\": \"every pull\",\n due: \"scheduled\",\n};\n\n/**\n * 이번에 원격을 얼마나 훑었나 (ADR-027). 바뀐 것만 찾았으면 Notion 에서 지운 것은 아직 볼트에 반영되지\n * 않았을 수 있다 — 그 사실과 언제 반영되는지를 함께 적는다. 적지 않으면 «Notion 에서 지웠는데 볼트에\n * 남았다» 를 결함으로 읽는다. 원격 삭제를 반영하지 않는 설정(`sync.deleteSync` 꺼짐)이면 적지 않는다.\n */\nexport function remoteScanLines(scan: RemoteScanInfo): string[] {\n if (scan.kind === \"full\") {\n return [dimText(scan.reason ? `Full scan (${FULL_SCAN_REASON[scan.reason]})` : \"Full scan\")];\n }\n const skipped = scan.skippedDatabases ?? 0;\n const skippedText =\n skipped > 0\n ? ` · ${skipped} unchanged ${skipped === 1 ? \"database\" : \"databases\"} skipped`\n : \"\";\n const lines = [dimText(`Changes-only scan${skippedText}`)];\n if (scan.deletionsDeferred) lines.push(remoteDeletionHint(scan));\n return lines;\n}\n\n/** 미룬 원격 삭제가 언제 반영되나 — {@link RemoteScanInfo.deletionsDeferred} 일 때 쓴다. */\nexport function remoteDeletionHint(scan: RemoteScanInfo): string {\n const when = scan.nextFullAt\n ? `the next full scan (${localDateTime(scan.nextFullAt)})`\n : \"the next pull (full scan due)\";\n return `${dimText(`Deletions in Notion apply at ${when} — or run`)} ${chalk.cyan(\"nobsi pull --force\")}`;\n}\n\n/** 마지막 전체 대조 시각 — 한 번도 없으면 never. */\nexport function lastFullScanText(lastFullScanAt: string | null): string {\n return lastFullScanAt ? localDateTime(lastFullScanAt) : chalk.yellow(\"never\");\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\n/**\n * 실패 한 줄. 볼트 최상위의 실패(DB 자동 발견 등)는 빈 경로로 오므로 `/` 로 보인다. `direction` 은\n * sync 가 받기(▼) · 올리기(▲)를 가를 때 쓴다.\n */\nexport function failedItem(path: string, error: string, direction?: string): void {\n const where = `${direction ? `${direction} ` : \"\"}${path || \"/\"}`;\n console.log(` ${icons.fail} ${where}: ${chalk.red(error)}`);\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\nimport {\n ConfigManager,\n NodeVaultFS,\n StateDB,\n matchesPathScope,\n ScopedVaultFS,\n findConnectionFolders,\n OBSIDIAN_CONFIG_DIR,\n type PathFilterConfig,\n type VaultFS,\n} from \"@im-nobsidian/core\";\n\n/** 이 폴더 안쪽의 다른 연결 폴더(ADR-038) — 이 폴더의 동기화가 비켜 간다. */\nexport async function nestedConnections(cwd: string): Promise<string[]> {\n return (await findConnectionFolders(cwd)).filter((folder) => folder !== \"\");\n}\n\n/**\n * 현재 폴더를 볼트로 다루는 VaultFS. 안쪽에 다른 연결 폴더가 있으면 그 폴더는 올리지도 덮어쓰지도 않는다 —\n * 그 폴더는 자기 설정 · Notion 으로 따로 동기화한다(`cd <폴더> && nobsi sync`).\n */\nexport async function openVaultFs(cwd: string, paths: PathFilterConfig): Promise<VaultFS> {\n const base = new NodeVaultFS(cwd, paths);\n const nested = await nestedConnections(cwd);\n return nested.length === 0 ? base : new ScopedVaultFS(base, \"\", [\"\", ...nested]);\n}\n\n/**\n * 명령줄에 받은 볼트 경로 → 볼트 경로(`/`). Windows 에서는 `00 Me\\a.md` 처럼 `\\` 로 쳐도 같은 노트다(이슈 #8) —\n * 예전에는 `-p \"00 Me\\\"` 가 어느 노트에도 맞지 않아 「변경 없음」 이었다. 다른 OS 에서 `\\` 는 이름의 글자다.\n */\nexport function vaultArgPath(path: string, platform: NodeJS.Platform = process.platform): string {\n return platform === \"win32\" ? path.replace(/\\\\/g, \"/\") : path;\n}\n\n/**\n * Obsidian 설정 폴더까지의 상대 경로 — 연결 폴더가 볼트 안쪽이면 위로 올라가 찾는다. 못 찾으면 `.obsidian`\n * (없으면 속성 타입을 값으로 정한다).\n */\nexport function obsidianConfigDir(cwd: string): string {\n for (let dir = cwd; ; dir = dirname(dir)) {\n if (existsSync(join(dir, OBSIDIAN_CONFIG_DIR))) {\n return relative(cwd, join(dir, OBSIDIAN_CONFIG_DIR)).split(\"\\\\\").join(\"/\");\n }\n if (dirname(dir) === dir) return OBSIDIAN_CONFIG_DIR;\n }\n}\n\n/**\n * 이 폴더를 이미 동기화하는 바깥 연결 — 그 연결 폴더와 이 폴더 안에서 추적하는 노트 수. 없으면 null.\n *\n * 이미 동기화하던 폴더를 따로 연결하면 바깥 연결이 멈춘다(ConnectionScopeError) — init 이 먼저 막는다.\n */\nexport function outerConnectionTracking(cwd: string): { folder: string; notes: number } | null {\n for (let dir = dirname(cwd); ; dir = dirname(dir)) {\n const manager = new ConfigManager(dir);\n if (existsSync(manager.dbPath)) {\n const scope = relative(dir, cwd).split(\"\\\\\").join(\"/\");\n const db = StateDB.open(manager.dbPath);\n try {\n // 폴더 자신의 레코드(폴더 페이지)는 빼고 그 안의 것만 센다.\n const notes = db\n .getAll()\n .filter(\n (r) => r.obsidianPath !== scope && matchesPathScope(r.obsidianPath, scope),\n ).length;\n return notes > 0 ? { folder: dir, notes } : null;\n } finally {\n db.close();\n }\n }\n if (dirname(dir) === dir) return null;\n }\n}\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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\";\nimport { enableJsonMode, printJson, pushJson } from \"../utils/json-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\nexport const pushCommand = new Command(\"push\")\n .description(\"로컬 변경사항을 Notion에 반영\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"-p, --path <paths...>\", \"특정 경로만 push\")\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n if (!json) {\n console.log(`\\n${header(\" Pushing to Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n }\n\n const result = await orchestrator.push({\n dryRun: options.dryRun,\n paths: options.path?.map((path: string) => vaultArgPath(path)),\n onProgress: json\n ? undefined\n : (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 (json) {\n printJson(pushJson(result));\n if (result.failed.length > 0) process.exitCode = 1;\n return;\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, result.moved)} ${failedCountText(failedCount)}`,\n );\n if (result.createdDatabases) {\n console.log(` ${options.dryRun ? \"만들 DB\" : \"만든 DB\"} ${result.createdDatabases}`);\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 { setLogger } from \"@im-nobsidian/core\";\nimport type { FailedOperation, PullResult, PushResult, RemoteScanInfo } from \"@im-nobsidian/core\";\n\n/**\n * `--json` 모드 — 자동화(E2E 하니스 · CI)가 사람용 문구를 grep 하지 않고 결과를 읽게 한다.\n *\n * 문구 grep 은 조용히 틀렸다. sync 로그에 Pull 쪽 \"no changes\" 가 한 줄만 있어도 Push 쪽\n * 변경과 무관하게 churn 0 으로 판정했고, restored · deleted 는 아예 세지 않았다.\n *\n * 계약: stdout 에는 JSON 한 줄만 쓴다. 코어 로그는 전부 stderr 로 돌린다.\n */\nexport function enableJsonMode(): void {\n const toStderr = (message: string, ...args: unknown[]): void => console.error(message, ...args);\n setLogger({ warn: toStderr, error: toStderr, info: toStderr, debug: () => {} });\n}\n\nexport interface PushJson {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n /** 옮김을 반영한 노트 · 폴더 수 — updated 와 겹치지 않는다. */\n readonly moved: number;\n /** 원격에 실제로 쓴(또는 dry-run 이면 쓸) 항목 수. 멱등 판정의 기준값. */\n readonly churn: number;\n /** 사이드카로 만든(dry-run 은 만들) Notion DB 수 — churn 에도 든다. */\n readonly createdDatabases: number;\n readonly failed: readonly FailedOperation[];\n readonly durationMs: number;\n}\n\nexport interface PullJson {\n readonly created: number;\n readonly updated: number;\n readonly deleted: number;\n readonly restored: number;\n /** Notion 에서 옮긴 페이지를 볼트에서 옮긴 수(ADR-039) · 옮기지 않고 둔 수. */\n readonly moved: number;\n readonly moveSkipped: number;\n /** 그중 Notion 에서 바꾼 제목으로 이름을 바꾼 수(ADR-040) · 옮긴 노트를 가리키던 링크를 고친 노트 수. */\n readonly renamed: number;\n readonly relinked: number;\n /** Notion 에 새로 생겼지만 뺀 자리(paths · .im-nobsidian-ignore)에 놓일 것이라 받지 않은 페이지 수. */\n readonly excluded: number;\n /** 볼트에 실제로 쓴 항목 수 — 복원 · 옮기기도 쓰기다. 멱등 판정의 기준값. */\n readonly churn: number;\n readonly conflicts: number;\n readonly failed: readonly FailedOperation[];\n readonly writtenPaths: number;\n readonly images: number;\n readonly files: number;\n readonly links: number;\n readonly durationMs: number;\n /** 원격을 얼마나 훑었나 — 바뀐 것만 찾았으면 Notion 에서 지운 것은 다음 전체 대조가 반영한다. */\n readonly remoteScan: RemoteScanInfo | null;\n}\n\nexport function pushJson(result: PushResult): PushJson {\n return {\n created: result.created,\n updated: result.updated,\n deleted: result.deleted,\n moved: result.moved,\n churn:\n result.created +\n result.updated +\n result.deleted +\n result.moved +\n (result.createdDatabases ?? 0),\n createdDatabases: result.createdDatabases ?? 0,\n failed: result.failed,\n durationMs: result.duration,\n };\n}\n\nexport function pullJson(result: PullResult): PullJson {\n return {\n created: result.created,\n updated: result.updated,\n deleted: result.deleted,\n restored: result.restored,\n moved: result.moved?.length ?? 0,\n moveSkipped: result.moveSkipped?.length ?? 0,\n renamed: result.moved?.filter((move) => move.renamed).length ?? 0,\n relinked: result.relinked?.length ?? 0,\n excluded: result.excluded?.length ?? 0,\n churn:\n result.created +\n result.updated +\n result.deleted +\n result.restored +\n (result.moved?.length ?? 0) +\n (result.relinked?.length ?? 0),\n conflicts: result.conflicts.length,\n failed: result.failed,\n writtenPaths: result.writtenPaths.length,\n images: result.imageCount,\n files: result.fileCount,\n links: result.linkCount,\n durationMs: result.duration,\n remoteScan: result.remoteScan ?? null,\n };\n}\n\nexport function printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value)}\\n`);\n}\n","import { Command, Option } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n IGNORE_FILE,\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 remoteScanLines,\n} from \"../utils/format.js\";\nimport { enableJsonMode, printJson, pullJson } from \"../utils/json-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.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 // 다시 그려 봐야 바뀌는지 안다 — 미리 보기로는 보일 것이 없다(B14).\n .addOption(\n new Option(\n \"--rerender\",\n \"Notion 에서 바뀌지 않은 받아 둔 노트도 지금 변환기로 다시 그림 (볼트에서 고친 노트는 그대로)\",\n ).conflicts(\"dryRun\"),\n )\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n if (!json) {\n console.log(`\\n${header(\" Pulling from Notion...\")}`);\n if (options.dryRun) console.log(dimText(\" (dry-run mode)\"));\n console.log(\"\");\n }\n\n const result = await orchestrator.pull({\n dryRun: options.dryRun,\n paths: options.path?.map((path: string) => vaultArgPath(path)),\n force: options.force,\n rerender: options.rerender,\n onProgress: json\n ? undefined\n : (current, total, item) => {\n // 받아 봤지만 바뀌지 않은 노트 — 다시 그리기는 받아 둔 노트를 모두 받아 본다(B14).\n if (item.unchanged) return;\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 (json) {\n printJson(pullJson(result));\n if (result.failed.length > 0) process.exitCode = 1;\n return;\n }\n\n if (result.imageCount > 0) {\n console.log(\n `\\n ${icons.success} ${result.imageCount} images → ${dimText(`${config.paths.attachments}/`)}`,\n );\n }\n if (result.fileCount > 0) {\n console.log(\n ` ${icons.success} ${result.fileCount} files → ${dimText(`${config.paths.attachments}/`)}`,\n );\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, result.moved?.length)} ${failedCountText(failedCount)}`,\n );\n // 복원은 별도 줄로 알린다 — 볼트에서 파일이 사라졌었다는 사실은 조용히 넘길 일이 아니다.\n if (result.restored > 0) {\n console.log(` ${icons.success} ${chalk.yellow(`${result.restored} restored`)}`);\n }\n // Notion 에서 옮긴 페이지 · 바꾼 제목 — 볼트의 노트가 어디로 갔는지 경로로 알린다(ADR-039 · ADR-040).\n for (const move of result.moved ?? []) {\n const verb = move.renamed ? \"rename\" : \"move\";\n console.log(\n ` ${icons.move} ${move.from} → ${move.to} ${dimText(options.dryRun ? `(would ${verb})` : `(${verb}d)`)}`,\n );\n }\n const excluded = result.excluded ?? [];\n if (excluded.length > 0) {\n console.log(\n ` ${icons.conflict} ${dimText(`${excluded.length} new in Notion ${options.dryRun ? \"would not be\" : \"not\"} pulled — excluded by paths / ${IGNORE_FILE}:`)}`,\n );\n for (const path of excluded.slice(0, 10)) console.log(` ${dimText(`· ${path}`)}`);\n if (excluded.length > 10) console.log(dimText(` ... and ${excluded.length - 10} more`));\n }\n if ((result.relinked?.length ?? 0) > 0) {\n console.log(\n ` ${icons.success} ${dimText(`links updated in ${result.relinked!.length} notes`)}`,\n );\n }\n for (const skip of result.moveSkipped ?? []) {\n console.log(` ${icons.conflict} ${skip.path} ${dimText(`(not moved: ${skip.reason})`)}`);\n }\n console.log(` ${duration(result.duration)}`);\n if (result.remoteScan) {\n for (const line of remoteScanLines(result.remoteScan)) console.log(` ${line}`);\n }\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 { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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 remoteScanLines,\n} from \"../utils/format.js\";\nimport { enableJsonMode, printJson, pullJson, pushJson } from \"../utils/json-output.js\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\nexport const syncCommand = new Command(\"sync\")\n .description(\"양방향 동기화 (Pull → Push)\")\n .option(\"--dry-run\", \"실제 반영 없이 변경사항만 표시\")\n .option(\"--json\", \"결과를 JSON 한 줄로 출력 (자동화용 — 로그는 stderr)\")\n .action(async (options) => {\n const json = options.json === true;\n if (json) enableJsonMode();\n // 진행 로그는 사람용이다 — JSON 모드에서는 stdout 계약(JSON 한 줄)을 지키려고 끈다.\n const log = json ? () => {} : (line: string) => console.log(line);\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n log(`\\n${header(\" Bidirectional Sync\")}`);\n log(` ${separator()}`);\n if (options.dryRun) log(dimText(\" (dry-run mode)\"));\n\n // 받기 · 올리기를 한 번에 돈다 — 플러그인 · watch 와 같은 sync 다. push 는 pull 이 방금 쓴 노트를\n // 다시 올리지 않는다(F-j). 예전에는 pull · push 를 따로 불러 두 «sync» 가 따로 자랐다.\n log(`\\n ${header(chalk.blue(\"▼ Pull\"))} ${dimText(\"(Notion → Obsidian)\")}`);\n let pushShown = false;\n const showPush = (): void => {\n if (pushShown) return;\n pushShown = true;\n log(`\\n ${header(chalk.magenta(\"▲ Push\"))} ${dimText(\"(Obsidian → Notion)\")}`);\n };\n\n const result = await orchestrator.sync({\n dryRun: options.dryRun,\n onProgress: (_current, _total, item) => {\n if (item.direction === \"push\") showPush();\n if (item.unchanged) return;\n const icon = operationIcon(item.operation);\n const label = operationLabel(item.operation);\n log(` ${icon} ${item.path} ${label}`);\n },\n });\n showPush();\n const { pull: pullResult, push: pushResult } = result;\n\n if (json) {\n const pull = pullJson(pullResult);\n const push = pushJson(pushResult);\n printJson({\n pull,\n push,\n churn: pull.churn + push.churn,\n conflicts: pull.conflicts,\n durationMs: result.duration,\n });\n if (pullResult.failed.length + pushResult.failed.length > 0) process.exitCode = 1;\n return;\n }\n\n // Summary\n // 예전엔 양쪽 failed 를 아예 찍지 않아, 전 건 실패한 sync 도 화면 끝은 초록 \"complete\"\n // 였다 — 실패를 헤더와 목록 양쪽에 드러낸다.\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, pullResult.moved?.length)}` +\n // 사라졌던 파일이 되살아난 경우에만 덧붙인다 — 평상시 요약을 어지럽히지 않는다.\n (pullResult.restored > 0 ? ` ${chalk.yellow(`${pullResult.restored} restored`)}` : \"\") +\n (pullFailed > 0 ? ` ${chalk.red(`${pullFailed} failed`)}` : \"\"),\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 console.log(\n ` ${chalk.magenta(\"Push:\")} ${summary(pushResult.created, pushResult.updated, pushResult.deleted, pushResult.moved)}` +\n (pushFailed > 0 ? ` ${chalk.red(`${pushFailed} failed`)}` : \"\"),\n );\n console.log(` ${duration(result.duration)}`);\n if (pullResult.remoteScan) {\n for (const line of remoteScanLines(pullResult.remoteScan)) console.log(` ${line}`);\n }\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 (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 IGNORE_FILE,\n isRemoteDeletion,\n} from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport {\n header,\n separator,\n icons,\n dimText,\n localDateTime,\n lastFullScanText,\n remoteDeletionHint,\n} from \"../utils/format.js\";\nimport { nestedConnections, openVaultFs } from \"../utils/vault.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 = await openVaultFs(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 console.log(` ${dimText(\"Last sync:\")} ${localDateTime(status.lastSyncAt)}`);\n } else {\n console.log(` ${dimText(\"Last sync:\")} ${chalk.yellow(\"never\")}`);\n }\n // Notion 에서 지운 노트는 전체 대조 때 볼트에 반영된다 — 그때를 함께 보인다.\n console.log(` ${dimText(\"Full scan:\")} ${lastFullScanText(status.lastFullScanAt)}`);\n // 안쪽 연결 폴더(ADR-038)는 여기서 보지 않는다 — 그 폴더에서 따로 본다.\n for (const folder of await nestedConnections(cwd)) {\n console.log(\n ` ${dimText(\"Connection:\")} ${folder}/ ${dimText(`(separate — cd ${folder} && nobsi status)`)}`,\n );\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 const movedChanges = status.localChanges.filter((c) => c.type === \"moved\");\n\n // 추적 파일 내역은 서로 겹치지 않아야 한다. DB 의 status 는 push/pull 시점에만 갱신되므로\n // 방금 고친 파일도 레코드상으론 여전히 'synced' 다 — 그대로 세면 같은 파일이 synced 와\n // modified 양쪽에 잡혀 내역 합이 총계를 넘는 표가 나온다(무엇이 밀렸는지 못 읽는다).\n // 실제 변경이 걸린 경로를 먼저 덜어 내고 남은 것만 synced 로 센다.\n // 옮긴 노트의 레코드는 아직 옛 경로(movedFrom)를 적고 있다 — 두 경로를 다 덜어 낸다.\n const dirtyPaths = new Set([\n ...modifiedChanges.map((c) => c.path),\n ...deletedChanges.map((c) => c.path),\n ...movedChanges.flatMap((c) => [c.path, c.movedFrom ?? 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 const remoteMoved = status.remoteChanges.filter((c) => c.type === \"moved\");\n const { folderMoves } = status;\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 (movedChanges.length > 0) {\n console.log(` ${icons.move} ${chalk.cyan(\"moved\")} ${movedChanges.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 // Moved — 폴더를 먼저 보인다. 그 안의 노트는 아래에 노트마다 따로 있다.\n if (folderMoves.length + movedChanges.length > 0) {\n console.log(`\\n ${header(\"Moved:\")}`);\n for (const move of folderMoves) {\n console.log(` ${icons.move} ${move.from}/ → ${move.to}/ ${dimText(\"(folder)\")}`);\n }\n for (const c of movedChanges.slice(0, 10)) {\n console.log(` ${icons.move} ${c.movedFrom ?? \"?\"} → ${c.path}`);\n }\n if (movedChanges.length > 10) {\n console.log(dimText(` ... and ${movedChanges.length - 10} more`));\n }\n }\n\n // Deleted files — 수만 보이면 무엇이 Notion 에서 지워질지 모른다.\n if (deletedChanges.length > 0) {\n console.log(`\\n ${header(\"Deleted files:\")}`);\n for (const c of deletedChanges.slice(0, 10)) {\n console.log(` ${chalk.red(\"-\")} ${c.path}`);\n }\n if (deletedChanges.length > 10) {\n console.log(dimText(` ... and ${deletedChanges.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 (remoteMoved.length > 0) {\n console.log(` ${icons.move} ${chalk.cyan(\"moved\")} ${remoteMoved.length}`);\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 : c.type === \"moved\"\n ? icons.move\n : chalk.red(\"-\");\n // 볼트에 있는 노트는 경로, 아직 없는 새 페이지는 Notion 제목 — 내부 id 는 둘 다 없을 때만.\n const name = c.path ?? (c.title ? `${c.title} ${dimText(\"(new page)\")}` : undefined);\n console.log(\n ` ${icon} ${name ?? `${c.pageId.slice(0, 8)}...`} ${dimText(`(${c.type})`)}`,\n );\n }\n if (status.remoteChanges.length > 10) {\n console.log(dimText(` ... and ${status.remoteChanges.length - 10} more`));\n }\n }\n\n // Notion 에서 옮기거나 제목을 바꾼 것 — 다음 pull 이 볼트에서 옮길 자리(ADR-039 · ADR-040).\n const remoteMoves = status.remoteMoves ?? [];\n const remoteMoveSkipped = status.remoteMoveSkipped ?? [];\n if (remoteMoves.length + remoteMoveSkipped.length > 0) {\n console.log(`\\n ${header(\"Moved in Notion (next pull):\")}`);\n for (const move of remoteMoves.slice(0, 10)) {\n console.log(\n ` ${icons.move} ${move.from} → ${move.to} ${dimText(move.renamed ? \"(will rename)\" : \"(will move)\")}`,\n );\n }\n if (remoteMoves.length > 10) {\n console.log(dimText(` ... and ${remoteMoves.length - 10} more`));\n }\n for (const skip of remoteMoveSkipped) {\n console.log(\n ` ${icons.conflict} ${skip.path} ${dimText(`(won't move: ${skip.reason})`)}`,\n );\n }\n }\n\n // 뺀 자리에 놓일 새 페이지 — 다음 pull 이 받지 않는다. 모르고 넘어가면 Notion 에만 있는 글이 된다.\n const remoteExcluded = status.remoteExcluded ?? [];\n if (remoteExcluded.length > 0) {\n console.log(\n `\\n ${header(`New in Notion, not pulled (excluded by paths / ${IGNORE_FILE}): ${remoteExcluded.length}`)}`,\n );\n for (const path of remoteExcluded.slice(0, 10)) {\n console.log(` ${dimText(\"·\")} ${path}`);\n }\n if (remoteExcluded.length > 10) {\n console.log(dimText(` ... and ${remoteExcluded.length - 10} more`));\n }\n }\n\n // Conflicts\n if (conflictCount > 0) {\n console.log(`\\n ${header(chalk.magenta(\"Conflicts:\"))}`);\n // 원격을 읽은 상태(--full)만 Notion 에서 지운 노트를 가른다.\n const remoteDeleted = new Set(\n status.conflicts.filter(isRemoteDeletion).map((c) => c.syncRecord.obsidianPath),\n );\n for (const r of status.conflictRecords) {\n const reason = remoteDeleted.has(r.obsidianPath)\n ? \"(deleted in Notion, local edits not pushed)\"\n : \"(both sides changed)\";\n console.log(` ${icons.conflict} ${r.obsidianPath.padEnd(30)} ${dimText(reason)}`);\n }\n }\n\n // Helpful commands\n const hasChanges =\n status.localChanges.length > 0 ||\n folderMoves.length > 0 ||\n status.remoteChanges.length > 0 ||\n remoteMoves.length > 0 ||\n 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 // 되돌릴 수 있는 것(고친 · 지운 노트)이 있을 때만 — git status 의 `git restore` 안내와 같다.\n if (modifiedChanges.length + deletedChanges.length > 0) {\n console.log(\n ` ${dimText(\"Run\")} ${chalk.cyan(\"nobsi discard <path>\")} ${dimText(\"to discard local edits\")}`,\n );\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 } else if (status.remoteScan?.deletionsDeferred) {\n // 바뀐 것만 찾았다 — 위 원격 변경에는 Notion 에서 지운 것이 아직 없다.\n console.log(`\\n ${remoteDeletionHint(status.remoteScan)}`);\n }\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n formatUnifiedDiff,\n lineDiff,\n matchesPathScope,\n} from \"@im-nobsidian/core\";\nimport type { ChangeDiff } from \"@im-nobsidian/core\";\nimport { printUnifiedDiff } from \"../utils/diff-output.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\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 = await openVaultFs(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, vaultArgPath(targetPath)))\n : status.localChanges;\n\n if (changes.length === 0) {\n console.log(\"변경사항 없음\");\n return;\n }\n\n let failed = 0;\n for (const change of changes) {\n // --remote: 마지막 스냅샷이 아니라 Notion 의 현재 본문을 기준선으로 삼는다.\n // 원격이 그 사이 바뀌었을 때 \"내가 뭘 덮어쓰게 되는지\"는 이 비교로만 보인다.\n if (options.remote && change.type !== \"deleted\") {\n // 옮긴 노트는 Notion 에 반영하기 전까지 추적 기록이 옛 자리에 있다 — Notion 쪽 이름도 그 자리다.\n let remotePath = change.path;\n let remote = await orchestrator.renderRemoteSnapshot(remotePath);\n if (remote === null && change.movedFrom) {\n remotePath = change.movedFrom;\n remote = await orchestrator.renderRemoteSnapshot(remotePath);\n }\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 hunks = lineDiff(remote, currentContent);\n const lines = formatUnifiedDiff(hunks, `notion/${remotePath}`, `b/${change.path}`);\n // 이름 줄만 찍고 끝나면 같은 것인지 못 읽은 것인지 알 수 없다 — 같다고 적는다.\n if (hunks.length === 0) lines.push(\"(Notion 과 내용이 같습니다)\");\n printUnifiedDiff(lines, options.color);\n console.log(\"\");\n continue;\n }\n\n // 지난 동기화 때의 글과 지금 글 — 플러그인 변경 패널의 비교와 같은 두 글이다.\n let diff: ChangeDiff;\n try {\n diff = await orchestrator.localChangeDiff(change);\n } catch (error) {\n failed++;\n console.log(`--- a/${change.path}`);\n console.log(`(${error instanceof Error ? error.message : String(error)})`);\n console.log(\"\");\n continue;\n }\n printChangeDiff(diff, options.color);\n console.log(\"\");\n }\n // 보이지 못한 비교가 있으면 실패로 끝낸다 — 스크립트가 빠진 것을 알 수 있게.\n if (failed > 0) process.exitCode = 1;\n } finally {\n stateDb.close();\n }\n });\n\n/** 옮긴 노트는 Git 처럼 옛 자리 · 새 자리를 먼저 적는다. 내용이 같으면 비교 대신 그렇다고 알린다. */\nfunction printChangeDiff(diff: ChangeDiff, color: boolean): void {\n const oldPath = diff.before === null ? \"/dev/null\" : `a/${diff.movedFrom ?? diff.path}`;\n const newPath = diff.after === null ? \"/dev/null\" : `b/${diff.path}`;\n if (diff.movedFrom) {\n console.log(`rename from ${diff.movedFrom}`);\n console.log(`rename to ${diff.path}`);\n if (diff.before === diff.after) {\n console.log(\"(내용은 그대로입니다 — 자리만 옮겼습니다)\");\n return;\n }\n }\n printUnifiedDiff(\n formatUnifiedDiff(lineDiff(diff.before ?? \"\", diff.after ?? \"\"), oldPath, newPath),\n color,\n );\n}\n","import { DIFF_SIGN } from \"@im-nobsidian/core\";\n\nconst BOLD = \"\\x1b[1m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst CYAN = \"\\x1b[36m\";\nconst RESET = \"\\x1b[0m\";\n\n/** 줄 첫 글자마다의 색 — 더한 줄 · 지운 줄의 부호는 core 의 것을 따른다. `@` 는 묶음 머리. */\nconst LINE_COLOR: Readonly<Record<string, string>> = {\n [DIFF_SIGN.added]: GREEN,\n [DIFF_SIGN.removed]: RED,\n \"@\": CYAN,\n};\n\n/**\n * 통합 diff 줄(core `formatUnifiedDiff`)을 찍는다 — `diff` · `resolve` 가 같은 모양 · 같은 색으로 보인다.\n *\n * 옛 글 · 새 글 이름 줄은 첫 글자가 아니라 자리(앞 두 줄)로 가른다 — 구분선(`---`)을 지운 줄은 `----` 로\n * 시작해 첫 글자로는 이름 줄과 구별되지 않는다.\n */\nexport function printUnifiedDiff(lines: readonly string[], color: boolean): void {\n lines.forEach((line, index) => {\n const code = index < 2 ? BOLD : LINE_COLOR[line.charAt(0)];\n console.log(color && code ? `${code}${line}${RESET}` : line);\n });\n}\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport { icons, failedItem } from \"../utils/format.js\";\nimport { openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\n// 변경 목록(status)에서 고른 노트를 지난 동기화 때의 글로 되돌린다 — Git 의 `restore` 와 같다.\n// 플러그인 변경 패널의 ↺ 와 같은 동작(orchestrator.discardLocalChange)이고 Notion 은 건드리지 않는다.\n// 하나를 되돌리지 못해도 나머지는 되돌린다 — 무엇이 왜 안 됐는지 경로마다 알리고 종료 코드로 남긴다.\nexport const discardCommand = new Command(\"discard\")\n .description(\"로컬 변경을 지난 동기화 때의 글로 되돌리기 (Notion 은 그대로)\")\n .argument(\"<paths...>\", \"되돌릴 노트 경로 — status 가 보인 볼트 기준 경로\")\n .action(async (paths: 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 try {\n const client = NotionClient.fromConfig(config);\n const vaultFs = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n let failed = 0;\n for (const path of paths) {\n try {\n await orchestrator.discardLocalChange(vaultArgPath(path));\n console.log(` ${icons.success} ${path}`);\n } catch (error) {\n failed++;\n failedItem(path, error instanceof Error ? error.message : String(error));\n }\n }\n if (failed > 0) process.exitCode = 1;\n } finally {\n stateDb.close();\n }\n });\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { header, separator, dimText } from \"../utils/format.js\";\nimport { openVaultFs } from \"../utils/vault.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 = await openVaultFs(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 applicableChoices,\n isRemoteDeletion,\n} from \"@im-nobsidian/core\";\nimport type { Conflict, ResolutionChoice } from \"@im-nobsidian/core\";\nimport { printUnifiedDiff } from \"../utils/diff-output.js\";\nimport { describeFailure } from \"../utils/state-db-failure.js\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\n/** 허용 전략 — 오타를 조용히 흘리지 않도록 실행 전에 한 번 검증한다. */\nconst STRATEGIES = [\"local-first\", \"remote-first\", \"duplicate\"] as const;\ntype Strategy = (typeof STRATEGIES)[number];\n\nconst CHOICE_NAMES: Record<ResolutionChoice, string> = {\n local: \"로컬 유지 (Obsidian 버전 유지)\",\n remote: \"원격 유지 (Notion 버전으로 덮어쓰기)\",\n merge: \"자동 병합 (3-way merge)\",\n duplicate: \"복제 (원본 유지 + 올리지 않는 .conflict 사본 생성)\",\n};\n\n/** Notion 에서 지운 노트는 같은 선택이 다른 일을 한다 — 로컬 유지는 다시 만들고, 원격 유지는 지운다. */\nconst REMOTE_DELETION_CHOICE_NAMES: Partial<Record<ResolutionChoice, string>> = {\n local: \"로컬 유지 (Notion 에 새 페이지로 다시 만들기)\",\n remote: \"삭제 따르기 (Obsidian 파일도 지우기)\",\n};\n\n/** 받기 전용(direction: pull)은 해결 결과를 Notion 에 올리지 않는다 — 다시 만들지도 않는다. */\nconst PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES: Partial<Record<ResolutionChoice, string>> = {\n local: \"로컬 유지 (Obsidian 파일만 두기 — 받기 전용이라 Notion 에 만들지 않음)\",\n};\n\nfunction choiceName(conflict: Conflict, choice: ResolutionChoice, uploads: boolean): string {\n const deleted = isRemoteDeletion(conflict);\n return (\n (deleted && !uploads ? PULL_ONLY_REMOTE_DELETION_CHOICE_NAMES[choice] : undefined) ??\n (deleted ? REMOTE_DELETION_CHOICE_NAMES[choice] : undefined) ??\n CHOICE_NAMES[choice] ??\n choice\n );\n}\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 = await openVaultFs(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 const uploads = config.sync.direction !== \"pull\";\n if (!uploads) {\n console.log(\n \" 받기 전용(direction: pull) — 해결 결과는 Obsidian 에만 두고 Notion 에는 올리지 않습니다\\n\",\n );\n }\n\n if (options.strategy) {\n const strategy = options.strategy as Strategy;\n // 원격에서 지운 노트는 전략이 파일을 지우거나 페이지를 새로 만든다 — 묻기 전에 알린다.\n const deletions = pending.filter(isRemoteDeletion).length;\n if (deletions > 0) {\n console.log(\n ` Notion 에서 삭제된 노트 ${deletions}건 — ${\n strategy === \"remote-first\"\n ? \"Obsidian 파일도 지웁니다\"\n : uploads\n ? \"Notion 에 새 페이지로 다시 만듭니다\"\n : \"Obsidian 파일을 그대로 둡니다\"\n }`,\n );\n }\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 [index, result] of results.entries()) {\n if (!result.success) failed++;\n const icon = result.success ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[31m✗\\x1b[0m\";\n const reason = result.error ? ` — ${result.error}` : \"\";\n console.log(\n ` ${icon} ${result.path} → ${choiceName(pending[index]!, result.choice, uploads)}${reason}`,\n );\n }\n report(results.length - failed, failed);\n return;\n }\n\n if (!isInteractive()) {\n console.error(\n `비대화형 환경에서는 대화형 해결을 쓸 수 없습니다 — --strategy <${STRATEGIES.join(\"|\")}> --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, uploads)) resolved++;\n else failed++;\n }\n report(resolved, failed);\n } catch (error) {\n console.error(\"오류:\", describeFailure(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 uploads: boolean,\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 if (isRemoteDeletion(conflict)) {\n // 견줄 원격 본문이 없다 — 줄 비교는 로컬 전부를 지운 것으로 보여 준다.\n console.log(\n \"\\x1b[33mNotion 에서 삭제된 노트입니다 — Notion 에 올리지 않은 로컬 편집이 남아 있습니다.\\x1b[0m\",\n );\n } else {\n printUnifiedDiff(orchestrator.generateConflictDiff(conflict).split(\"\\n\"), true);\n }\n\n const choice = await select<ResolutionChoice>({\n message: \"어떻게 해결할까요?\",\n choices: applicableChoices(conflict).map((value) => ({\n name: choiceName(conflict, value, uploads),\n value,\n })),\n });\n\n const result = await orchestrator.resolveConflict(conflict, choice);\n\n if (result.success) {\n console.log(`\\x1b[32m✓ ${path} → ${choiceName(conflict, choice, uploads)}\\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","/**\n * 상태 DB 를 쓸 수 없을 때의 CLI 출력 — 이유 뒤에 할 일을 붙인다.\n *\n * 명령은 처음에 상태 DB 를 연다(`StateDB.open`). 파일이 비었거나 잘렸거나(`SavedStateDbError`), 플러그인이 같은\n * 볼트를 쓰는 중이면(`StateDbLockedError`) 던지는데, 명령 밖으로 나오면 Node 가 코드 위치(스택)와 함께 이유만\n * 보였다 — 사용자가 할 일은 어디에도 없었다.\n */\nimport { StateDbUnavailableError } from \"@im-nobsidian/core\";\nimport type { ExitableProcess } from \"./exit.js\";\n\n/** 실패를 보일 말 — 상태 DB 를 쓸 수 없는 것이면 할 일을 붙이고, 다른 실패는 그 말 그대로다. */\nexport function describeFailure(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (!(error instanceof StateDbUnavailableError)) return message;\n return `${message} — ${error.guidance(\"명령을 다시 실행하세요\")}`;\n}\n\n/**\n * 명령 밖으로 나온 실패를 알린다. 상태 DB 를 쓸 수 없는 것은 사용자가 풀 일이라 이유와 할 일만 보이고 실패(1)로\n * 끝낸다. 다른 실패는 그대로 다시 던진다 — 지금까지처럼 Node 가 스택과 함께 보이고 1 로 끝난다.\n */\nexport function reportStateDbFailure(error: unknown, proc: ExitableProcess): void {\n if (!(error instanceof StateDbUnavailableError)) throw error;\n console.error(`오류: ${describeFailure(error)}`);\n proc.exitCode = 1;\n}\n","import { Command } from \"commander\";\nimport {\n ConfigManager,\n StateDB,\n NotionClient,\n SyncOrchestrator,\n WatchSyncService,\n DEFAULT_WATCH_DEBOUNCE_MS,\n} from \"@im-nobsidian/core\";\nimport type { SyncResult, WatchSyncScope } from \"@im-nobsidian/core\";\nimport { openVaultFs } from \"../utils/vault.js\";\n\nfunction formatResult(result: SyncResult): string {\n const pull = `Pull: +${result.pull.created} ~${result.pull.updated} -${result.pull.deleted}`;\n const moved = result.push.moved > 0 ? ` →${result.push.moved}` : \"\";\n const push = `Push: +${result.push.created} ~${result.push.updated} -${result.push.deleted}${moved}`;\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\n/** 로그의 sync 이름 — 주기 sync 는 볼트 전체를 본다. */\nfunction syncLabel(scope: WatchSyncScope): string {\n return scope === \"full\" ? \"주기적 동기화\" : \"동기화\";\n}\n\nexport const watchCommand = new Command(\"watch\")\n .description(\"파일 변경 감지 + 자동 동기화\")\n .option(\"-d, --debounce <ms>\", \"디바운스 대기 시간(ms)\", String(DEFAULT_WATCH_DEBOUNCE_MS))\n .option(\"-i, --interval <seconds>\", \"풀 동기화 주기(초, 0이면 비활성)\", \"0\")\n .action(async (options: { debounce: string; interval: string }) => {\n // 숫자가 아니면 디바운스가 0 이 되거나 주기 동기화가 말없이 꺼진다 — 시작하기 전에 거절한다.\n const debounceMs = Number(options.debounce);\n const intervalSec = Number(options.interval);\n if (!Number.isInteger(debounceMs) || debounceMs < 0) {\n console.error(`--debounce 는 0 이상의 정수(ms)여야 합니다: ${options.debounce}`);\n process.exitCode = 1;\n return;\n }\n if (!Number.isInteger(intervalSec) || intervalSec < 0) {\n console.error(`--interval 은 0 이상의 정수(초)여야 합니다: ${options.interval}`);\n process.exitCode = 1;\n return;\n }\n\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 = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n\n const service = new WatchSyncService(cwd, orchestrator, {\n debounceMs,\n onFileChange: (event, path) => {\n console.log(`[${timestamp()}] ${event}: ${path}`);\n },\n onSyncStart: (scope) => {\n console.log(`[${timestamp()}] ${syncLabel(scope)} 시작...`);\n },\n onSyncComplete: (result, scope) => {\n console.log(`[${timestamp()}] ${syncLabel(scope)} 완료 — ${formatResult(result)}`);\n },\n onSyncCancelled: (result, scope) => {\n console.log(\n `[${timestamp()}] ${syncLabel(scope)} 취소 — 멈추기 전까지 ${formatResult(result)}`,\n );\n },\n onSyncError: (error, scope) => {\n console.error(`[${timestamp()}] ${syncLabel(scope)} 실패: ${error.message}`);\n },\n });\n\n let pullTimer: ReturnType<typeof setInterval> | null = null;\n let shuttingDown = false;\n\n const shutdown = async (): Promise<void> => {\n // 도는 sync 를 기다리는 중에 한 번 더 누르면 기다리지 않고 끝낸다.\n if (shuttingDown) {\n console.log(`[${timestamp()}] 강제 종료`);\n return process.exit(1);\n }\n shuttingDown = true;\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 // 파일 변경 sync 와 같은 줄에 세운다 — 도는 sync 가 있으면 끝난 뒤 한 번 돈다(S-09).\n pullTimer = setInterval(() => service.requestFullSync(), intervalSec * 1000);\n }\n\n await new Promise(() => {});\n });\n","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } 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\";\nimport { openVaultFs } from \"../utils/vault.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 = await openVaultFs(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","import { Command } from \"commander\";\nimport { ConfigManager, StateDB, NotionClient, SyncOrchestrator } from \"@im-nobsidian/core\";\nimport chalk from \"chalk\";\nimport { icons, failedItem } from \"../utils/format.js\";\nimport { obsidianConfigDir, openVaultFs, vaultArgPath } from \"../utils/vault.js\";\n\n// 볼트에서 만든 노트 폴더를 Notion DB 로 올릴 준비(#75) — frontmatter · `.base` 로 사이드카를 써 둔다.\n// Notion 에는 쓰지 않는다. 사이드카는 JSON 이라 push 전에 고칠 수 있다 — 무엇을 왜 그렇게 정했는지 보인다.\nexport const prepareDbCommand = new Command(\"prepare-db\")\n .description(\"볼트의 노트 폴더를 다음 push 에서 Notion DB 로 만들도록 준비 (Notion 은 그대로)\")\n .argument(\"<folder>\", \"DB 로 만들 폴더 — 볼트 기준 경로. 바로 아래 노트가 행이 된다\")\n .action(async (folder: 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 try {\n const client = NotionClient.fromConfig(config);\n const vaultFs = await openVaultFs(cwd, config.paths);\n const orchestrator = new SyncOrchestrator(config, stateDb, client, vaultFs);\n const report = await orchestrator.prepareFolderDatabase(\n vaultArgPath(folder),\n obsidianConfigDir(cwd),\n );\n\n console.log(` ${icons.success} ${report.sidecarPath} — 행 ${report.rows}개`);\n for (const property of report.properties) {\n console.log(` ${property.name}: ${property.type} ${chalk.dim(`(${property.reason})`)}`);\n }\n if (report.views.length > 0) console.log(` 보기: ${report.views.join(\", \")}`);\n for (const skip of report.skipped) {\n console.log(chalk.dim(` 옮기지 않음 — ${skip.what}: ${skip.reason}`));\n }\n console.log(chalk.dim(\" 타입을 바꾸려면 사이드카를 고친 뒤 push 하세요.\"));\n } catch (error) {\n failedItem(folder, error instanceof Error ? error.message : String(error));\n 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","ConfigManager","chalk","ConfigManager","chalk","spinner","Command","ConfigManager","StateDB","NotionClient","Command","ConfigManager","StateDB","NotionClient","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","IGNORE_FILE","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","IGNORE_FILE","remoteDeleted","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","matchesPathScope","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","matchesPathScope","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","confirm","ConfigManager","StateDB","NotionClient","SyncOrchestrator","isRemoteDeletion","isRemoteDeletion","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","confirm","failed","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","report","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","chalk","Command","ConfigManager","StateDB","NotionClient","SyncOrchestrator","report","chalk","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-nobsidian",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "CLI tool for Obsidian ↔ Notion bidirectional sync",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "chalk": "^5.6.2",
16
16
  "commander": "^15.0.0",
17
17
  "ora": "^9.4.1",
18
- "@im-nobsidian/core": "0.10.0"
18
+ "@im-nobsidian/core": "0.11.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "tsup": "^8.0.0",