pushwork 1.1.4 → 2.0.0-a.sub.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.
Files changed (100) hide show
  1. package/CLAUDE.md +9 -5
  2. package/dist/cli.js +48 -55
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands.d.ts +5 -1
  5. package/dist/commands.d.ts.map +1 -1
  6. package/dist/commands.js +262 -263
  7. package/dist/commands.js.map +1 -1
  8. package/dist/core/change-detection.d.ts +1 -1
  9. package/dist/core/change-detection.d.ts.map +1 -1
  10. package/dist/core/change-detection.js +66 -103
  11. package/dist/core/change-detection.js.map +1 -1
  12. package/dist/core/config.d.ts +1 -1
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/core/config.js +14 -57
  15. package/dist/core/config.js.map +1 -1
  16. package/dist/core/index.d.ts +5 -5
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +5 -21
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/move-detection.d.ts +2 -2
  21. package/dist/core/move-detection.d.ts.map +1 -1
  22. package/dist/core/move-detection.js +9 -13
  23. package/dist/core/move-detection.js.map +1 -1
  24. package/dist/core/snapshot.d.ts +1 -1
  25. package/dist/core/snapshot.d.ts.map +1 -1
  26. package/dist/core/snapshot.js +9 -46
  27. package/dist/core/snapshot.js.map +1 -1
  28. package/dist/core/sync-engine.d.ts +1 -1
  29. package/dist/core/sync-engine.d.ts.map +1 -1
  30. package/dist/core/sync-engine.js +113 -150
  31. package/dist/core/sync-engine.js.map +1 -1
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -20
  35. package/dist/index.js.map +1 -1
  36. package/dist/types/config.d.ts +7 -6
  37. package/dist/types/config.d.ts.map +1 -1
  38. package/dist/types/config.js +1 -5
  39. package/dist/types/config.js.map +1 -1
  40. package/dist/types/documents.js +4 -7
  41. package/dist/types/documents.js.map +1 -1
  42. package/dist/types/index.d.ts +3 -3
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist/types/index.js +3 -19
  45. package/dist/types/index.js.map +1 -1
  46. package/dist/types/snapshot.js +1 -2
  47. package/dist/utils/content.js +4 -8
  48. package/dist/utils/content.js.map +1 -1
  49. package/dist/utils/directory.js +5 -9
  50. package/dist/utils/directory.js.map +1 -1
  51. package/dist/utils/fs.d.ts +1 -1
  52. package/dist/utils/fs.d.ts.map +1 -1
  53. package/dist/utils/fs.js +34 -84
  54. package/dist/utils/fs.js.map +1 -1
  55. package/dist/utils/index.d.ts +4 -4
  56. package/dist/utils/index.d.ts.map +1 -1
  57. package/dist/utils/index.js +4 -20
  58. package/dist/utils/index.js.map +1 -1
  59. package/dist/utils/mime-types.js +5 -43
  60. package/dist/utils/mime-types.js.map +1 -1
  61. package/dist/utils/network-sync.d.ts +13 -8
  62. package/dist/utils/network-sync.d.ts.map +1 -1
  63. package/dist/utils/network-sync.js +65 -137
  64. package/dist/utils/network-sync.js.map +1 -1
  65. package/dist/utils/node-polyfills.d.ts +9 -0
  66. package/dist/utils/node-polyfills.d.ts.map +1 -0
  67. package/dist/utils/node-polyfills.js +9 -0
  68. package/dist/utils/node-polyfills.js.map +1 -0
  69. package/dist/utils/output.js +32 -39
  70. package/dist/utils/output.js.map +1 -1
  71. package/dist/utils/repo-factory.d.ts +8 -2
  72. package/dist/utils/repo-factory.d.ts.map +1 -1
  73. package/dist/utils/repo-factory.js +38 -47
  74. package/dist/utils/repo-factory.js.map +1 -1
  75. package/dist/utils/string-similarity.js +1 -5
  76. package/dist/utils/string-similarity.js.map +1 -1
  77. package/dist/utils/text-diff.js +5 -43
  78. package/dist/utils/text-diff.js.map +1 -1
  79. package/dist/utils/trace.js +6 -11
  80. package/dist/utils/trace.js.map +1 -1
  81. package/package.json +7 -5
  82. package/src/cli.ts +25 -34
  83. package/src/commands.ts +75 -11
  84. package/src/core/change-detection.ts +4 -4
  85. package/src/core/config.ts +2 -12
  86. package/src/core/index.ts +5 -5
  87. package/src/core/move-detection.ts +4 -4
  88. package/src/core/snapshot.ts +3 -3
  89. package/src/core/sync-engine.ts +11 -16
  90. package/src/index.ts +4 -4
  91. package/src/types/config.ts +8 -8
  92. package/src/types/index.ts +3 -3
  93. package/src/utils/directory.ts +1 -1
  94. package/src/utils/fs.ts +6 -4
  95. package/src/utils/index.ts +4 -4
  96. package/src/utils/network-sync.ts +62 -115
  97. package/src/utils/node-polyfills.ts +8 -0
  98. package/src/utils/repo-factory.ts +55 -10
  99. package/src/utils/trace.ts +1 -1
  100. package/tsconfig.json +2 -1
package/dist/commands.js CHANGED
@@ -1,82 +1,32 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.init = init;
40
- exports.sync = sync;
41
- exports.diff = diff;
42
- exports.status = status;
43
- exports.log = log;
44
- exports.checkout = checkout;
45
- exports.clone = clone;
46
- exports.url = url;
47
- exports.rm = rm;
48
- exports.commit = commit;
49
- exports.ls = ls;
50
- exports.config = config;
51
- exports.watch = watch;
52
- exports.root = root;
53
- const path = __importStar(require("path"));
54
- const fs = __importStar(require("fs/promises"));
55
- const fsSync = __importStar(require("fs"));
56
- const diffLib = __importStar(require("diff"));
57
- const child_process_1 = require("child_process");
58
- const core_1 = require("./core");
59
- const utils_1 = require("./utils");
60
- const config_1 = require("./core/config");
61
- const repo_factory_1 = require("./utils/repo-factory");
62
- const output_1 = require("./utils/output");
63
- const network_sync_1 = require("./utils/network-sync");
64
- const chalk_1 = __importDefault(require("chalk"));
1
+ import * as path from "path";
2
+ import * as fs from "fs/promises";
3
+ import * as fsSync from "fs";
4
+ import * as diffLib from "diff";
5
+ import { spawn } from "child_process";
6
+ import { SyncEngine } from "./core/index.js";
7
+ import { pathExists, ensureDirectoryExists, formatRelativePath } from "./utils/index.js";
8
+ import { ConfigManager } from "./core/config.js";
9
+ import { createRepo, createEphemeralRepo } from "./utils/repo-factory.js";
10
+ import { out } from "./utils/output.js";
11
+ import { waitForSync } from "./utils/network-sync.js";
12
+ import { readDocContent } from "./utils/text-diff.js";
13
+ import { DEFAULT_SYNC_SERVER } from "./types/config.js";
14
+ import chalk from "chalk";
65
15
  /**
66
16
  * Initialize repository directory structure and configuration
67
17
  * Shared logic for init and clone commands
68
18
  */
