@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.
- package/README.md +699 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth/index.d.ts +2 -0
- package/dist/commands/auth/index.js +18 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/auth/login.d.ts +1 -0
- package/dist/commands/auth/login.js +8 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +1 -0
- package/dist/commands/auth/logout.js +5 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/register.d.ts +1 -0
- package/dist/commands/auth/register.js +8 -0
- package/dist/commands/auth/register.js.map +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +3 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/journal/add.d.ts +2 -0
- package/dist/commands/journal/add.js +5 -0
- package/dist/commands/journal/add.js.map +1 -0
- package/dist/commands/journal/amend.d.ts +2 -0
- package/dist/commands/journal/amend.js +5 -0
- package/dist/commands/journal/amend.js.map +1 -0
- package/dist/commands/journal/edit.d.ts +6 -0
- package/dist/commands/journal/edit.js +60 -0
- package/dist/commands/journal/edit.js.map +1 -0
- package/dist/commands/journal/index.d.ts +2 -0
- package/dist/commands/journal/index.js +32 -0
- package/dist/commands/journal/index.js.map +1 -0
- package/dist/commands/journal/list.d.ts +1 -0
- package/dist/commands/journal/list.js +20 -0
- package/dist/commands/journal/list.js.map +1 -0
- package/dist/commands/journal/show.d.ts +2 -0
- package/dist/commands/journal/show.js +35 -0
- package/dist/commands/journal/show.js.map +1 -0
- package/dist/commands/journal/sync.d.ts +1 -0
- package/dist/commands/journal/sync.js +9 -0
- package/dist/commands/journal/sync.js.map +1 -0
- package/dist/commands/types.d.ts +9 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/components/Browser.d.ts +29 -0
- package/dist/components/Browser.js +124 -0
- package/dist/components/Browser.js.map +1 -0
- package/dist/components/BrowserFooter.d.ts +6 -0
- package/dist/components/BrowserFooter.js +6 -0
- package/dist/components/BrowserFooter.js.map +1 -0
- package/dist/components/BrowserHeader.d.ts +6 -0
- package/dist/components/BrowserHeader.js +6 -0
- package/dist/components/BrowserHeader.js.map +1 -0
- package/dist/components/ColdStart.d.ts +6 -0
- package/dist/components/ColdStart.js +24 -0
- package/dist/components/ColdStart.js.map +1 -0
- package/dist/components/FormInput.d.ts +10 -0
- package/dist/components/FormInput.js +7 -0
- package/dist/components/FormInput.js.map +1 -0
- package/dist/components/JournalListView.d.ts +10 -0
- package/dist/components/JournalListView.js +40 -0
- package/dist/components/JournalListView.js.map +1 -0
- package/dist/components/JournalViewer.d.ts +32 -0
- package/dist/components/JournalViewer.js +146 -0
- package/dist/components/JournalViewer.js.map +1 -0
- package/dist/components/LoginForm.d.ts +1 -0
- package/dist/components/LoginForm.js +68 -0
- package/dist/components/LoginForm.js.map +1 -0
- package/dist/components/Logo.d.ts +1 -0
- package/dist/components/Logo.js +57 -0
- package/dist/components/Logo.js.map +1 -0
- package/dist/components/RegisterForm.d.ts +1 -0
- package/dist/components/RegisterForm.js +72 -0
- package/dist/components/RegisterForm.js.map +1 -0
- package/dist/components/StatusMessage.d.ts +7 -0
- package/dist/components/StatusMessage.js +19 -0
- package/dist/components/StatusMessage.js.map +1 -0
- package/dist/components/SyncProgress.d.ts +1 -0
- package/dist/components/SyncProgress.js +46 -0
- package/dist/components/SyncProgress.js.map +1 -0
- package/dist/lib/api/api-client.d.ts +23 -0
- package/dist/lib/api/api-client.js +111 -0
- package/dist/lib/api/api-client.js.map +1 -0
- package/dist/lib/api/index.d.ts +3 -0
- package/dist/lib/api/index.js +6 -0
- package/dist/lib/api/index.js.map +1 -0
- package/dist/lib/auth/index.d.ts +1 -0
- package/dist/lib/auth/index.js +2 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/require-auth.d.ts +67 -0
- package/dist/lib/auth/require-auth.js +107 -0
- package/dist/lib/auth/require-auth.js.map +1 -0
- package/dist/lib/storage/base-storage.d.ts +50 -0
- package/dist/lib/storage/base-storage.js +91 -0
- package/dist/lib/storage/base-storage.js.map +1 -0
- package/dist/lib/storage/config-store.d.ts +40 -0
- package/dist/lib/storage/config-store.js +63 -0
- package/dist/lib/storage/config-store.js.map +1 -0
- package/dist/lib/storage/index.d.ts +12 -0
- package/dist/lib/storage/index.js +13 -0
- package/dist/lib/storage/index.js.map +1 -0
- package/dist/lib/storage/journal-storage.d.ts +46 -0
- package/dist/lib/storage/journal-storage.js +78 -0
- package/dist/lib/storage/journal-storage.js.map +1 -0
- package/dist/lib/storage/sync-meta-store.d.ts +37 -0
- package/dist/lib/storage/sync-meta-store.js +50 -0
- package/dist/lib/storage/sync-meta-store.js.map +1 -0
- package/dist/lib/storage/token-store.d.ts +25 -0
- package/dist/lib/storage/token-store.js +40 -0
- package/dist/lib/storage/token-store.js.map +1 -0
- package/dist/lib/sync/sync-engine.d.ts +13 -0
- package/dist/lib/sync/sync-engine.js +96 -0
- package/dist/lib/sync/sync-engine.js.map +1 -0
- package/dist/utils/date.d.ts +58 -0
- package/dist/utils/date.js +117 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/editor.d.ts +2 -0
- package/dist/utils/editor.js +81 -0
- package/dist/utils/editor.js.map +1 -0
- package/dist/utils/template.d.ts +2 -0
- package/dist/utils/template.js +17 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/token.d.ts +20 -0
- package/dist/utils/token.js +64 -0
- package/dist/utils/token.js.map +1 -0
- 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":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,MAAc,CAAC;IAEnB,0CAA0C;IAC1C,IAAI,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC;IAChD,CAAC;IACD,uBAAuB;SAClB,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,GAAG,YAAY,EAAE,CAAC;IAC1B,CAAC;SAAM,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;QACjC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC;SAAM,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC;IACD,0CAA0C;SACrC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;IAC1D,CAAC;IACD,kBAAkB;SACb,CAAC;QACJ,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;IAED,+BAA+B;IAC/B,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,6BAA6B,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,gBAAgB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAAkB;IAC1D,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,0BAA0B;IACtF,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,8DAA8D;IAC9D,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;IACjD,MAAM,OAAO,GAA+B;QAC1C,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,OAAO,EAAE,MAAM;KAChB,CAAC;IAEF,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -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
|