openwriter 0.5.4 → 0.6.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/bin/pad.js +3 -0
- package/dist/client/assets/index-CCMCrgTu.js +209 -0
- package/dist/client/assets/index-DN1_4Au6.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/connection-routes.js +327 -0
- package/dist/server/connections.js +35 -0
- package/dist/server/documents.js +30 -24
- package/dist/server/export-html-template.js +1 -1
- package/dist/server/export-routes.js +1 -1
- package/dist/server/gdoc-import.js +2 -2
- package/dist/server/git-sync.js +30 -30
- package/dist/server/helpers.js +175 -20
- package/dist/server/image-upload.js +10 -7
- package/dist/server/index.js +162 -5
- package/dist/server/marks.js +11 -11
- package/dist/server/mcp.js +134 -50
- package/dist/server/plugin-manager.js +2 -2
- package/dist/server/scheduler-routes.js +121 -0
- package/dist/server/state.js +126 -42
- package/dist/server/versions.js +6 -2
- package/dist/server/workspace-routes.js +14 -13
- package/dist/server/workspaces.js +7 -7
- package/dist/server/ws.js +15 -0
- package/package.json +1 -1
- package/skill/SKILL.md +95 -11
- package/dist/client/assets/index-BAbqg4Q8.js +0 -210
- package/dist/client/assets/index-BR_sMmFf.css +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform connection proxy — all connections live on the platform (Neon),
|
|
3
|
+
* local app proxies through the publish API.
|
|
4
|
+
*/
|
|
5
|
+
import { readConfig, getActiveProfile } from './helpers.js';
|
|
6
|
+
const DEFAULT_API_URL = 'https://publish.openwriter.io';
|
|
7
|
+
/** Get API key and URL from plugin config */
|
|
8
|
+
function getPublishConfig() {
|
|
9
|
+
const config = readConfig();
|
|
10
|
+
const publishConfig = config.plugins?.['@openwriter/plugin-publish']?.config || {};
|
|
11
|
+
return {
|
|
12
|
+
apiKey: publishConfig['api-key'] || '',
|
|
13
|
+
apiUrl: publishConfig['api-url'] || DEFAULT_API_URL,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Authenticated fetch to the platform API */
|
|
17
|
+
export async function platformFetch(path, options = {}) {
|
|
18
|
+
const { apiKey, apiUrl } = getPublishConfig();
|
|
19
|
+
const profile = getActiveProfile();
|
|
20
|
+
if (!apiKey) {
|
|
21
|
+
throw new Error('Not authenticated. Use the publish plugin to log in first.');
|
|
22
|
+
}
|
|
23
|
+
const headers = {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
Authorization: `Bearer ${apiKey}`,
|
|
26
|
+
'X-Profile': profile,
|
|
27
|
+
...(options.headers || {}),
|
|
28
|
+
};
|
|
29
|
+
return fetch(`${apiUrl}${path}`, { ...options, headers });
|
|
30
|
+
}
|
|
31
|
+
/** Check if the user is authenticated with the platform */
|
|
32
|
+
export function isAuthenticated() {
|
|
33
|
+
const { apiKey } = getPublishConfig();
|
|
34
|
+
return !!apiKey;
|
|
35
|
+
}
|
package/dist/server/documents.js
CHANGED
|
@@ -10,23 +10,23 @@ import trash from 'trash';
|
|
|
10
10
|
import { tiptapToMarkdown, markdownToTiptap } from './markdown.js';
|
|
11
11
|
import { parseMarkdownContent } from './compact.js';
|
|
12
12
|
import { getDocument, getTitle, getFilePath, getIsTemp, getMetadata, save, cancelDebouncedSave, setActiveDocument, registerExternalDoc, unregisterExternalDoc, getExternalDocs, cacheActiveDocument, getCachedDocument, invalidateDocCache, removePendingCacheEntry, } from './state.js';
|
|
13
|
-
import {
|
|
13
|
+
import { getDataDir, TEMP_PREFIX, ensureDataDir, filePathForTitle, tempFilePath, generateNodeId, resolveDocPath, isExternalDoc, atomicWriteFileSync } from './helpers.js';
|
|
14
14
|
import { ensureDocId } from './versions.js';
|
|
15
15
|
import { renameDocInAllWorkspaces, removeDocFromAllWorkspaces } from './workspaces.js';
|
|
16
16
|
import { renameMark } from './marks.js';
|
|
17
17
|
import { getDocId as getActiveDocId } from './state.js';
|
|
18
|
-
|
|
19
|
-
/** Scan files for matching docId. Checks active doc first (free), then
|
|
18
|
+
function getDocOrderFile() { return join(getDataDir(), '_doc-order.json'); }
|
|
19
|
+
/** Scan files for matching docId. Checks active doc first (free), then getDataDir(), then external docs. */
|
|
20
20
|
export function filenameByDocId(docId) {
|
|
21
21
|
// Fast path: check active document (no disk read)
|
|
22
22
|
if (getActiveDocId() === docId) {
|
|
23
23
|
return getActiveFilename();
|
|
24
24
|
}
|
|
25
|
-
// Scan
|
|
25
|
+
// Scan getDataDir() files
|
|
26
26
|
ensureDataDir();
|
|
27
|
-
for (const f of readdirSync(
|
|
27
|
+
for (const f of readdirSync(getDataDir()).filter(f => f.endsWith('.md'))) {
|
|
28
28
|
try {
|
|
29
|
-
const raw = readFileSync(join(
|
|
29
|
+
const raw = readFileSync(join(getDataDir(), f), 'utf-8');
|
|
30
30
|
const { data } = matter(raw);
|
|
31
31
|
if (data.docId === docId)
|
|
32
32
|
return f;
|
|
@@ -56,9 +56,9 @@ export function resolveDocId(docId) {
|
|
|
56
56
|
}
|
|
57
57
|
function readDocOrder() {
|
|
58
58
|
try {
|
|
59
|
-
if (!existsSync(
|
|
59
|
+
if (!existsSync(getDocOrderFile()))
|
|
60
60
|
return [];
|
|
61
|
-
return JSON.parse(readFileSync(
|
|
61
|
+
return JSON.parse(readFileSync(getDocOrderFile(), 'utf-8'));
|
|
62
62
|
}
|
|
63
63
|
catch {
|
|
64
64
|
return [];
|
|
@@ -66,7 +66,7 @@ function readDocOrder() {
|
|
|
66
66
|
}
|
|
67
67
|
function writeDocOrder(order) {
|
|
68
68
|
ensureDataDir();
|
|
69
|
-
writeFileSync(
|
|
69
|
+
writeFileSync(getDocOrderFile(), JSON.stringify(order, null, 2), 'utf-8');
|
|
70
70
|
}
|
|
71
71
|
export function reorderDocs(orderedFilenames) {
|
|
72
72
|
writeDocOrder(orderedFilenames);
|
|
@@ -74,10 +74,10 @@ export function reorderDocs(orderedFilenames) {
|
|
|
74
74
|
export function listDocuments() {
|
|
75
75
|
ensureDataDir();
|
|
76
76
|
const currentPath = getFilePath();
|
|
77
|
-
const files = readdirSync(
|
|
77
|
+
const files = readdirSync(getDataDir())
|
|
78
78
|
.filter((f) => f.endsWith('.md'))
|
|
79
79
|
.map((f) => {
|
|
80
|
-
const fullPath = join(
|
|
80
|
+
const fullPath = join(getDataDir(), f);
|
|
81
81
|
try {
|
|
82
82
|
const stat = statSync(fullPath);
|
|
83
83
|
const raw = readFileSync(fullPath, 'utf-8');
|
|
@@ -100,6 +100,7 @@ export function listDocuments() {
|
|
|
100
100
|
wordCount,
|
|
101
101
|
isActive: fullPath === currentPath,
|
|
102
102
|
...(data.docId ? { docId: data.docId } : {}),
|
|
103
|
+
...(data.newsletterContext?.lastSend?.sentAt ? { lastSent: data.newsletterContext.lastSend.sentAt } : {}),
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
catch {
|
|
@@ -128,6 +129,7 @@ export function listDocuments() {
|
|
|
128
129
|
wordCount,
|
|
129
130
|
isActive: extPath === currentPath,
|
|
130
131
|
...(data.docId ? { docId: data.docId } : {}),
|
|
132
|
+
...(data.newsletterContext?.lastSend?.sentAt ? { lastSent: data.newsletterContext.lastSend.sentAt } : {}),
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
135
|
catch { /* skip unreadable external files */ }
|
|
@@ -157,10 +159,10 @@ export function listDocuments() {
|
|
|
157
159
|
// ============================================================================
|
|
158
160
|
export function listArchivedDocuments() {
|
|
159
161
|
ensureDataDir();
|
|
160
|
-
const files = readdirSync(
|
|
162
|
+
const files = readdirSync(getDataDir())
|
|
161
163
|
.filter((f) => f.endsWith('.md'))
|
|
162
164
|
.map((f) => {
|
|
163
|
-
const fullPath = join(
|
|
165
|
+
const fullPath = join(getDataDir(), f);
|
|
164
166
|
try {
|
|
165
167
|
const stat = statSync(fullPath);
|
|
166
168
|
const raw = readFileSync(fullPath, 'utf-8');
|
|
@@ -207,10 +209,10 @@ export function archiveDocument(filename) {
|
|
|
207
209
|
const isArchivingActive = targetPath === getFilePath();
|
|
208
210
|
if (isArchivingActive) {
|
|
209
211
|
// Switch to most recent remaining doc
|
|
210
|
-
const remaining = readdirSync(
|
|
212
|
+
const remaining = readdirSync(getDataDir())
|
|
211
213
|
.filter((f) => f.endsWith('.md') && f !== filename)
|
|
212
214
|
.map((f) => {
|
|
213
|
-
const fullPath = join(
|
|
215
|
+
const fullPath = join(getDataDir(), f);
|
|
214
216
|
try {
|
|
215
217
|
const stat = statSync(fullPath);
|
|
216
218
|
const raw = readFileSync(fullPath, 'utf-8');
|
|
@@ -255,9 +257,9 @@ export function searchDocuments(query, includeArchived = false) {
|
|
|
255
257
|
// Collect all files (same pattern as listDocuments)
|
|
256
258
|
ensureDataDir();
|
|
257
259
|
const allFiles = [];
|
|
258
|
-
for (const f of readdirSync(
|
|
260
|
+
for (const f of readdirSync(getDataDir()).filter(f => f.endsWith('.md'))) {
|
|
259
261
|
try {
|
|
260
|
-
const fullPath = join(
|
|
262
|
+
const fullPath = join(getDataDir(), f);
|
|
261
263
|
const mtime = statSync(fullPath).mtime;
|
|
262
264
|
const raw = readFileSync(fullPath, 'utf-8');
|
|
263
265
|
allFiles.push({ filename: f, path: fullPath, raw, mtime });
|
|
@@ -333,6 +335,10 @@ export function searchDocuments(query, includeArchived = false) {
|
|
|
333
335
|
return results;
|
|
334
336
|
}
|
|
335
337
|
export function switchDocument(filename) {
|
|
338
|
+
// No-op if already on this document — avoids save/reload cycle that can clear editor content
|
|
339
|
+
if (filename === getActiveFilename()) {
|
|
340
|
+
return { document: getDocument(), title: getTitle(), filename };
|
|
341
|
+
}
|
|
336
342
|
// Cancel any pending debounced save, then save current doc immediately.
|
|
337
343
|
cancelDebouncedSave();
|
|
338
344
|
save();
|
|
@@ -429,7 +435,7 @@ export function createDocument(title, content, path) {
|
|
|
429
435
|
* so the user's editor isn't hijacked during agent content generation.
|
|
430
436
|
* The file is written with agentCreated: true in frontmatter.
|
|
431
437
|
*/
|
|
432
|
-
export function createDocumentFile(title, path) {
|
|
438
|
+
export function createDocumentFile(title, path, extraMeta) {
|
|
433
439
|
const docTitle = title || 'Untitled';
|
|
434
440
|
let filePath;
|
|
435
441
|
let filename;
|
|
@@ -458,7 +464,7 @@ export function createDocumentFile(title, path) {
|
|
|
458
464
|
filename = filePath.split(/[/\\]/).pop();
|
|
459
465
|
}
|
|
460
466
|
const newDoc = { type: 'doc', content: [{ type: 'paragraph', content: [] }] };
|
|
461
|
-
const metadata = { title: docTitle, docId: generateNodeId(), agentCreated: true };
|
|
467
|
+
const metadata = { title: docTitle, docId: generateNodeId(), agentCreated: true, ...extraMeta };
|
|
462
468
|
const markdown = tiptapToMarkdown(newDoc, docTitle, metadata);
|
|
463
469
|
ensureDataDir();
|
|
464
470
|
atomicWriteFileSync(filePath, markdown);
|
|
@@ -473,7 +479,7 @@ export async function deleteDocument(filename) {
|
|
|
473
479
|
if (isExternalDoc(filename)) {
|
|
474
480
|
unregisterExternalDoc(targetPath);
|
|
475
481
|
}
|
|
476
|
-
const allDocs = readdirSync(
|
|
482
|
+
const allDocs = readdirSync(getDataDir()).filter((f) => f.endsWith('.md'));
|
|
477
483
|
if (allDocs.length <= 1) {
|
|
478
484
|
throw new Error('Cannot delete the only document');
|
|
479
485
|
}
|
|
@@ -482,9 +488,9 @@ export async function deleteDocument(filename) {
|
|
|
482
488
|
await trash(targetPath);
|
|
483
489
|
}
|
|
484
490
|
if (isDeletingActive) {
|
|
485
|
-
const remaining = readdirSync(
|
|
491
|
+
const remaining = readdirSync(getDataDir())
|
|
486
492
|
.filter((f) => f.endsWith('.md'))
|
|
487
|
-
.map((f) => ({ name: f, path: join(
|
|
493
|
+
.map((f) => ({ name: f, path: join(getDataDir(), f), mtime: statSync(join(getDataDir(), f)).mtimeMs }))
|
|
488
494
|
.sort((a, b) => b.mtime - a.mtime);
|
|
489
495
|
if (remaining.length > 0) {
|
|
490
496
|
const next = remaining[0];
|
|
@@ -537,7 +543,7 @@ export function openFile(fullPath) {
|
|
|
537
543
|
save();
|
|
538
544
|
// Cache current doc before switching
|
|
539
545
|
cacheActiveDocument();
|
|
540
|
-
// Register as external if not in
|
|
546
|
+
// Register as external if not in getDataDir()
|
|
541
547
|
if (isExternalDoc(fullPath)) {
|
|
542
548
|
registerExternalDoc(fullPath);
|
|
543
549
|
}
|
|
@@ -554,7 +560,7 @@ export function openFile(fullPath) {
|
|
|
554
560
|
ensureDocId(parsed.metadata);
|
|
555
561
|
const baseName = fullPath.split(/[/\\]/).pop() || '';
|
|
556
562
|
setActiveDocument(parsed.document, parsed.title, fullPath, baseName.startsWith(TEMP_PREFIX), mtime, parsed.metadata);
|
|
557
|
-
// Use full path as filename for external docs, basename for
|
|
563
|
+
// Use full path as filename for external docs, basename for getDataDir() docs
|
|
558
564
|
const filename = isExternalDoc(fullPath) ? fullPath : baseName;
|
|
559
565
|
return { document: getDocument(), title: getTitle(), filename };
|
|
560
566
|
}
|
|
@@ -12,7 +12,7 @@ import { tiptapToMarkdown } from './markdown.js';
|
|
|
12
12
|
import { getDocument, getTitle, getPlainText, getMetadata } from './state.js';
|
|
13
13
|
import { buildExportHtml } from './export-html-template.js';
|
|
14
14
|
// markdown-it instance matching markdown-parse.ts configuration
|
|
15
|
-
const md = new MarkdownIt({ linkify: false });
|
|
15
|
+
const md = new MarkdownIt({ linkify: false, html: true });
|
|
16
16
|
md.enable('strikethrough');
|
|
17
17
|
md.use(markdownItIns);
|
|
18
18
|
md.use(markdownItMark);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { writeFileSync } from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
9
|
-
import {
|
|
9
|
+
import { getDataDir, ensureDataDir, sanitizeFilename } from './helpers.js';
|
|
10
10
|
import { createWorkspace, addDoc, addContainerToWorkspace } from './workspaces.js';
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// GOOGLE DOC → MARKDOWN CONVERSION
|
|
@@ -146,7 +146,7 @@ function elementsToMarkdown(elements) {
|
|
|
146
146
|
}
|
|
147
147
|
function writeDocFile(title, markdownBody) {
|
|
148
148
|
const filename = `${sanitizeFilename(title).substring(0, 200)}.md`;
|
|
149
|
-
const filepath = join(
|
|
149
|
+
const filepath = join(getDataDir(), filename);
|
|
150
150
|
const metadata = { title };
|
|
151
151
|
const content = `---\n${JSON.stringify(metadata)}\n---\n\n${markdownBody}`;
|
|
152
152
|
writeFileSync(filepath, content, 'utf-8');
|
package/dist/server/git-sync.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { execFile } from 'child_process';
|
|
6
6
|
import { existsSync, writeFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import { getDataDir, readConfig, saveConfig } from './helpers.js';
|
|
9
9
|
import { save, cancelDebouncedSave } from './state.js';
|
|
10
10
|
const GITIGNORE_CONTENT = `config.json\n.versions/\n`;
|
|
11
11
|
const NETWORK_TIMEOUT = 30000;
|
|
@@ -25,7 +25,7 @@ function exec(cmd, args, cwd, timeout = 10000) {
|
|
|
25
25
|
}
|
|
26
26
|
export async function isGitInstalled() {
|
|
27
27
|
try {
|
|
28
|
-
await exec('git', ['--version'],
|
|
28
|
+
await exec('git', ['--version'], getDataDir());
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
@@ -34,7 +34,7 @@ export async function isGitInstalled() {
|
|
|
34
34
|
}
|
|
35
35
|
export async function isGhInstalled() {
|
|
36
36
|
try {
|
|
37
|
-
await exec('gh', ['--version'],
|
|
37
|
+
await exec('gh', ['--version'], getDataDir());
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
catch {
|
|
@@ -43,7 +43,7 @@ export async function isGhInstalled() {
|
|
|
43
43
|
}
|
|
44
44
|
export async function isGhAuthenticated() {
|
|
45
45
|
try {
|
|
46
|
-
await exec('gh', ['auth', 'status'],
|
|
46
|
+
await exec('gh', ['auth', 'status'], getDataDir());
|
|
47
47
|
return true;
|
|
48
48
|
}
|
|
49
49
|
catch {
|
|
@@ -51,10 +51,10 @@ export async function isGhAuthenticated() {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
export function isGitRepo() {
|
|
54
|
-
return existsSync(join(
|
|
54
|
+
return existsSync(join(getDataDir(), '.git'));
|
|
55
55
|
}
|
|
56
56
|
function ensureGitignore() {
|
|
57
|
-
const gitignorePath = join(
|
|
57
|
+
const gitignorePath = join(getDataDir(), '.gitignore');
|
|
58
58
|
if (!existsSync(gitignorePath)) {
|
|
59
59
|
writeFileSync(gitignorePath, GITIGNORE_CONTENT, 'utf-8');
|
|
60
60
|
}
|
|
@@ -65,7 +65,7 @@ async function countPendingFiles() {
|
|
|
65
65
|
return 0;
|
|
66
66
|
try {
|
|
67
67
|
// Check for any changes (staged + unstaged + untracked)
|
|
68
|
-
const status = await exec('git', ['status', '--porcelain'],
|
|
68
|
+
const status = await exec('git', ['status', '--porcelain'], getDataDir());
|
|
69
69
|
if (!status)
|
|
70
70
|
return 0;
|
|
71
71
|
return status.split('\n').filter(Boolean).length;
|
|
@@ -79,7 +79,7 @@ export async function getPendingFiles() {
|
|
|
79
79
|
if (!isGitRepo())
|
|
80
80
|
return [];
|
|
81
81
|
try {
|
|
82
|
-
const output = await exec('git', ['status', '--porcelain'],
|
|
82
|
+
const output = await exec('git', ['status', '--porcelain'], getDataDir());
|
|
83
83
|
if (!output)
|
|
84
84
|
return [];
|
|
85
85
|
return output.split('\n').filter(Boolean).map(line => {
|
|
@@ -125,7 +125,7 @@ export async function getCapabilities() {
|
|
|
125
125
|
let remoteUrl;
|
|
126
126
|
if (isGitRepo()) {
|
|
127
127
|
try {
|
|
128
|
-
remoteUrl = await exec('git', ['remote', 'get-url', 'origin'],
|
|
128
|
+
remoteUrl = await exec('git', ['remote', 'get-url', 'origin'], getDataDir());
|
|
129
129
|
}
|
|
130
130
|
catch { /* no remote */ }
|
|
131
131
|
}
|
|
@@ -139,40 +139,40 @@ export async function getCapabilities() {
|
|
|
139
139
|
}
|
|
140
140
|
async function initRepo() {
|
|
141
141
|
if (!isGitRepo()) {
|
|
142
|
-
await exec('git', ['init'],
|
|
142
|
+
await exec('git', ['init'], getDataDir());
|
|
143
143
|
}
|
|
144
144
|
ensureGitignore();
|
|
145
145
|
// Ensure git user is configured (required for commits)
|
|
146
146
|
try {
|
|
147
|
-
await exec('git', ['config', 'user.name'],
|
|
147
|
+
await exec('git', ['config', 'user.name'], getDataDir());
|
|
148
148
|
}
|
|
149
149
|
catch {
|
|
150
|
-
await exec('git', ['config', 'user.name', 'OpenWriter'],
|
|
150
|
+
await exec('git', ['config', 'user.name', 'OpenWriter'], getDataDir());
|
|
151
151
|
}
|
|
152
152
|
try {
|
|
153
|
-
await exec('git', ['config', 'user.email'],
|
|
153
|
+
await exec('git', ['config', 'user.email'], getDataDir());
|
|
154
154
|
}
|
|
155
155
|
catch {
|
|
156
|
-
await exec('git', ['config', 'user.email', 'openwriter@local'],
|
|
156
|
+
await exec('git', ['config', 'user.email', 'openwriter@local'], getDataDir());
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
async function initialCommit() {
|
|
160
|
-
await exec('git', ['add', '-A'],
|
|
160
|
+
await exec('git', ['add', '-A'], getDataDir());
|
|
161
161
|
// Check if there's anything staged
|
|
162
|
-
const status = await exec('git', ['status', '--porcelain'],
|
|
162
|
+
const status = await exec('git', ['status', '--porcelain'], getDataDir());
|
|
163
163
|
if (!status)
|
|
164
164
|
return; // Nothing to commit
|
|
165
|
-
await exec('git', ['commit', '-m', 'Initial sync from OpenWriter'],
|
|
165
|
+
await exec('git', ['commit', '-m', 'Initial sync from OpenWriter'], getDataDir());
|
|
166
166
|
// Ensure branch is named 'main'
|
|
167
|
-
await exec('git', ['branch', '-M', 'main'],
|
|
167
|
+
await exec('git', ['branch', '-M', 'main'], getDataDir());
|
|
168
168
|
}
|
|
169
169
|
export async function setupWithGh(repoName, isPrivate) {
|
|
170
170
|
await initRepo();
|
|
171
171
|
await initialCommit();
|
|
172
172
|
const visibility = isPrivate ? '--private' : '--public';
|
|
173
173
|
// Create repo without --push, then push separately for better error control
|
|
174
|
-
await exec('gh', ['repo', 'create', repoName, visibility, '--source=.', '--remote=origin'],
|
|
175
|
-
await exec('git', ['push', '-u', 'origin', 'main'],
|
|
174
|
+
await exec('gh', ['repo', 'create', repoName, visibility, '--source=.', '--remote=origin'], getDataDir(), NETWORK_TIMEOUT);
|
|
175
|
+
await exec('git', ['push', '-u', 'origin', 'main'], getDataDir(), NETWORK_TIMEOUT);
|
|
176
176
|
saveConfig({
|
|
177
177
|
gitConfigured: true,
|
|
178
178
|
repoName,
|
|
@@ -201,11 +201,11 @@ export async function setupWithPat(pat, repoName, isPrivate) {
|
|
|
201
201
|
await initialCommit();
|
|
202
202
|
// Set remote
|
|
203
203
|
try {
|
|
204
|
-
await exec('git', ['remote', 'remove', 'origin'],
|
|
204
|
+
await exec('git', ['remote', 'remove', 'origin'], getDataDir());
|
|
205
205
|
}
|
|
206
206
|
catch { /* no remote */ }
|
|
207
|
-
await exec('git', ['remote', 'add', 'origin', remoteUrl],
|
|
208
|
-
await exec('git', ['push', '-u', 'origin', 'main'],
|
|
207
|
+
await exec('git', ['remote', 'add', 'origin', remoteUrl], getDataDir());
|
|
208
|
+
await exec('git', ['push', '-u', 'origin', 'main'], getDataDir(), NETWORK_TIMEOUT);
|
|
209
209
|
saveConfig({
|
|
210
210
|
gitConfigured: true,
|
|
211
211
|
gitPat: pat,
|
|
@@ -224,11 +224,11 @@ export async function connectExisting(remoteUrl, pat) {
|
|
|
224
224
|
finalUrl = remoteUrl.replace('https://', `https://${pat}@`);
|
|
225
225
|
}
|
|
226
226
|
try {
|
|
227
|
-
await exec('git', ['remote', 'remove', 'origin'],
|
|
227
|
+
await exec('git', ['remote', 'remove', 'origin'], getDataDir());
|
|
228
228
|
}
|
|
229
229
|
catch { /* no remote */ }
|
|
230
|
-
await exec('git', ['remote', 'add', 'origin', finalUrl],
|
|
231
|
-
await exec('git', ['push', '-u', 'origin', 'main'],
|
|
230
|
+
await exec('git', ['remote', 'add', 'origin', finalUrl], getDataDir());
|
|
231
|
+
await exec('git', ['push', '-u', 'origin', 'main'], getDataDir(), NETWORK_TIMEOUT);
|
|
232
232
|
saveConfig({
|
|
233
233
|
gitConfigured: true,
|
|
234
234
|
gitPat: pat,
|
|
@@ -246,16 +246,16 @@ export async function pushSync(onStatus) {
|
|
|
246
246
|
cancelDebouncedSave();
|
|
247
247
|
save();
|
|
248
248
|
ensureGitignore();
|
|
249
|
-
await exec('git', ['add', '-A'],
|
|
249
|
+
await exec('git', ['add', '-A'], getDataDir());
|
|
250
250
|
// Check if there's anything to commit
|
|
251
|
-
const status = await exec('git', ['status', '--porcelain'],
|
|
251
|
+
const status = await exec('git', ['status', '--porcelain'], getDataDir());
|
|
252
252
|
if (status) {
|
|
253
253
|
const timestamp = new Date().toLocaleString('en-US', {
|
|
254
254
|
month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit',
|
|
255
255
|
});
|
|
256
|
-
await exec('git', ['commit', '-m', `Sync: ${timestamp}`],
|
|
256
|
+
await exec('git', ['commit', '-m', `Sync: ${timestamp}`], getDataDir());
|
|
257
257
|
}
|
|
258
|
-
await exec('git', ['push'],
|
|
258
|
+
await exec('git', ['push'], getDataDir(), NETWORK_TIMEOUT);
|
|
259
259
|
const now = new Date().toISOString();
|
|
260
260
|
saveConfig({ lastSyncTime: now });
|
|
261
261
|
currentSyncState = 'synced';
|