69
19
  async function initializeRepository(resolvedPath, overrides) {
70
20
  // Create .pushwork directory structure
71
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
72
- await (0, utils_1.ensureDirectoryExists)(syncToolDir);
73
- await (0, utils_1.ensureDirectoryExists)(path.join(syncToolDir, "automerge"));
21
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
22
+ await ensureDirectoryExists(syncToolDir);
23
+ await ensureDirectoryExists(path.join(syncToolDir, "automerge"));
74
24
  // Create configuration with overrides
75
- const configManager = new config_1.ConfigManager(resolvedPath);
25
+ const configManager = new ConfigManager(resolvedPath);
76
26
  const config = await configManager.initializeWithOverrides(overrides);
77
27
  // Create repository and sync engine
78
- const repo = await (0, repo_factory_1.createRepo)(resolvedPath, config);
79
- const syncEngine = new core_1.SyncEngine(repo, resolvedPath, config);
28
+ const repo = await createRepo(resolvedPath, config);
29
+ const syncEngine = new SyncEngine(repo, resolvedPath, config);
80
30
  return { config, repo, syncEngine };
81
31
  }
82
32
  /**
@@ -86,12 +36,12 @@ async function initializeRepository(resolvedPath, overrides) {
86
36
  async function setupCommandContext(workingDir = process.cwd(), options) {
87
37
  const resolvedPath = path.resolve(workingDir);
88
38
  // Check if initialized
89
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
90
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
39
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
40
+ if (!(await pathExists(syncToolDir))) {
91
41
  throw new Error('Directory not initialized for sync. Run "pushwork init" first.');
92
42
  }
93
43
  // Load configuration
94
- const configManager = new config_1.ConfigManager(resolvedPath);
44
+ const configManager = new ConfigManager(resolvedPath);
95
45
  let config;
96
46
  if (options?.forceDefaults) {
97
47
  // Force mode: use defaults, only preserving root_directory_url from local config
@@ -109,9 +59,9 @@ async function setupCommandContext(workingDir = process.cwd(), options) {
109
59
  config = { ...config, sync_enabled: options.syncEnabled };
110
60
  }
111
61
  // Create repo with config
112
- const repo = await (0, repo_factory_1.createRepo)(resolvedPath, config);
62
+ const repo = await createRepo(resolvedPath, config);
113
63
  // Create sync engine
114
- const syncEngine = new core_1.SyncEngine(repo, resolvedPath, config);
64
+ const syncEngine = new SyncEngine(repo, resolvedPath, config);
115
65
  return {
116
66
  repo,
117
67
  syncEngine,
@@ -156,24 +106,23 @@ async function safeRepoShutdown(repo) {
156
106
  /**
157
107
  * Initialize sync in a directory
158
108
  */
