@rewrlution/papyrus-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +699 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +16 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/auth/index.d.ts +2 -0
  6. package/dist/commands/auth/index.js +18 -0
  7. package/dist/commands/auth/index.js.map +1 -0
  8. package/dist/commands/auth/login.d.ts +1 -0
  9. package/dist/commands/auth/login.js +8 -0
  10. package/dist/commands/auth/login.js.map +1 -0
  11. package/dist/commands/auth/logout.d.ts +1 -0
  12. package/dist/commands/auth/logout.js +5 -0
  13. package/dist/commands/auth/logout.js.map +1 -0
  14. package/dist/commands/auth/register.d.ts +1 -0
  15. package/dist/commands/auth/register.js +8 -0
  16. package/dist/commands/auth/register.js.map +1 -0
  17. package/dist/commands/index.d.ts +2 -0
  18. package/dist/commands/index.js +3 -0
  19. package/dist/commands/index.js.map +1 -0
  20. package/dist/commands/journal/add.d.ts +2 -0
  21. package/dist/commands/journal/add.js +5 -0
  22. package/dist/commands/journal/add.js.map +1 -0
  23. package/dist/commands/journal/amend.d.ts +2 -0
  24. package/dist/commands/journal/amend.js +5 -0
  25. package/dist/commands/journal/amend.js.map +1 -0
  26. package/dist/commands/journal/edit.d.ts +6 -0
  27. package/dist/commands/journal/edit.js +60 -0
  28. package/dist/commands/journal/edit.js.map +1 -0
  29. package/dist/commands/journal/index.d.ts +2 -0
  30. package/dist/commands/journal/index.js +32 -0
  31. package/dist/commands/journal/index.js.map +1 -0
  32. package/dist/commands/journal/list.d.ts +1 -0
  33. package/dist/commands/journal/list.js +20 -0
  34. package/dist/commands/journal/list.js.map +1 -0
  35. package/dist/commands/journal/show.d.ts +2 -0
  36. package/dist/commands/journal/show.js +35 -0
  37. package/dist/commands/journal/show.js.map +1 -0
  38. package/dist/commands/journal/sync.d.ts +1 -0
  39. package/dist/commands/journal/sync.js +9 -0
  40. package/dist/commands/journal/sync.js.map +1 -0
  41. package/dist/commands/types.d.ts +9 -0
  42. package/dist/commands/types.js +2 -0
  43. package/dist/commands/types.js.map +1 -0
  44. package/dist/components/Browser.d.ts +29 -0
  45. package/dist/components/Browser.js +124 -0
  46. package/dist/components/Browser.js.map +1 -0
  47. package/dist/components/BrowserFooter.d.ts +6 -0
  48. package/dist/components/BrowserFooter.js +6 -0
  49. package/dist/components/BrowserFooter.js.map +1 -0
  50. package/dist/components/BrowserHeader.d.ts +6 -0
  51. package/dist/components/BrowserHeader.js +6 -0
  52. package/dist/components/BrowserHeader.js.map +1 -0
  53. package/dist/components/ColdStart.d.ts +6 -0
  54. package/dist/components/ColdStart.js +24 -0
  55. package/dist/components/ColdStart.js.map +1 -0
  56. package/dist/components/FormInput.d.ts +10 -0
  57. package/dist/components/FormInput.js +7 -0
  58. package/dist/components/FormInput.js.map +1 -0
  59. package/dist/components/JournalListView.d.ts +10 -0
  60. package/dist/components/JournalListView.js +40 -0
  61. package/dist/components/JournalListView.js.map +1 -0
  62. package/dist/components/JournalViewer.d.ts +32 -0
  63. package/dist/components/JournalViewer.js +146 -0
  64. package/dist/components/JournalViewer.js.map +1 -0
  65. package/dist/components/LoginForm.d.ts +1 -0
  66. package/dist/components/LoginForm.js +68 -0
  67. package/dist/components/LoginForm.js.map +1 -0
  68. package/dist/components/Logo.d.ts +1 -0
  69. package/dist/components/Logo.js +57 -0
  70. package/dist/components/Logo.js.map +1 -0
  71. package/dist/components/RegisterForm.d.ts +1 -0
  72. package/dist/components/RegisterForm.js +72 -0
  73. package/dist/components/RegisterForm.js.map +1 -0
  74. package/dist/components/StatusMessage.d.ts +7 -0
  75. package/dist/components/StatusMessage.js +19 -0
  76. package/dist/components/StatusMessage.js.map +1 -0
  77. package/dist/components/SyncProgress.d.ts +1 -0
  78. package/dist/components/SyncProgress.js +46 -0
  79. package/dist/components/SyncProgress.js.map +1 -0
  80. package/dist/lib/api/api-client.d.ts +23 -0
  81. package/dist/lib/api/api-client.js +111 -0
  82. package/dist/lib/api/api-client.js.map +1 -0
  83. package/dist/lib/api/index.d.ts +3 -0
  84. package/dist/lib/api/index.js +6 -0
  85. package/dist/lib/api/index.js.map +1 -0
  86. package/dist/lib/auth/index.d.ts +1 -0
  87. package/dist/lib/auth/index.js +2 -0
  88. package/dist/lib/auth/index.js.map +1 -0
  89. package/dist/lib/auth/require-auth.d.ts +67 -0
  90. package/dist/lib/auth/require-auth.js +107 -0
  91. package/dist/lib/auth/require-auth.js.map +1 -0
  92. package/dist/lib/storage/base-storage.d.ts +50 -0
  93. package/dist/lib/storage/base-storage.js +91 -0
  94. package/dist/lib/storage/base-storage.js.map +1 -0
  95. package/dist/lib/storage/config-store.d.ts +40 -0
  96. package/dist/lib/storage/config-store.js +63 -0
  97. package/dist/lib/storage/config-store.js.map +1 -0
  98. package/dist/lib/storage/index.d.ts +12 -0
  99. package/dist/lib/storage/index.js +13 -0
  100. package/dist/lib/storage/index.js.map +1 -0
  101. package/dist/lib/storage/journal-storage.d.ts +46 -0
  102. package/dist/lib/storage/journal-storage.js +78 -0
  103. package/dist/lib/storage/journal-storage.js.map +1 -0
  104. package/dist/lib/storage/sync-meta-store.d.ts +37 -0
  105. package/dist/lib/storage/sync-meta-store.js +50 -0
  106. package/dist/lib/storage/sync-meta-store.js.map +1 -0
  107. package/dist/lib/storage/token-store.d.ts +25 -0
  108. package/dist/lib/storage/token-store.js +40 -0
  109. package/dist/lib/storage/token-store.js.map +1 -0
  110. package/dist/lib/sync/sync-engine.d.ts +13 -0
  111. package/dist/lib/sync/sync-engine.js +96 -0
  112. package/dist/lib/sync/sync-engine.js.map +1 -0
  113. package/dist/utils/date.d.ts +58 -0
  114. package/dist/utils/date.js +117 -0
  115. package/dist/utils/date.js.map +1 -0
  116. package/dist/utils/editor.d.ts +2 -0
  117. package/dist/utils/editor.js +81 -0
  118. package/dist/utils/editor.js.map +1 -0
  119. package/dist/utils/template.d.ts +2 -0
  120. package/dist/utils/template.js +17 -0
  121. package/dist/utils/template.js.map +1 -0
  122. package/dist/utils/token.d.ts +20 -0
  123. package/dist/utils/token.js +64 -0
  124. package/dist/utils/token.js.map +1 -0
  125. package/package.json +53 -0