159
- async function init(targetPath, options = {}) {
109
+ export async function init(targetPath, options = {}) {
160
110
  const resolvedPath = path.resolve(targetPath);
161
- output_1.out.task(`Initializing`);
162
- await (0, utils_1.ensureDirectoryExists)(resolvedPath);
111
+ out.task(`Initializing`);
112
+ await ensureDirectoryExists(resolvedPath);
163
113
  // Check if already initialized
164
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
165
- if (await (0, utils_1.pathExists)(syncToolDir)) {
166
- output_1.out.error("Directory already initialized for sync");
167
- output_1.out.exit(1);
114
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
115
+ if (await pathExists(syncToolDir)) {
116
+ out.error("Directory already initialized for sync");
117
+ out.exit(1);
168
118
  }
169
119
  // Initialize repository with optional CLI overrides
170
- output_1.out.update("Setting up repository");
120
+ out.update("Setting up repository");
171
121
  const { repo, syncEngine, config } = await initializeRepository(resolvedPath, {
172
122
  sync_server: options.syncServer,
173
- sync_server_storage_id: options.syncServerStorageId,
174
123
  });
175
124
  // Create new root directory document
176
- output_1.out.update("Creating root directory");
125
+ out.update("Creating root directory");
177
126
  const dirName = path.basename(resolvedPath);
178
127
  const rootDoc = {
179
128
  "@patchwork": { type: "folder" },
@@ -187,31 +136,31 @@ async function init(targetPath, options = {}) {
187
136
  // Wait for root document to sync to server if sync is enabled
188
137
  // This ensures the document is uploaded before we exit
189
138
  // waitForSync() verifies the server has the document by comparing local and remote heads
190
- if (config.sync_enabled && config.sync_server_storage_id) {
191
- output_1.out.update("Syncing to server");
192
- const { failed } = await (0, network_sync_1.waitForSync)([rootHandle], config.sync_server_storage_id);
139
+ if (config.sync_enabled && config.sync_server) {
140
+ out.update("Syncing to server");
141
+ const { failed } = await waitForSync([rootHandle]);
193
142
  if (failed.length > 0) {
194
- output_1.out.taskLine("Root document failed to sync to server", true);
143
+ out.taskLine("Root document failed to sync to server", true);
195
144
  // Continue anyway - the document is created locally and will sync later
196
145
  }
197
146
  }
198
147
  // Run initial sync to capture existing files
199
- output_1.out.update("Running initial sync");
148
+ out.update("Running initial sync");
200
149
  const result = await syncEngine.sync();
201
- output_1.out.update("Writing to disk");
150
+ out.update("Writing to disk");
202
151
  await safeRepoShutdown(repo);
203
- output_1.out.done("Initialized");
204
- output_1.out.successBlock("INITIALIZED", rootHandle.url);
152
+ out.done("Initialized");
153
+ out.successBlock("INITIALIZED", rootHandle.url);
205
154
  if (result.filesChanged > 0) {
206
- output_1.out.info(`Synced ${result.filesChanged} ${plural("file", result.filesChanged)}`);
155
+ out.info(`Synced ${result.filesChanged} ${plural("file", result.filesChanged)}`);
207
156
  }
208
157
  process.exit();
209
158
  }
210
159
  /**
211
160
  * Run bidirectional sync
212
161
  */
213
- async function sync(targetPath = ".", options) {
214
- output_1.out.task(options.nuclear
162
+ export async function sync(targetPath = ".", options) {
163
+ out.task(options.nuclear
215
164
  ? "Nuclear syncing"
216
165
  : options.gentle
217
166
  ? "Gentle syncing"
@@ -223,78 +172,78 @@ async function sync(targetPath = ".", options) {
223
172
  await syncEngine.nuclearReset();
224
173
  }
225
174
  if (options.dryRun) {
226
- output_1.out.update("Analyzing changes");
175
+ out.update("Analyzing changes");
227
176
  const preview = await syncEngine.previewChanges();
228
177
  if (preview.changes.length === 0 && preview.moves.length === 0) {
229
- output_1.out.done("Already synced");
178
+ out.done("Already synced");
230
179
  return;
231
180
  }
232
- output_1.out.done();
233
- output_1.out.infoBlock("CHANGES");
234
- output_1.out.obj({
181
+ out.done();
182
+ out.infoBlock("CHANGES");
183
+ out.obj({
235
184
  Changes: preview.changes.length.toString(),
236
185
  Moves: preview.moves.length > 0 ? preview.moves.length.toString() : undefined,
237
186
  });
238
- output_1.out.log("");
239
- output_1.out.log("Files:");
187
+ out.log("");
188
+ out.log("Files:");
240
189
  for (const change of preview.changes.slice(0, 10)) {
241
190
  const prefix = change.changeType === "local_only"
242
191
  ? "[local] "
243
192
  : change.changeType === "remote_only"
244
193
  ? "[remote] "
245
194
  : "[conflict]";
246
- output_1.out.log(` ${prefix} ${change.path}`);
195
+ out.log(` ${prefix} ${change.path}`);
247
196
  }
248
197
  if (preview.changes.length > 10) {
249
- output_1.out.log(` ... and ${preview.changes.length - 10} more`);
198
+ out.log(` ... and ${preview.changes.length - 10} more`);
250
199
  }
251
200
  if (preview.moves.length > 0) {
252
- output_1.out.log("");
253
- output_1.out.log("Moves:");
201
+ out.log("");
202
+ out.log("Moves:");
254
203
  for (const move of preview.moves.slice(0, 5)) {
255
- output_1.out.log(` ${move.fromPath} → ${move.toPath}`);
204
+ out.log(` ${move.fromPath} → ${move.toPath}`);
256
205
  }
257
206
  if (preview.moves.length > 5) {
258
- output_1.out.log(` ... and ${preview.moves.length - 5} more`);
207
+ out.log(` ... and ${preview.moves.length - 5} more`);
259
208
  }
260
209
  }
261
- output_1.out.log("");
262
- output_1.out.log("Run without --dry-run to apply these changes");
210
+ out.log("");
211
+ out.log("Run without --dry-run to apply these changes");
263
212
  }
264
213
  else {
265
214
  const result = await syncEngine.sync();
266
- output_1.out.taskLine("Writing to disk");
215
+ out.taskLine("Writing to disk");
267
216
  await safeRepoShutdown(repo);
268
217
  if (result.success) {
269
- output_1.out.done("Synced");
218
+ out.done("Synced");
270
219
  if (result.filesChanged === 0 && result.directoriesChanged === 0) {
271
220
  }
272
221
  else {
273
- output_1.out.successBlock("SYNCED", `${result.filesChanged} ${plural("file", result.filesChanged)}`);
222
+ out.successBlock("SYNCED", `${result.filesChanged} ${plural("file", result.filesChanged)}`);
274
223
  }
275
224
  if (result.warnings.length > 0) {
276
- output_1.out.log("");
277
- output_1.out.warnBlock("WARNINGS", `${result.warnings.length} warnings`);
225
+ out.log("");
226
+ out.warnBlock("WARNINGS", `${result.warnings.length} warnings`);
278
227
  for (const warning of result.warnings.slice(0, 5)) {
279
- output_1.out.log(` ${warning}`);
228
+ out.log(` ${warning}`);
280
229
  }
281
230
  if (result.warnings.length > 5) {
282
- output_1.out.log(` ... and ${result.warnings.length - 5} more`);
231
+ out.log(` ... and ${result.warnings.length - 5} more`);
283
232
  }
284
233
  }
285
234
  }
286
235
  else {
287
- output_1.out.done("partial", false);
288
- output_1.out.warnBlock("PARTIAL", `${result.filesChanged} updated, ${result.errors.length} errors`);
289
- output_1.out.obj({
236
+ out.done("partial", false);
237
+ out.warnBlock("PARTIAL", `${result.filesChanged} updated, ${result.errors.length} errors`);
238
+ out.obj({
290
239
  Files: result.filesChanged,
291
240
  Errors: result.errors.length,
292
241
  });
293
242
  result.errors
294
243
  .slice(0, 5)
295
- .forEach((error) => output_1.out.error(`${error.path}: ${error.error.message}`));
244
+ .forEach((error) => out.error(`${error.path}: ${error.error.message}`));
296
245
  if (result.errors.length > 5) {
297
- output_1.out.warn(`... and ${result.errors.length - 5} more errors`);
246
+ out.warn(`... and ${result.errors.length - 5} more errors`);
298
247
  }
299
248
  }
300
249
  }
@@ -303,24 +252,24 @@ async function sync(targetPath = ".", options) {
303
252
  /**
304
253
  * Show differences between local and remote
305
254
  */
306
- async function diff(targetPath = ".", options) {
307
- output_1.out.task("Analyzing changes");
255
+ export async function diff(targetPath = ".", options) {
256
+ out.task("Analyzing changes");
308
257
  const { repo, syncEngine } = await setupCommandContext(targetPath, { syncEnabled: false });
309
258
  const preview = await syncEngine.previewChanges();
310
- output_1.out.done();
259
+ out.done();
311
260
  if (options.nameOnly) {
312
261
  for (const change of preview.changes) {
313
- output_1.out.log(change.path);
262
+ out.log(change.path);
314
263
  }
315
264
  return;
316
265
  }
317
266
  if (preview.changes.length === 0) {
318
- output_1.out.success("No changes detected");
267
+ out.success("No changes detected");
319
268
  await safeRepoShutdown(repo);
320
- output_1.out.exit();
269
+ out.exit();
321
270
  return;
322
271
  }
323
- output_1.out.warn(`${preview.changes.length} changes detected`);
272
+ out.warn(`${preview.changes.length} changes detected`);
324
273
  for (const change of preview.changes) {
325
274
  const prefix = change.changeType === "local_only"
326
275
  ? "[local] "
@@ -344,7 +293,7 @@ async function diff(targetPath = ".", options) {
344
293
  // Skip the header lines and process the diff
345
294
  const lines = diffResult.split("\n").slice(4); // Skip index, ===, ---, +++ lines
346
295
  if (lines.length === 0 || (lines.length === 1 && lines[0] === "")) {
347
- output_1.out.log(`${prefix}${change.path} (content identical)`, "cyan");
296
+ out.log(`${prefix}${change.path} (content identical)`, "cyan");
348
297
  continue;
349
298
  }
350
299
  // Extract first hunk header and show inline with path
@@ -354,28 +303,28 @@ async function diff(targetPath = ".", options) {
354
303
  firstHunk = ` ${lines[0]}`;
355
304
  diffLines = lines.slice(1);
356
305
  }
357
- output_1.out.log(`${prefix}${change.path}${firstHunk}`, "cyan");
306
+ out.log(`${prefix}${change.path}${firstHunk}`, "cyan");
358
307
  for (const line of diffLines) {
359
308
  if (line.startsWith("@@")) {
360
309
  // Additional hunk headers
361
- output_1.out.log(line, "dim");
310
+ out.log(line, "dim");
362
311
  }
363
312
  else if (line.startsWith("+")) {
364
313
  // Added line
365
- output_1.out.log(line, "green");
314
+ out.log(line, "green");
366
315
  }
367
316
  else if (line.startsWith("-")) {
368
317
  // Removed line
369
- output_1.out.log(line, "red");
318
+ out.log(line, "red");
370
319
  }
371
320
  else if (line.startsWith(" ") || line === "") {
372
321
  // Context line or empty
373
- output_1.out.log(line, "dim");
322
+ out.log(line, "dim");
374
323
  }
375
324
  }
376
325
  }
377
326
  catch (error) {
378
- output_1.out.log(`${prefix}${change.path} (diff error: ${error})`, "cyan");
327
+ out.log(`${prefix}${change.path} (diff error: ${error})`, "cyan");
379
328
  }
380
329
  }
381
330
  await safeRepoShutdown(repo);
@@ -383,10 +332,10 @@ async function diff(targetPath = ".", options) {
383
332
  /**
384
333
  * Show sync status
385
334
  */
386
- async function status(targetPath = ".", options = {}) {
335
+ export async function status(targetPath = ".", options = {}) {
387
336
  const { repo, syncEngine, config } = await setupCommandContext(targetPath, { syncEnabled: false });
388
337
  const syncStatus = await syncEngine.getStatus();
389
- output_1.out.infoBlock("STATUS");
338
+ out.infoBlock("STATUS");
390
339
  const statusInfo = {};
391
340
  const fileCount = syncStatus.snapshot?.files.size || 0;
392
341
  statusInfo["URL"] = syncStatus.snapshot?.rootDirectoryUrl;
@@ -409,61 +358,61 @@ async function status(targetPath = ".", options = {}) {
409
358
  }
410
359
  }
411
360
  catch (error) {
412
- output_1.out.warn(`Warning: Could not load detailed info: ${error}`);
361
+ out.warn(`Warning: Could not load detailed info: ${error}`);
413
362
  }
414
363
  }
415
364
  statusInfo["Changes"] = syncStatus.hasChanges
416
365
  ? `${syncStatus.changeCount} pending`
417
366
  : undefined;
418
367
  statusInfo["Status"] = !syncStatus.hasChanges ? "up to date" : undefined;
419
- output_1.out.obj(statusInfo);
368
+ out.obj(statusInfo);
420
369
  // Show verbose details if requested
421
370
  if (options.verbose && syncStatus.snapshot?.rootDirectoryUrl) {
422
371
  const rootHandle = await repo.find(syncStatus.snapshot.rootDirectoryUrl);
423
372
  const rootDoc = await rootHandle.doc();
424
373
  if (rootDoc) {
425
- output_1.out.infoBlock("HEADS");
426
- output_1.out.arr(rootHandle.heads());
374
+ out.infoBlock("HEADS");
375
+ out.arr(rootHandle.heads());
427
376
  if (syncStatus.snapshot && syncStatus.snapshot.files.size > 0) {
428
- output_1.out.infoBlock("TRACKED FILES");
377
+ out.infoBlock("TRACKED FILES");
429
378
  const filesObj = {};
430
379
  syncStatus.snapshot.files.forEach((entry, filePath) => {
431
380
  filesObj[filePath] = entry.url;
432
381
  });
433
- output_1.out.obj(filesObj);
382
+ out.obj(filesObj);
434
383
  }
435
384
  }
436
385
  }
437
386
  if (syncStatus.hasChanges && !options.verbose) {
438
- output_1.out.info("Run 'pushwork diff' to see changes");
387
+ out.info("Run 'pushwork diff' to see changes");
439
388
  }
440
389
  await safeRepoShutdown(repo);
441
390
  }
442
391
  /**
443
392
  * Show sync history
444
393
  */
445
- async function log(targetPath = ".", _options) {
394
+ export async function log(targetPath = ".", _options) {
446
395
  const { repo: logRepo, workingDir } = await setupCommandContext(targetPath, { syncEnabled: false });
447
396
  // TODO: Implement history tracking
448
- const snapshotPath = path.join(workingDir, config_1.ConfigManager.CONFIG_DIR, "snapshot.json");
449
- if (await (0, utils_1.pathExists)(snapshotPath)) {
397
+ const snapshotPath = path.join(workingDir, ConfigManager.CONFIG_DIR, "snapshot.json");
398
+ if (await pathExists(snapshotPath)) {
450
399
  const stats = await fs.stat(snapshotPath);
451
- output_1.out.infoBlock("HISTORY", "Sync history (stub)");
452
- output_1.out.obj({ "Last sync": stats.mtime.toISOString() });
400
+ out.infoBlock("HISTORY", "Sync history (stub)");
401
+ out.obj({ "Last sync": stats.mtime.toISOString() });
453
402
  }
454
403
  else {
455
- output_1.out.info("No sync history found");
404
+ out.info("No sync history found");
456
405
  }
457
406
  await safeRepoShutdown(logRepo);
458
407
  }
459
408
  /**
460
409
  * Checkout/restore from previous sync
461
410
  */
462
- async function checkout(syncId, targetPath = ".", _options) {
411
+ export async function checkout(syncId, targetPath = ".", _options) {
463
412
  const { workingDir } = await setupCommandContext(targetPath);
464
413
  // TODO: Implement checkout functionality
465
- output_1.out.warnBlock("NOT IMPLEMENTED", "Checkout not yet implemented");
466
- output_1.out.obj({
414
+ out.warnBlock("NOT IMPLEMENTED", "Checkout not yet implemented");
415
+ out.obj({
467
416
  "Sync ID": syncId,
468
417
  Path: workingDir,
469
418
  });
@@ -471,150 +420,149 @@ async function checkout(syncId, targetPath = ".", _options) {
471
420
  /**
472
421
  * Clone an existing synced directory from an AutomergeUrl
473
422
  */
474
- async function clone(rootUrl, targetPath, options) {
423
+ export async function clone(rootUrl, targetPath, options) {
475
424
  // Validate that rootUrl is actually an Automerge URL
476
425
  if (!rootUrl.startsWith("automerge:")) {
477
- output_1.out.error(`Invalid Automerge URL: ${rootUrl}\n` +
426
+ out.error(`Invalid Automerge URL: ${rootUrl}\n` +
478
427
  `Expected format: automerge:XXXXX\n` +
479
428
  `Usage: pushwork clone <automerge-url> <path>`);
480
- output_1.out.exit(1);
429
+ out.exit(1);
481
430
  }
482
431
  const resolvedPath = path.resolve(targetPath);
483
- output_1.out.task(`Cloning ${rootUrl}`);
432
+ out.task(`Cloning ${rootUrl}`);
484
433
  // Check if directory exists and handle --force
485
- if (await (0, utils_1.pathExists)(resolvedPath)) {
434
+ if (await pathExists(resolvedPath)) {
486
435
  const files = await fs.readdir(resolvedPath);
487
436
  if (files.length > 0 && !options.force) {
488
- output_1.out.error("Target directory is not empty. Use --force to overwrite");
489
- output_1.out.exit(1);
437
+ out.error("Target directory is not empty. Use --force to overwrite");
438
+ out.exit(1);
490
439
  }
491
440
  }
492
441
  else {
493
- await (0, utils_1.ensureDirectoryExists)(resolvedPath);
442
+ await ensureDirectoryExists(resolvedPath);
494
443
  }
495
444
  // Check if already initialized
496
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
497
- if (await (0, utils_1.pathExists)(syncToolDir)) {
445
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
446
+ if (await pathExists(syncToolDir)) {
498
447
  if (!options.force) {
499
- output_1.out.error("Directory already initialized. Use --force to overwrite");
500
- output_1.out.exit(1);
448
+ out.error("Directory already initialized. Use --force to overwrite");
449
+ out.exit(1);
501
450
  }
502
451
  await fs.rm(syncToolDir, { recursive: true, force: true });
503
452
  }
504
453
  // Initialize repository with optional CLI overrides
505
- output_1.out.update("Setting up repository");
454
+ out.update("Setting up repository");
506
455
  const { config, repo, syncEngine } = await initializeRepository(resolvedPath, {
507
456
  sync_server: options.syncServer,
508
- sync_server_storage_id: options.syncServerStorageId,
509
457
  });
510
458
  // Connect to existing root directory and download files
511
- output_1.out.update("Downloading files");
459
+ out.update("Downloading files");
512
460
  await syncEngine.setRootDirectoryUrl(rootUrl);
513
461
  const result = await syncEngine.sync();
514
- output_1.out.update("Writing to disk");
462
+ out.update("Writing to disk");
515
463
  await safeRepoShutdown(repo);
516
- output_1.out.done();
517
- output_1.out.obj({
464
+ out.done();
465
+ out.obj({
518
466
  Path: resolvedPath,
519
467
  Files: `${result.filesChanged} downloaded`,
520
468
  Sync: config.sync_server,
521
469
  });
522
- output_1.out.successBlock("CLONED", rootUrl);
470
+ out.successBlock("CLONED", rootUrl);
523
471
  process.exit();
524
472
  }
525
473
  /**
526
474
  * Get the root URL for the current pushwork repository
527
475
  */
528
- async function url(targetPath = ".") {
476
+ export async function url(targetPath = ".") {
529
477
  const resolvedPath = path.resolve(targetPath);
530
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
531
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
532
- output_1.out.error("Directory not initialized for sync");
533
- output_1.out.exit(1);
478
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
479
+ if (!(await pathExists(syncToolDir))) {
480
+ out.error("Directory not initialized for sync");
481
+ out.exit(1);
534
482
  }
535
483
  const snapshotPath = path.join(syncToolDir, "snapshot.json");
536
- if (!(await (0, utils_1.pathExists)(snapshotPath))) {
537
- output_1.out.error("No snapshot found");
538
- output_1.out.exit(1);
484
+ if (!(await pathExists(snapshotPath))) {
485
+ out.error("No snapshot found");
486
+ out.exit(1);
539
487
  }
540
488
  const snapshotData = await fs.readFile(snapshotPath, "utf-8");
541
489
  const snapshot = JSON.parse(snapshotData);
542
490
  if (snapshot.rootDirectoryUrl) {
543
491
  // Output just the URL for easy use in scripts
544
- output_1.out.log(snapshot.rootDirectoryUrl);
492
+ out.log(snapshot.rootDirectoryUrl);
545
493
  }
546
494
  else {
547
- output_1.out.error("No root URL found in snapshot");
548
- output_1.out.exit(1);
495
+ out.error("No root URL found in snapshot");
496
+ out.exit(1);
549
497
  }
550
498
  }
551
499
  /**
552
500
  * Remove local pushwork data and log URL for recovery
553
501
  */
554
- async function rm(targetPath = ".") {
502
+ export async function rm(targetPath = ".") {
555
503
  const resolvedPath = path.resolve(targetPath);
556
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
557
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
558
- output_1.out.error("Directory not initialized for sync");
559
- output_1.out.exit(1);
504
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
505
+ if (!(await pathExists(syncToolDir))) {
506
+ out.error("Directory not initialized for sync");
507
+ out.exit(1);
560
508
  }
561
509
  // Read the URL before deletion for recovery
562
510
  let recoveryUrl = "";
563
511
  const snapshotPath = path.join(syncToolDir, "snapshot.json");
564
- if (await (0, utils_1.pathExists)(snapshotPath)) {
512
+ if (await pathExists(snapshotPath)) {
565
513
  try {
566
514
  const snapshotData = await fs.readFile(snapshotPath, "utf-8");
567
515
  const snapshot = JSON.parse(snapshotData);
568
516
  recoveryUrl = snapshot.rootDirectoryUrl || null;
569
517
  }
570
518
  catch (error) {
571
- output_1.out.error(`Remove failed: ${error}`);
572
- output_1.out.exit(1);
519
+ out.error(`Remove failed: ${error}`);
520
+ out.exit(1);
573
521
  return;
574
522
  }
575
523
  }
576
- output_1.out.task("Removing local pushwork data");
524
+ out.task("Removing local pushwork data");
577
525
  await fs.rm(syncToolDir, { recursive: true, force: true });
578
- output_1.out.done();
579
- output_1.out.warnBlock("REMOVED", recoveryUrl);
526
+ out.done();
527
+ out.warnBlock("REMOVED", recoveryUrl);
580
528
  process.exit();
581
529
  }
582
- async function commit(targetPath, _options = {}) {
583
- output_1.out.task("Committing local changes");
530
+ export async function commit(targetPath, _options = {}) {
531
+ out.task("Committing local changes");
584
532
  const { repo, syncEngine } = await setupCommandContext(targetPath, { syncEnabled: false });
585
533
  const result = await syncEngine.commitLocal();
586
534
  await safeRepoShutdown(repo);
587
- output_1.out.done();
535
+ out.done();
588
536
  if (result.errors.length > 0) {
589
- output_1.out.errorBlock("ERROR", `${result.errors.length} errors`);
590
- result.errors.forEach((error) => output_1.out.error(error));
591
- output_1.out.exit(1);
537
+ out.errorBlock("ERROR", `${result.errors.length} errors`);
538
+ result.errors.forEach((error) => out.error(error));
539
+ out.exit(1);
592
540
  }
593
- output_1.out.successBlock("COMMITTED", `${result.filesChanged} files`);
594
- output_1.out.obj({
541
+ out.successBlock("COMMITTED", `${result.filesChanged} files`);
542
+ out.obj({
595
543
  Files: result.filesChanged,
596
544
  Directories: result.directoriesChanged,
597
545
  });
598
546
  if (result.warnings.length > 0) {
599
- result.warnings.forEach((warning) => output_1.out.warn(warning));
547
+ result.warnings.forEach((warning) => out.warn(warning));
600
548
  }
601
549
  process.exit();
602
550
  }
603
551
  /**
604
552
  * List tracked files
605
553
  */
606
- async function ls(targetPath = ".", options = {}) {
554
+ export async function ls(targetPath = ".", options = {}) {
607
555
  const { repo, syncEngine } = await setupCommandContext(targetPath, { syncEnabled: false });
608
556
  const syncStatus = await syncEngine.getStatus();
609
557
  if (!syncStatus.snapshot) {
610
- output_1.out.error("No snapshot found");
558
+ out.error("No snapshot found");
611
559
  await safeRepoShutdown(repo);
612
- output_1.out.exit(1);
560
+ out.exit(1);
613
561
  return;
614
562
  }
615
563
  const files = Array.from(syncStatus.snapshot.files.entries()).sort(([pathA], [pathB]) => pathA.localeCompare(pathB));
616
564
  if (files.length === 0) {
617
- output_1.out.info("No tracked files");
565
+ out.info("No tracked files");
618
566
  await safeRepoShutdown(repo);
619
567
  return;
620
568
  }
@@ -622,13 +570,13 @@ async function ls(targetPath = ".", options = {}) {
622
570
  // Long format with URLs
623
571
  for (const [filePath, entry] of files) {
624
572
  const url = entry?.url || "unknown";
625
- output_1.out.log(`${filePath} -> ${url}`);
573
+ out.log(`${filePath} -> ${url}`);
626
574
  }
627
575
  }
628
576
  else {
629
577
  // Simple list
630
578
  for (const [filePath] of files) {
631
- output_1.out.log(filePath);
579
+ out.log(filePath);
632
580
  }
633
581
  }
634
582
  await safeRepoShutdown(repo);
@@ -636,19 +584,19 @@ async function ls(targetPath = ".", options = {}) {
636
584
  /**
637
585
  * View or edit configuration
638
586
  */
639
- async function config(targetPath = ".", options = {}) {
587
+ export async function config(targetPath = ".", options = {}) {
640
588
  const resolvedPath = path.resolve(targetPath);
641
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
642
- if (!(await (0, utils_1.pathExists)(syncToolDir))) {
643
- output_1.out.error("Directory not initialized for sync");
644
- output_1.out.exit(1);
589
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
590
+ if (!(await pathExists(syncToolDir))) {
591
+ out.error("Directory not initialized for sync");
592
+ out.exit(1);
645
593
  }
646
- const configManager = new config_1.ConfigManager(resolvedPath);
594
+ const configManager = new ConfigManager(resolvedPath);
647
595
  const config = await configManager.getMerged();
648
596
  if (options.list) {
649
597
  // List all configuration
650
- output_1.out.infoBlock("CONFIGURATION", "Full configuration");
651
- output_1.out.log(JSON.stringify(config, null, 2));
598
+ out.infoBlock("CONFIGURATION", "Full configuration");
599
+ out.log(JSON.stringify(config, null, 2));
652
600
  }
653
601
  else if (options.get) {
654
602
  // Get specific config value
@@ -658,44 +606,44 @@ async function config(targetPath = ".", options = {}) {
658
606
  value = value?.[key];
659
607
  }
660
608
  if (value !== undefined) {
661
- output_1.out.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
609
+ out.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
662
610
  }
663
611
  else {
664
- output_1.out.error(`Config key not found: ${options.get}`);
665
- output_1.out.exit(1);
612
+ out.error(`Config key not found: ${options.get}`);
613
+ out.exit(1);
666
614
  }
667
615
  }
668
616
  else {
669
617
  // Show basic config info
670
- output_1.out.infoBlock("CONFIGURATION");
671
- output_1.out.obj({
618
+ out.infoBlock("CONFIGURATION");
619
+ out.obj({
672
620
  "Sync server": config.sync_server || "default",
673
621
  "Sync enabled": config.sync_enabled ? "yes" : "no",
674
622
  Exclusions: config.exclude_patterns?.length,
675
623
  });
676
- output_1.out.log("");
677
- output_1.out.log("Use --list to see full configuration");
624
+ out.log("");
625
+ out.log("Use --list to see full configuration");
678
626
  }
679
627
  }
680
628
  /**
681
629
  * Watch a directory and sync after build script completes
682
630
  */
683
- async function watch(targetPath = ".", options = {}) {
631
+ export async function watch(targetPath = ".", options = {}) {
684
632
  const script = options.script || "pnpm build";
685
633
  const watchDir = options.watchDir || "src"; // Default to watching 'src' directory
686
634
  const verbose = options.verbose || false;
687
635
  const { repo, syncEngine, workingDir } = await setupCommandContext(targetPath);
688
636
  const absoluteWatchDir = path.resolve(workingDir, watchDir);
689
637
  // Check if watch directory exists
690
- if (!(await (0, utils_1.pathExists)(absoluteWatchDir))) {
691
- output_1.out.error(`Watch directory does not exist: ${watchDir}`);
638
+ if (!(await pathExists(absoluteWatchDir))) {
639
+ out.error(`Watch directory does not exist: ${watchDir}`);
692
640
  await safeRepoShutdown(repo);
693
- output_1.out.exit(1);
641
+ out.exit(1);
694
642
  return;
695
643
  }
696
- output_1.out.spicyBlock("WATCHING", `${chalk_1.default.underline((0, utils_1.formatRelativePath)(watchDir))} for changes...`);
697
- output_1.out.info(`Build script: ${script}`);
698
- output_1.out.info(`Working directory: ${workingDir}`);
644
+ out.spicyBlock("WATCHING", `${chalk.underline(formatRelativePath(watchDir))} for changes...`);
645
+ out.info(`Build script: ${script}`);
646
+ out.info(`Working directory: ${workingDir}`);
699
647
  let isProcessing = false;
700
648
  let pendingChange = false;
701
649
  // Function to run build and sync
@@ -707,14 +655,14 @@ async function watch(targetPath = ".", options = {}) {
707
655
  isProcessing = true;
708
656
  pendingChange = false;
709
657
  try {
710
- output_1.out.spicy(`[${new Date().toLocaleTimeString()}] Changes detected...`);
658
+ out.spicy(`[${new Date().toLocaleTimeString()}] Changes detected...`);
711
659
  // Run build script
712
660
  const buildResult = await runScript(script, workingDir, verbose);
713
661
  if (!buildResult.success) {
714
- output_1.out.warn("Build script failed");
662
+ out.warn("Build script failed");
715
663
  if (buildResult.output) {
716
- output_1.out.log("");
717
- output_1.out.log(buildResult.output);
664
+ out.log("");
665
+ out.log(buildResult.output);
718
666
  }
719
667
  isProcessing = false;
720
668
  if (pendingChange) {
@@ -722,38 +670,38 @@ async function watch(targetPath = ".", options = {}) {
722
670
  }
723
671
  return;
724
672
  }
725
- output_1.out.info("Build completed...");
673
+ out.info("Build completed...");
726
674
  // Run sync
727
- output_1.out.task("Syncing");
675
+ out.task("Syncing");
728
676
  const result = await syncEngine.sync();
729
677
  if (result.success) {
730
678
  if (result.filesChanged === 0 && result.directoriesChanged === 0) {
731
- output_1.out.done("Already synced");
679
+ out.done("Already synced");
732
680
  }
733
681
  else {
734
- output_1.out.done(`Synced ${result.filesChanged} ${plural("file", result.filesChanged)}`);
682
+ out.done(`Synced ${result.filesChanged} ${plural("file", result.filesChanged)}`);
735
683
  }
736
684
  }
737
685
  else {
738
- output_1.out.warn(`⚠ Partial sync: ${result.filesChanged} updated, ${result.errors.length} errors`);
686
+ out.warn(`⚠ Partial sync: ${result.filesChanged} updated, ${result.errors.length} errors`);
739
687
  result.errors
740
688
  .slice(0, 3)
741
- .forEach((error) => output_1.out.error(` ${error.path}: ${error.error.message}`));
689
+ .forEach((error) => out.error(` ${error.path}: ${error.error.message}`));
742
690
  if (result.errors.length > 3) {
743
- output_1.out.warn(` ... and ${result.errors.length - 3} more errors`);
691
+ out.warn(` ... and ${result.errors.length - 3} more errors`);
744
692
  }
745
693
  }
746
694
  if (result.warnings.length > 0) {
747
695
  result.warnings
748
696
  .slice(0, 3)
749
- .forEach((warning) => output_1.out.warn(` ${warning}`));
697
+ .forEach((warning) => out.warn(` ${warning}`));
750
698
  if (result.warnings.length > 3) {
751
- output_1.out.warn(` ... and ${result.warnings.length - 3} more warnings`);
699
+ out.warn(` ... and ${result.warnings.length - 3} more warnings`);
752
700
  }
753
701
  }
754
702
  }
755
703
  catch (error) {
756
- output_1.out.error(`Error during build/sync: ${error}`);
704
+ out.error(`Error during build/sync: ${error}`);
757
705
  }
758
706
  finally {
759
707
  isProcessing = false;
@@ -771,11 +719,11 @@ async function watch(targetPath = ".", options = {}) {
771
719
  });
772
720
  // Handle graceful shutdown
773
721
  const shutdown = async () => {
774
- output_1.out.log("");
775
- output_1.out.info("Shutting down...");
722
+ out.log("");
723
+ out.info("Shutting down...");
776
724
  watcher.close();
777
725
  await safeRepoShutdown(repo);
778
- output_1.out.rainbow("Goodbye!");
726
+ out.rainbow("Goodbye!");
779
727
  process.exit(0);
780
728
  };
781
729
  process.on("SIGINT", shutdown);
@@ -791,7 +739,7 @@ async function watch(targetPath = ".", options = {}) {
791
739
  async function runScript(script, cwd, verbose) {
792
740
  return new Promise((resolve) => {
793
741
  const [command, ...args] = script.split(" ");
794
- const child = (0, child_process_1.spawn)(command, args, {
742
+ const child = spawn(command, args, {
795
743
  cwd,
796
744
  stdio: verbose ? "inherit" : "pipe", // Show output directly if verbose, otherwise capture
797
745
  shell: true,
@@ -813,7 +761,7 @@ async function runScript(script, cwd, verbose) {
813
761
  });
814
762
  });
815
763
  child.on("error", (error) => {
816
- output_1.out.error(`Failed to run script: ${error.message}`);
764
+ out.error(`Failed to run script: ${error.message}`);
817
765
  resolve({
818
766
  success: false,
819
767
  output: !verbose ? output : undefined,
@@ -824,22 +772,22 @@ async function runScript(script, cwd, verbose) {
824
772
  /**
825
773
  * Set root directory URL for an existing or new pushwork directory
826
774
  */
827
- async function root(rootUrl, targetPath = ".", options = {}) {
775
+ export async function root(rootUrl, targetPath = ".", options = {}) {
828
776
  if (!rootUrl.startsWith("automerge:")) {
829
- output_1.out.error(`Invalid Automerge URL: ${rootUrl}\n` +
777
+ out.error(`Invalid Automerge URL: ${rootUrl}\n` +
830
778
  `Expected format: automerge:XXXXX`);
831
- output_1.out.exit(1);
779
+ out.exit(1);
832
780
  }
833
781
  const resolvedPath = path.resolve(targetPath);
834
- const syncToolDir = path.join(resolvedPath, config_1.ConfigManager.CONFIG_DIR);
835
- if (await (0, utils_1.pathExists)(syncToolDir)) {
782
+ const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
783
+ if (await pathExists(syncToolDir)) {
836
784
  if (!options.force) {
837
- output_1.out.error("Directory already initialized for pushwork. Use --force to overwrite");
838
- output_1.out.exit(1);
785
+ out.error("Directory already initialized for pushwork. Use --force to overwrite");
786
+ out.exit(1);
839
787
  }
840
788
  }
841
- await (0, utils_1.ensureDirectoryExists)(syncToolDir);
842
- await (0, utils_1.ensureDirectoryExists)(path.join(syncToolDir, "automerge"));
789
+ await ensureDirectoryExists(syncToolDir);
790
+ await ensureDirectoryExists(path.join(syncToolDir, "automerge"));
843
791
  // Create minimal snapshot with just the root URL
844
792
  const snapshotPath = path.join(syncToolDir, "snapshot.json");
845
793
  const snapshot = {
@@ -851,9 +799,60 @@ async function root(rootUrl, targetPath = ".", options = {}) {
851
799
  };
852
800
  await fs.writeFile(snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
853
801
  // Ensure config exists
854
- const configManager = new config_1.ConfigManager(resolvedPath);
802
+ const configManager = new ConfigManager(resolvedPath);
855
803
  await configManager.initializeWithOverrides({});
856
- output_1.out.successBlock("ROOT SET", rootUrl);
804
+ out.successBlock("ROOT SET", rootUrl);
805
+ process.exit();
806
+ }
807
+ /**
808
+ * Read a file document by its Automerge URL and print its content
809
+ */
810
+ export async function read(docUrl, options = {}) {
811
+ if (!docUrl.startsWith("automerge:")) {
812
+ out.error(`Invalid Automerge URL: ${docUrl}\n` +
813
+ `Expected format: automerge:XXXXX`);
814
+ out.exit(1);
815
+ }
816
+ let repo;
817
+ if (options.remote) {
818
+ // Create an ephemeral repo with Subduction to fetch from sync server
819
+ repo = await createEphemeralRepo(DEFAULT_SYNC_SERVER);
820
+ }
821
+ else {
822
+ // Read from local pushwork storage
823
+ const ctx = await setupCommandContext(".", { syncEnabled: false });
824
+ repo = ctx.repo;
825
+ }
826
+ try {
827
+ const handle = await repo.find(docUrl);
828
+ const doc = await handle.doc();
829
+ if (!doc) {
830
+ out.error("Document not found or unavailable");
831
+ await safeRepoShutdown(repo);
832
+ out.exit(1);
833
+ return;
834
+ }
835
+ const content = readDocContent(doc.content);
836
+ if (content === null) {
837
+ out.error("Document has no content");
838
+ await safeRepoShutdown(repo);
839
+ out.exit(1);
840
+ return;
841
+ }
842
+ if (content instanceof Uint8Array) {
843
+ process.stdout.write(content);
844
+ }
845
+ else {
846
+ process.stdout.write(content);
847
+ }
848
+ }
849
+ catch (error) {
850
+ out.error(`Failed to read document: ${error}`);
851
+ await safeRepoShutdown(repo);
852
+ out.exit(1);
853
+ return;
854
+ }
855
+ await safeRepoShutdown(repo);
857
856
  process.exit();
858
857
  }
859
858
  function plural(word, count) {