@@ -0,0 +1,78 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { BaseStorage } from './base-storage.js';
4
+ /**
5
+ * Manages journal entries stored as markdown files with YAML frontmatter.
6
+ *
7
+ * File format:
8
+ * ---
9
+ * date: "20251210"
10
+ * hash: "abc123..."
11
+ * createdAt: "2025-12-10T10:00:00.000Z"
12
+ * updatedAt: "2025-12-10T10:00:00.000Z"
13
+ * deletedAt: null
14
+ * ---
15
+ *
16
+ * # Journal content here
17
+ */
18
+ export class JournalStore extends BaseStorage {
19
+ journalsDir;
20
+ constructor(dataDir) {
21
+ super();
22
+ const dir = dataDir ?? this.getDataDir();
23
+ this.journalsDir = path.join(dir, 'journals');
24
+ }
25
+ getEntryPath(date) {
26
+ return path.join(this.journalsDir, `${date}.md`);
27
+ }
28
+ /**
29
+ * Create a new journal entry
30
+ */
31
+ create(date, content = '') {
32
+ const filepath = this.getEntryPath(date);
33
+ this.writeFile(filepath, content);
34
+ }
35
+ /**
36
+ * Get journal entry by date
37
+ */
38
+ load(date) {
39
+ const filepath = this.getEntryPath(date);
40
+ return this.readFile(filepath);
41
+ }
42
+ /**
43
+ * Delete journal entry
44
+ */
45
+ delete(date) {
46
+ const filepath = this.getEntryPath(date);
47
+ this.deleteFile(filepath);
48
+ }
49
+ /**
50
+ * Check if entry exists for date
51
+ */
52
+ exists(date) {
53
+ const filepath = this.getEntryPath(date);
54
+ return this.fileExists(filepath);
55
+ }
56
+ /**
57
+ * List all journal entries (sorted by date descending)
58
+ */
59
+ list() {
60
+ this.ensureDir(this.journalsDir);
61
+ const files = fs.readdirSync(this.journalsDir);
62
+ return files
63
+ .filter((f) => f.endsWith('.md') && /^\d{8}\.md$/.test(f))
64
+ .map((f) => {
65
+ const date = f.replace('.md', '');
66
+ const filepath = path.join(this.journalsDir, f);
67
+ const stats = fs.statSync(filepath);
68
+ return {
69
+ date,
70
+ path: filepath,
71
+ size: stats.size,
72
+ modified: stats.mtime,
73
+ };
74
+ })
75
+ .sort((a, b) => b.date.localeCompare(a.date));
76
+ }
77
+ }
78
+ //# sourceMappingURL=journal-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal-storage.js","sourceRoot":"","sources":["../../../src/lib/storage/journal-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAShD;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,YAAa,SAAQ,WAAW;IACnC,WAAW,CAAS;IAE5B,YAAY,OAAgB;QAC1B,KAAK,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAChD,CAAC;IAEO,YAAY,CAAC,IAAY;QAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY,EAAE,UAAkB,EAAE;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,IAAY;QACf,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEjC,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC/C,OAAO,KAAK;aACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aACzD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;YAChD,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO;gBACL,IAAI;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,QAAQ,EAAE,KAAK,CAAC,KAAK;aACtB,CAAC;QACJ,CAAC,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,37 @@
1
+ import { BaseStorage } from './base-storage.js';
2
+ export interface SyncMetadata {
3
+ lastSyncHash: string;
4
+ }
5
+ /**
6
+ * Map of date to sync metadata
7
+ */
8
+ export type SyncMetaEntries = Record<string, SyncMetadata>;
9
+ /**
10
+ * Sync metadata storage structure
11
+ * Example: {
12
+ * "lastSync": "2026-01-04T10:30:00.000Z",
13
+ * "entries": {
14
+ * "20260101": { lastSyncHash: "abc123..." }
15
+ * }
16
+ * }
17
+ */
18
+ export interface SyncMetaStoreData {
19
+ lastSync?: string;
20
+ entries: SyncMetaEntries;
21
+ }
22
+ export declare class SyncMetaStore extends BaseStorage {
23
+ private metaPath;
24
+ constructor(dataDir?: string);
25
+ load(): SyncMetaStoreData;
26
+ save(data: SyncMetaStoreData): void;
27
+ get(date: string): SyncMetadata | null;
28
+ update(date: string, hash: string): void;
29
+ /**
30
+ * Update the last sync timestamp to now
31
+ */
32
+ updateLastSync(): void;
33
+ /**
34
+ * Get the last sync timestamp
35
+ */
36
+ getLastSync(): string | null;
37
+ }
@@ -0,0 +1,50 @@
1
+ import path from 'path';
2
+ import { BaseStorage } from './base-storage.js';
3
+ export class SyncMetaStore extends BaseStorage {
4
+ metaPath;
5
+ constructor(dataDir) {
6
+ super();
7
+ const dir = dataDir ?? this.getDataDir();
8
+ this.metaPath = path.join(dir, 'sync-meta.json');
9
+ }
10
+ load() {
11
+ const content = this.readFile(this.metaPath);
12
+ if (!content)
13
+ return { entries: {} };
14
+ try {
15
+ return JSON.parse(content);
16
+ }
17
+ catch {
18
+ return { entries: {} };
19
+ }
20
+ }
21
+ save(data) {
22
+ const content = JSON.stringify(data, null, 2);
23
+ this.writeFile(this.metaPath, content);
24
+ }
25
+ get(date) {
26
+ const data = this.load();
27
+ return data.entries[date] ?? null;
28
+ }
29
+ update(date, hash) {
30
+ const data = this.load();
31
+ data.entries[date] = { lastSyncHash: hash };
32
+ this.save(data);
33
+ }
34
+ /**
35
+ * Update the last sync timestamp to now
36
+ */
37
+ updateLastSync() {
38
+ const data = this.load();
39
+ data.lastSync = new Date().toISOString();
40
+ this.save(data);
41
+ }
42
+ /**
43
+ * Get the last sync timestamp
44
+ */
45
+ getLastSync() {
46
+ const data = this.load();
47
+ return data.lastSync ?? null;
48
+ }
49
+ }
50
+ //# sourceMappingURL=sync-meta-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-meta-store.js","sourceRoot":"","sources":["../../../src/lib/storage/sync-meta-store.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAyBhD,MAAM,OAAO,aAAc,SAAQ,WAAW;IACpC,QAAQ,CAAS;IAEzB,YAAY,OAAgB;QAC1B,KAAK,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IACnD,CAAC;IAED,IAAI;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAErC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAsB,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAuB;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,IAAY;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,WAAW;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import { BaseStorage } from './base-storage.js';
2
+ /**
3
+ * Manages authentication token stored in the config directory
4
+ * Example: ~/.config/papyrus/token (Linux)
5
+ */
6
+ export declare class TokenStore extends BaseStorage {
7
+ private tokenPath;
8
+ constructor(configDir?: string);
9
+ /**
10
+ * Save authentication token
11
+ */
12
+ save(token: string): void;
13
+ /**
14
+ * Get stored token, returns null if not found
15
+ */
16
+ get(): string | null;
17
+ /**
18
+ * Remove stored token (logout)
19
+ */
20
+ clear(): void;
21
+ /**
22
+ * Check if token exists
23
+ */
24
+ exists(): boolean;
25
+ }
@@ -0,0 +1,40 @@
1
+ import path from 'path';
2
+ import { BaseStorage } from './base-storage.js';
3
+ /**
4
+ * Manages authentication token stored in the config directory
5
+ * Example: ~/.config/papyrus/token (Linux)
6
+ */
7
+ export class TokenStore extends BaseStorage {
8
+ tokenPath;
9
+ constructor(configDir) {
10
+ super();
11
+ const dir = configDir ?? this.getConfigDir();
12
+ this.tokenPath = path.join(dir, 'token');
13
+ }
14
+ /**
15
+ * Save authentication token
16
+ */
17
+ save(token) {
18
+ this.writeFile(this.tokenPath, token);
19
+ }
20
+ /**
21
+ * Get stored token, returns null if not found
22
+ */
23
+ get() {
24
+ const content = this.readFile(this.tokenPath);
25
+ return content ? content.trim() : null;
26
+ }
27
+ /**
28
+ * Remove stored token (logout)
29
+ */
30
+ clear() {
31
+ this.deleteFile(this.tokenPath);
32
+ }
33
+ /**
34
+ * Check if token exists
35
+ */
36
+ exists() {
37
+ return this.fileExists(this.tokenPath);
38
+ }
39
+ }
40
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../../src/lib/storage/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,UAAW,SAAQ,WAAW;IACjC,SAAS,CAAS;IAE1B,YAAY,SAAkB;QAC5B,KAAK,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,SAAS,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;QAC7C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,KAAa;QAChB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,GAAG;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ export interface SyncResult {
2
+ uploaded: number;
3
+ downloaded: number;
4
+ conflicts: number;
5
+ }
6
+ /**
7
+ * Perform three-way sync between local and remote journals
8
+ * Uses hash-based conflict detection
9
+ *
10
+ * @param onProgress - Progress callback options
11
+ * @returns Sync statistics
12
+ */
13
+ export declare function performSync(onProgress?: (message: string) => void): Promise<SyncResult>;
@@ -0,0 +1,96 @@
1
+ import { generateContentHash } from '@rewrlution/papyrus-shared';
2
+ import { api } from '../api/index.js';
3
+ import { journalStore, syncMetaStore } from '../storage/index.js';
4
+ /**
5
+ * Perform three-way sync between local and remote journals
6
+ * Uses hash-based conflict detection
7
+ *
8
+ * @param onProgress - Progress callback options
9
+ * @returns Sync statistics
10
+ */
11
+ export async function performSync(onProgress) {
12
+ const result = {
13
+ uploaded: 0,
14
+ downloaded: 0,
15
+ conflicts: 0,
16
+ };
17
+ // Get all local journals
18
+ const localJournals = journalStore.list();
19
+ const localByDate = new Map(localJournals.map((j) => [j.date, j]));
20
+ // Get remote journal metadata (includes hashes)
21
+ const remoteJournals = await api.listJournalsMetadata();
22
+ const remoteByDate = new Map(remoteJournals.map((j) => [j.date, j]));
23
+ // Process union of all dates
24
+ const allDates = new Set([...localByDate.keys(), ...remoteByDate.keys()]);
25
+ onProgress?.(`Syncing ${allDates.size} journal(s)...`);
26
+ for (const date of allDates) {
27
+ const local = localByDate.get(date);
28
+ const remote = remoteByDate.get(date);
29
+ const syncMeta = syncMetaStore.get(date);
30
+ // Case 1: Only exists remotely → Download
31
+ if (!local && remote) {
32
+ onProgress?.(`↓ Downloading ${date}.md`);
33
+ const entry = await api.getJournal(date);
34
+ journalStore.create(date, entry.content);
35
+ syncMetaStore.update(date, entry.hash);
36
+ result.downloaded++;
37
+ continue;
38
+ }
39
+ // Case 2: Only exists locally → Upload
40
+ if (local && !remote) {
41
+ onProgress?.(`↑ Uploading ${date}.md`);
42
+ const content = journalStore.load(date);
43
+ if (content) {
44
+ const created = await api.createJournal(date, content);
45
+ syncMetaStore.update(date, created.hash);
46
+ result.uploaded++;
47
+ }
48
+ continue;
49
+ }
50
+ // Case 3: Exists on both → Three-way comparison
51
+ if (local && remote) {
52
+ const localContent = journalStore.load(date);
53
+ if (!localContent)
54
+ continue; // Shouldn't happen, but guard against it
55
+ const currentHash = generateContentHash(localContent);
56
+ const remoteHash = remote.hash;
57
+ const lastSyncedHash = syncMeta?.lastSyncHash;
58
+ const localChanged = currentHash !== lastSyncedHash;
59
+ const remoteChanged = remoteHash !== lastSyncedHash;
60
+ // Both changed → Conflict
61
+ if (localChanged && remoteChanged) {
62
+ onProgress?.(`⚠️ Conflict in ${date}.md - merging`);
63
+ const { content: remoteContent } = await api.getJournal(date);
64
+ // Merge Strategy: append both versions
65
+ const merged = `${localContent}\n\n<!-- MERGED FROM SERVER -->\n\n${remoteContent}`;
66
+ journalStore.create(date, merged);
67
+ const updated = await api.updateJournal(date, merged);
68
+ syncMetaStore.update(date, updated.hash);
69
+ result.conflicts++;
70
+ continue;
71
+ }
72
+ // Only local changed → Upload
73
+ if (localChanged) {
74
+ onProgress?.(`↑ Uploading changes to ${date}.md`);
75
+ const updated = await api.updateJournal(date, localContent);
76
+ syncMetaStore.update(date, updated.hash);
77
+ result.uploaded++;
78
+ continue;
79
+ }
80
+ // Only remote changed → Download
81
+ if (remoteChanged) {
82
+ onProgress?.(`↓ Downloading changes to ${date}.md`);
83
+ const remoteEntry = await api.getJournal(date);
84
+ journalStore.create(date, remoteEntry.content);
85
+ syncMetaStore.update(date, remoteHash);
86
+ result.downloaded++;
87
+ continue;
88
+ }
89
+ // No changes → Skip
90
+ }
91
+ }
92
+ // Update last sync timestamp
93
+ syncMetaStore.updateLastSync();
94
+ return result;
95
+ }
96
+ //# sourceMappingURL=sync-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-engine.js","sourceRoot":"","sources":["../../../src/lib/sync/sync-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Use local timezone to get today's date in YYYYMMDD format.
3
+ * If you're in PST on Dec 10, you get "20251210"
4
+ * even if UTC is Dec 11
5
+ *
6
+ * @returns today's date in YYYYMMDD format
7
+ */
8
+ export declare function getTodayDate(): string;
9
+ /**
10
+ * Convert YYYYMMDD to readable format like "December 10, 2025"
11
+ *
12
+ * @param date - date in YYYYMMDD format
13
+ * @returns readable format of the same date
14
+ */
15
+ export declare function formatDate(date: string): string;
16
+ /**
17
+ * Parse user input and convert to YYYYMMDD format.
18
+ * Supports:
19
+ * - YYYYMMDD: "20251218"
20
+ * - ISO: "2025-12-18"
21
+ * - Relative: "today", "yesterday", "tomorrow"
22
+ * - Offsets: "+1", "-7"
23
+ *
24
+ * @param input - user input
25
+ * @returns date in YYYYMMDD format
26
+ * @throws Error if input cannot be parsed or is invalid
27
+ */
28
+ export declare function parseDate(input: string): string;
29
+ /**
30
+ * Check if date string is valid YYYYMMDD.
31
+ * Uses shared validation schema.
32
+ *
33
+ * @param date - date string
34
+ * @returns true if it is a valid date, otherwise false
35
+ */
36
+ export declare function isValidDate(date: string): boolean;
37
+ /**
38
+ * Parse date string (YYYYMMDD) to Date object in local timezone at noon.
39
+ *
40
+ * This function is needed for creating consistent timestamps from date strings
41
+ * while avoiding timezone edge cases. By using noon (12:00:00), we ensure the
42
+ * date stays correct regardless of timezone offsets.
43
+ *
44
+ * Use case: When reading legacy journal files without frontmatter, we need to
45
+ * create timestamps from the filename (e.g., "20251231.md") that reflect the
46
+ * correct date in the user's local timezone.
47
+ *
48
+ * @param dateString - date in YYYYMMDD format
49
+ * @returns Date object at noon in local timezone
50
+ * @example
51
+ * parseDateToLocalTimestamp("20251231") // Dec 31, 2025 12:00:00 (local time)
52
+ */
53
+ export declare function parseDateToLocalTimestamp(dateString: string): Date;
54
+ /**
55
+ * Format date string as "Month DD, YYYY (Day)"
56
+ * Example: "20260104" → "January 4, 2026 (Saturday)"
57
+ */
58
+ export declare function formatDateHeader(dateStr: string): string;
@@ -0,0 +1,117 @@
1
+ import { format, parse, addDays, subDays, parseISO } from 'date-fns';
2
+ import { DATE_FORMAT, DateStringSchema } from '@rewrlution/papyrus-shared';
3
+ /**
4
+ * Use local timezone to get today's date in YYYYMMDD format.
5
+ * If you're in PST on Dec 10, you get "20251210"
6
+ * even if UTC is Dec 11
7
+ *
8
+ * @returns today's date in YYYYMMDD format
9
+ */
10
+ export function getTodayDate() {
11
+ return format(new Date(), DATE_FORMAT);
12
+ }
13
+ /**
14
+ * Convert YYYYMMDD to readable format like "December 10, 2025"
15
+ *
16
+ * @param date - date in YYYYMMDD format
17
+ * @returns readable format of the same date
18
+ */
19
+ export function formatDate(date) {
20
+ const dateObj = parse(date, DATE_FORMAT, new Date());
21
+ return format(dateObj, ' MMMM d, yyyy');
22
+ }
23
+ /**
24
+ * Parse user input and convert to YYYYMMDD format.
25
+ * Supports:
26
+ * - YYYYMMDD: "20251218"
27
+ * - ISO: "2025-12-18"
28
+ * - Relative: "today", "yesterday", "tomorrow"
29
+ * - Offsets: "+1", "-7"
30
+ *
31
+ * @param input - user input
32
+ * @returns date in YYYYMMDD format
33
+ * @throws Error if input cannot be parsed or is invalid
34
+ */
35
+ export function parseDate(input) {
36
+ let result;
37
+ // Parse ISO format: 2025-12-10 → 20251210
38
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
39
+ result = format(parseISO(input), DATE_FORMAT);
40
+ }
41
+ // Parse relative dates
42
+ else if (input === 'today') {
43
+ result = getTodayDate();
44
+ }
45
+ else if (input === 'yesterday') {
46
+ result = format(subDays(new Date(), 1), DATE_FORMAT);
47
+ }
48
+ else if (input === 'tomorrow') {
49
+ result = format(addDays(new Date(), 1), DATE_FORMAT);
50
+ }
51
+ // Parse offsets: +1, -7 (days from today)
52
+ else if (/^([+-]\d+)$/.test(input)) {
53
+ const days = parseInt(input);
54
+ result = format(addDays(new Date(), days), DATE_FORMAT);
55
+ }
56
+ // Assume YYYYMMDD
57
+ else {
58
+ result = input;
59
+ }
60
+ // Validate using shared schema
61
+ const validation = DateStringSchema.safeParse(result);
62
+ if (!validation.success) {
63
+ throw new Error(`Invalid date: ${input}. Expected YYYYMMDD format.`);
64
+ }
65
+ return result;
66
+ }
67
+ /**
68
+ * Check if date string is valid YYYYMMDD.
69
+ * Uses shared validation schema.
70
+ *
71
+ * @param date - date string
72
+ * @returns true if it is a valid date, otherwise false
73
+ */
74
+ export function isValidDate(date) {
75
+ return DateStringSchema.safeParse(date).success;
76
+ }
77
+ /**
78
+ * Parse date string (YYYYMMDD) to Date object in local timezone at noon.
79
+ *
80
+ * This function is needed for creating consistent timestamps from date strings
81
+ * while avoiding timezone edge cases. By using noon (12:00:00), we ensure the
82
+ * date stays correct regardless of timezone offsets.
83
+ *
84
+ * Use case: When reading legacy journal files without frontmatter, we need to
85
+ * create timestamps from the filename (e.g., "20251231.md") that reflect the
86
+ * correct date in the user's local timezone.
87
+ *
88
+ * @param dateString - date in YYYYMMDD format
89
+ * @returns Date object at noon in local timezone
90
+ * @example
91
+ * parseDateToLocalTimestamp("20251231") // Dec 31, 2025 12:00:00 (local time)
92
+ */
93
+ export function parseDateToLocalTimestamp(dateString) {
94
+ const year = parseInt(dateString.substring(0, 4), 10);
95
+ const month = parseInt(dateString.substring(4, 6), 10) - 1; // JS months are 0-indexed
96
+ const day = parseInt(dateString.substring(6, 8), 10);
97
+ // Create date at noon local time to avoid timezone edge cases
98
+ return new Date(year, month, day, 12, 0, 0);
99
+ }
100
+ /**
101
+ * Format date string as "Month DD, YYYY (Day)"
102
+ * Example: "20260104" → "January 4, 2026 (Saturday)"
103
+ */
104
+ export function formatDateHeader(dateStr) {
105
+ const year = dateStr.substring(0, 4);
106
+ const month = dateStr.substring(4, 6);
107
+ const day = dateStr.substring(6, 8);
108
+ const date = new Date(`${year}-${month}-${day}`);
109
+ const options = {
110
+ year: 'numeric',
111
+ month: 'long',
112
+ day: 'numeric',
113
+ weekday: 'long',
114
+ };
115
+ return date.toLocaleDateString('en-US', options);
116
+ }
117
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.js","sourceRoot":"","sources":["../../src/utils/date.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function detectEditor(): string;
2
+ export declare function openInEditor(content: string, baseFilename?: string): string;
@@ -0,0 +1,81 @@
1
+ import { spawnSync } from 'child_process';
2
+ import crypto from 'crypto';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ /**
7
+ * Supported editors in order of preference.
8
+ * Windows: vi -> vim -> code -> notepad (default)
9
+ * Unix/MacOS: vi -> vim -> code -> nano (default)
10
+ */
11
+ const EDITORS = ['vi', 'vim', 'nano', 'code', 'notepad'];
12
+ /**
13
+ * Determines if an editor needs shell wrapper to execute.
14
+ * - vi/vim/nano: Better without shell (direct TTY access)
15
+ * - code: Needs shell on Windows (code.cmd is a batch file)
16
+ * - notepad: Works either way
17
+ */
18
+ function needsShell(editor) {
19
+ return editor === 'code' || editor === 'notepad';
20
+ }
21
+ function isAvailable(editor) {
22
+ // Notepad is always available on Windows and doesn't support --version
23
+ if (editor === 'notepad' && process.platform === 'win32') {
24
+ return true;
25
+ }
26
+ // Test if the command is actually executable, not just if it exists
27
+ // shell: true is required to execute batch files (.cmd/.bat) on Windows (e.g., code.cmd)
28
+ // and works correctly on Linux/macOS as well (uses /bin/sh)
29
+ const result = spawnSync(editor, ['--version'], {
30
+ stdio: 'ignore',
31
+ shell: needsShell(editor),
32
+ timeout: 1000,
33
+ });
34
+ return !result.error;
35
+ }
36
+ export function detectEditor() {
37
+ for (const editor of EDITORS) {
38
+ if (isAvailable(editor)) {
39
+ return editor;
40
+ }
41
+ }
42
+ throw new Error('No text editor found. Please install one of: vi, vim, nano, VS Code, Notepad');
43
+ }
44
+ export function openInEditor(content, baseFilename = 'papyrus.md') {
45
+ // Create temp file
46
+ const ext = path.extname(baseFilename);
47
+ const base = path.basename(baseFilename, ext);
48
+ const randomSuffix = crypto.randomBytes(6).toString('hex');
49
+ const uniqueFilename = `${base}-${randomSuffix}${ext}`;
50
+ const tempFile = path.join(os.tmpdir(), uniqueFilename);
51
+ // Write initial content
52
+ fs.writeFileSync(tempFile, content, 'utf-8');
53
+ try {
54
+ const editor = detectEditor();
55
+ console.log(`Opening in ${editor}...`);
56
+ // Determine args based on editor
57
+ const args = editor === 'code' ? ['--wait', tempFile] : [tempFile];
58
+ const result = spawnSync(editor, args, {
59
+ stdio: 'inherit',
60
+ shell: needsShell(editor), // Required for batch files on Windows
61
+ });
62
+ if (result.error) {
63
+ throw new Error(`Failed to open editor: ${result.error.message}`);
64
+ }
65
+ if (result.status !== 0 && result.status !== null) {
66
+ throw new Error(`Editor exited with code ${result.status}`);
67
+ }
68
+ const editedContent = fs.readFileSync(tempFile, 'utf-8');
69
+ // Clean up tem file
70
+ fs.unlinkSync(tempFile);
71
+ return editedContent;
72
+ }
73
+ catch (error) {
74
+ // Clean up temp file on error
75
+ if (fs.existsSync(tempFile)) {
76
+ fs.unlinkSync(tempFile);
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/utils/editor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const JOURNAL_TEMPATE = "\n<!-- ================================================================ -->\n<!-- Symbol Guide -->\n<!-- ---------------------------------------------------------------- -->\n<!-- @person Tag people @alice, @bob -->\n<!-- #project Tag projects #papyrus, #feature-x -->\n<!-- +tech Tag technologies +typescript, +react -->\n<!-- ================================================================ -->\n";
2
+ export declare function stripTemplateComments(content: string): string;
@@ -0,0 +1,17 @@
1
+ export const JOURNAL_TEMPATE = `
2
+ <!-- ================================================================ -->
3
+ <!-- Symbol Guide -->
4
+ <!-- ---------------------------------------------------------------- -->
5
+ <!-- @person Tag people @alice, @bob -->
6
+ <!-- #project Tag projects #papyrus, #feature-x -->
7
+ <!-- +tech Tag technologies +typescript, +react -->
8
+ <!-- ================================================================ -->
9
+ `;
10
+ export function stripTemplateComments(content) {
11
+ return content
12
+ .split('\n')
13
+ .filter((line) => !line.trim().match(/^<!--.*-->$/))
14
+ .join('\n')
15
+ .trim();
16
+ }
17
+ //# sourceMappingURL=template.js.map