ctx-sync 1.0.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 (145) hide show
  1. package/dist/commands/audit.d.ts +76 -0
  2. package/dist/commands/audit.d.ts.map +1 -0
  3. package/dist/commands/audit.js +367 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/config.d.ts +58 -0
  6. package/dist/commands/config.d.ts.map +1 -0
  7. package/dist/commands/config.js +114 -0
  8. package/dist/commands/config.js.map +1 -0
  9. package/dist/commands/dir.d.ts +56 -0
  10. package/dist/commands/dir.d.ts.map +1 -0
  11. package/dist/commands/dir.js +172 -0
  12. package/dist/commands/dir.js.map +1 -0
  13. package/dist/commands/docker.d.ts +140 -0
  14. package/dist/commands/docker.d.ts.map +1 -0
  15. package/dist/commands/docker.js +380 -0
  16. package/dist/commands/docker.js.map +1 -0
  17. package/dist/commands/env.d.ts +96 -0
  18. package/dist/commands/env.d.ts.map +1 -0
  19. package/dist/commands/env.js +352 -0
  20. package/dist/commands/env.js.map +1 -0
  21. package/dist/commands/init.d.ts +89 -0
  22. package/dist/commands/init.d.ts.map +1 -0
  23. package/dist/commands/init.js +272 -0
  24. package/dist/commands/init.js.map +1 -0
  25. package/dist/commands/key.d.ts +92 -0
  26. package/dist/commands/key.d.ts.map +1 -0
  27. package/dist/commands/key.js +274 -0
  28. package/dist/commands/key.js.map +1 -0
  29. package/dist/commands/list.d.ts +38 -0
  30. package/dist/commands/list.d.ts.map +1 -0
  31. package/dist/commands/list.js +84 -0
  32. package/dist/commands/list.js.map +1 -0
  33. package/dist/commands/note.d.ts +151 -0
  34. package/dist/commands/note.d.ts.map +1 -0
  35. package/dist/commands/note.js +411 -0
  36. package/dist/commands/note.js.map +1 -0
  37. package/dist/commands/pull.d.ts +47 -0
  38. package/dist/commands/pull.d.ts.map +1 -0
  39. package/dist/commands/pull.js +94 -0
  40. package/dist/commands/pull.js.map +1 -0
  41. package/dist/commands/push.d.ts +40 -0
  42. package/dist/commands/push.d.ts.map +1 -0
  43. package/dist/commands/push.js +94 -0
  44. package/dist/commands/push.js.map +1 -0
  45. package/dist/commands/restore.d.ts +116 -0
  46. package/dist/commands/restore.d.ts.map +1 -0
  47. package/dist/commands/restore.js +336 -0
  48. package/dist/commands/restore.js.map +1 -0
  49. package/dist/commands/service.d.ts +83 -0
  50. package/dist/commands/service.d.ts.map +1 -0
  51. package/dist/commands/service.js +259 -0
  52. package/dist/commands/service.js.map +1 -0
  53. package/dist/commands/show.d.ts +63 -0
  54. package/dist/commands/show.d.ts.map +1 -0
  55. package/dist/commands/show.js +243 -0
  56. package/dist/commands/show.js.map +1 -0
  57. package/dist/commands/status.d.ts +53 -0
  58. package/dist/commands/status.d.ts.map +1 -0
  59. package/dist/commands/status.js +150 -0
  60. package/dist/commands/status.js.map +1 -0
  61. package/dist/commands/sync.d.ts +105 -0
  62. package/dist/commands/sync.d.ts.map +1 -0
  63. package/dist/commands/sync.js +243 -0
  64. package/dist/commands/sync.js.map +1 -0
  65. package/dist/commands/team.d.ts +79 -0
  66. package/dist/commands/team.d.ts.map +1 -0
  67. package/dist/commands/team.js +233 -0
  68. package/dist/commands/team.js.map +1 -0
  69. package/dist/commands/track.d.ts +109 -0
  70. package/dist/commands/track.d.ts.map +1 -0
  71. package/dist/commands/track.js +406 -0
  72. package/dist/commands/track.js.map +1 -0
  73. package/dist/core/command-validator.d.ts +100 -0
  74. package/dist/core/command-validator.d.ts.map +1 -0
  75. package/dist/core/command-validator.js +299 -0
  76. package/dist/core/command-validator.js.map +1 -0
  77. package/dist/core/config-store.d.ts +76 -0
  78. package/dist/core/config-store.d.ts.map +1 -0
  79. package/dist/core/config-store.js +148 -0
  80. package/dist/core/config-store.js.map +1 -0
  81. package/dist/core/directories-handler.d.ts +116 -0
  82. package/dist/core/directories-handler.d.ts.map +1 -0
  83. package/dist/core/directories-handler.js +199 -0
  84. package/dist/core/directories-handler.js.map +1 -0
  85. package/dist/core/docker-handler.d.ts +183 -0
  86. package/dist/core/docker-handler.d.ts.map +1 -0
  87. package/dist/core/docker-handler.js +515 -0
  88. package/dist/core/docker-handler.js.map +1 -0
  89. package/dist/core/encryption.d.ts +79 -0
  90. package/dist/core/encryption.d.ts.map +1 -0
  91. package/dist/core/encryption.js +111 -0
  92. package/dist/core/encryption.js.map +1 -0
  93. package/dist/core/env-handler.d.ts +128 -0
  94. package/dist/core/env-handler.d.ts.map +1 -0
  95. package/dist/core/env-handler.js +272 -0
  96. package/dist/core/env-handler.js.map +1 -0
  97. package/dist/core/git-sync.d.ts +88 -0
  98. package/dist/core/git-sync.d.ts.map +1 -0
  99. package/dist/core/git-sync.js +143 -0
  100. package/dist/core/git-sync.js.map +1 -0
  101. package/dist/core/key-store.d.ts +51 -0
  102. package/dist/core/key-store.d.ts.map +1 -0
  103. package/dist/core/key-store.js +108 -0
  104. package/dist/core/key-store.js.map +1 -0
  105. package/dist/core/log-sanitizer.d.ts +72 -0
  106. package/dist/core/log-sanitizer.d.ts.map +1 -0
  107. package/dist/core/log-sanitizer.js +202 -0
  108. package/dist/core/log-sanitizer.js.map +1 -0
  109. package/dist/core/path-validator.d.ts +37 -0
  110. package/dist/core/path-validator.d.ts.map +1 -0
  111. package/dist/core/path-validator.js +127 -0
  112. package/dist/core/path-validator.js.map +1 -0
  113. package/dist/core/recipients.d.ts +99 -0
  114. package/dist/core/recipients.d.ts.map +1 -0
  115. package/dist/core/recipients.js +206 -0
  116. package/dist/core/recipients.js.map +1 -0
  117. package/dist/core/services-handler.d.ts +113 -0
  118. package/dist/core/services-handler.d.ts.map +1 -0
  119. package/dist/core/services-handler.js +176 -0
  120. package/dist/core/services-handler.js.map +1 -0
  121. package/dist/core/state-manager.d.ts +96 -0
  122. package/dist/core/state-manager.d.ts.map +1 -0
  123. package/dist/core/state-manager.js +165 -0
  124. package/dist/core/state-manager.js.map +1 -0
  125. package/dist/core/transport.d.ts +28 -0
  126. package/dist/core/transport.d.ts.map +1 -0
  127. package/dist/core/transport.js +79 -0
  128. package/dist/core/transport.js.map +1 -0
  129. package/dist/index.d.ts +20 -0
  130. package/dist/index.d.ts.map +1 -0
  131. package/dist/index.js +80 -0
  132. package/dist/index.js.map +1 -0
  133. package/dist/types/index.d.ts +5 -0
  134. package/dist/types/index.d.ts.map +1 -0
  135. package/dist/types/index.js +2 -0
  136. package/dist/types/index.js.map +1 -0
  137. package/dist/utils/errors.d.ts +81 -0
  138. package/dist/utils/errors.d.ts.map +1 -0
  139. package/dist/utils/errors.js +191 -0
  140. package/dist/utils/errors.js.map +1 -0
  141. package/dist/utils/secure-memory.d.ts +65 -0
  142. package/dist/utils/secure-memory.d.ts.map +1 -0
  143. package/dist/utils/secure-memory.js +86 -0
  144. package/dist/utils/secure-memory.js.map +1 -0
  145. package/package.json +58 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * `ctx-sync status` command.
3
+ *
4
+ * Shows sync status (last sync time, pending changes, remote connectivity)
5
+ * and per-project status (branch, uncommitted changes).
6
+ *
7
+ * @module commands/status
8
+ */
9
+ import { withErrorHandler } from '../utils/errors.js';
10
+ import { loadKey } from '../core/key-store.js';
11
+ import { readState, readManifest } from '../core/state-manager.js';
12
+ import { getStatus } from '../core/git-sync.js';
13
+ import { getConfigDir, getSyncDir } from './init.js';
14
+ /**
15
+ * Execute the status command logic.
16
+ *
17
+ * 1. Read manifest for last sync time.
18
+ * 2. Get sync repo status (pending changes, remote).
19
+ * 3. Read state.age for per-project info.
20
+ *
21
+ * @returns Status result with sync and project info.
22
+ */
23
+ export async function executeStatus() {
24
+ const configDir = getConfigDir();
25
+ const syncDir = getSyncDir();
26
+ // Read manifest for last sync time
27
+ let manifest = null;
28
+ try {
29
+ manifest = readManifest(syncDir);
30
+ }
31
+ catch {
32
+ // Manifest might not exist yet
33
+ }
34
+ // Get sync repo status
35
+ let syncStatus = {
36
+ files: [],
37
+ ahead: 0,
38
+ behind: 0,
39
+ isClean: true,
40
+ };
41
+ let hasRemote = false;
42
+ try {
43
+ syncStatus = await getStatus(syncDir);
44
+ // Check if remote exists by looking at the git status
45
+ // getStatus returns ahead/behind which requires a remote
46
+ hasRemote = syncStatus.ahead > 0 || syncStatus.behind > 0 || !syncStatus.isClean;
47
+ }
48
+ catch {
49
+ // Sync repo might not be initialized yet
50
+ }
51
+ // Try to determine if remote exists by checking git remotes directly
52
+ try {
53
+ const { simpleGit } = await import('simple-git');
54
+ const git = simpleGit(syncDir);
55
+ const remotes = await git.getRemotes();
56
+ hasRemote = remotes.length > 0;
57
+ }
58
+ catch {
59
+ // Can't check remotes — that's fine
60
+ }
61
+ const sync = {
62
+ lastSync: manifest?.lastSync ?? null,
63
+ pendingChanges: syncStatus.files.length,
64
+ hasRemote,
65
+ isClean: syncStatus.isClean,
66
+ ahead: syncStatus.ahead,
67
+ behind: syncStatus.behind,
68
+ };
69
+ // Read per-project status
70
+ const projects = [];
71
+ try {
72
+ const privateKey = loadKey(configDir);
73
+ const state = await readState(syncDir, privateKey, 'state');
74
+ if (state?.projects) {
75
+ for (const project of state.projects) {
76
+ projects.push({
77
+ name: project.name,
78
+ path: project.path,
79
+ branch: project.git.branch,
80
+ hasUncommitted: project.git.hasUncommitted,
81
+ stashCount: project.git.stashCount,
82
+ lastAccessed: project.lastAccessed,
83
+ });
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ // State file might not exist yet
89
+ }
90
+ return { sync, projects };
91
+ }
92
+ /**
93
+ * Register the `status` command on the given Commander program.
94
+ */
95
+ export function registerStatusCommand(program) {
96
+ program
97
+ .command('status')
98
+ .description('Show sync status and project overview')
99
+ .action(withErrorHandler(async () => {
100
+ const result = await executeStatus();
101
+ const chalk = (await import('chalk')).default;
102
+ // Sync status
103
+ console.log(chalk.bold('Sync Status:\n'));
104
+ if (result.sync.lastSync) {
105
+ const syncDate = new Date(result.sync.lastSync);
106
+ console.log(` Last sync: ${syncDate.toLocaleDateString()} ${syncDate.toLocaleTimeString()}`);
107
+ }
108
+ else {
109
+ console.log(' Last sync: never');
110
+ }
111
+ if (result.sync.isClean) {
112
+ console.log(chalk.green(' State: up to date'));
113
+ }
114
+ else {
115
+ console.log(chalk.yellow(` State: ${result.sync.pendingChanges} pending change(s)`));
116
+ }
117
+ if (result.sync.hasRemote) {
118
+ console.log(chalk.green(' Remote: connected'));
119
+ if (result.sync.ahead > 0) {
120
+ console.log(chalk.yellow(` Ahead: ${result.sync.ahead} commit(s)`));
121
+ }
122
+ if (result.sync.behind > 0) {
123
+ console.log(chalk.yellow(` Behind: ${result.sync.behind} commit(s)`));
124
+ }
125
+ }
126
+ else {
127
+ console.log(chalk.dim(' Remote: not configured'));
128
+ }
129
+ // Project status
130
+ if (result.projects.length === 0) {
131
+ console.log(chalk.dim('\nNo projects tracked.'));
132
+ return;
133
+ }
134
+ console.log(chalk.bold(`\nProjects (${result.projects.length}):\n`));
135
+ for (const project of result.projects) {
136
+ const statusIcon = project.hasUncommitted
137
+ ? chalk.yellow('●')
138
+ : chalk.green('●');
139
+ console.log(` ${statusIcon} ${project.name}`);
140
+ console.log(` Branch: ${project.branch}`);
141
+ if (project.hasUncommitted) {
142
+ console.log(chalk.yellow(' Uncommitted changes'));
143
+ }
144
+ if (project.stashCount > 0) {
145
+ console.log(chalk.yellow(` ${project.stashCount} stash(es)`));
146
+ }
147
+ }
148
+ }));
149
+ }
150
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `ctx-sync sync` command.
3
+ *
4
+ * Performs a full bidirectional sync:
5
+ * 1. Validate remote URL (transport security).
6
+ * 2. Pull latest from remote (if remote exists).
7
+ * 3. Detect and handle merge conflicts on encrypted (.age) files.
8
+ * 4. Commit all .age files + manifest.json.
9
+ * 5. Push to remote.
10
+ *
11
+ * Merge conflicts on .age files are NEVER auto-merged — the user must
12
+ * choose which version to keep (local or remote) because encrypted blobs
13
+ * cannot be meaningfully merged.
14
+ *
15
+ * @module commands/sync
16
+ */
17
+ import type { Command } from 'commander';
18
+ /** Options for the sync command */
19
+ export interface SyncOptions {
20
+ /** Skip pulling from remote */
21
+ noPull?: boolean;
22
+ /** Skip pushing to remote */
23
+ noPush?: boolean;
24
+ /** Non-interactive mode — use local version on conflict */
25
+ noInteractive?: boolean;
26
+ }
27
+ /** Result of a sync operation */
28
+ export interface SyncResult {
29
+ /** Whether a pull was performed */
30
+ pulled: boolean;
31
+ /** Whether a commit was created */
32
+ committed: boolean;
33
+ /** Whether a push was performed */
34
+ pushed: boolean;
35
+ /** Commit hash, if a commit was created */
36
+ commitHash: string | null;
37
+ /** Number of files synced */
38
+ fileCount: number;
39
+ /** Whether there were merge conflicts */
40
+ hadConflicts: boolean;
41
+ /** Files that had merge conflicts */
42
+ conflictFiles: string[];
43
+ /** Whether the repo has a remote configured */
44
+ hasRemote: boolean;
45
+ }
46
+ /**
47
+ * Check whether the sync repo has a remote configured and validate it.
48
+ *
49
+ * @param syncDir - The sync directory path.
50
+ * @returns The remote URL, or `null` if no remote is configured.
51
+ * @throws If the remote URL uses an insecure protocol.
52
+ */
53
+ export declare function validateSyncRemote(syncDir: string): Promise<string | null>;
54
+ /**
55
+ * Pull latest from the remote, detecting merge conflicts.
56
+ *
57
+ * Attempts `git pull`. If a merge conflict is detected on `.age` files,
58
+ * returns the list of conflicting files. Conflicts on .age files are
59
+ * NEVER auto-merged — the user must resolve them.
60
+ *
61
+ * @param syncDir - The sync directory path.
62
+ * @returns List of files with merge conflicts (empty if no conflicts).
63
+ */
64
+ export declare function pullWithConflictDetection(syncDir: string): Promise<{
65
+ pulled: boolean;
66
+ conflictFiles: string[];
67
+ }>;
68
+ /**
69
+ * Resolve merge conflicts on .age files by choosing local or remote version.
70
+ *
71
+ * Encrypted files cannot be meaningfully merged, so the user must choose
72
+ * one version. In non-interactive mode, local version is kept (safest default).
73
+ *
74
+ * @param syncDir - The sync directory path.
75
+ * @param conflictFiles - Files with conflicts.
76
+ * @param useLocal - If true, keep local version; if false, use remote version.
77
+ */
78
+ export declare function resolveConflicts(syncDir: string, conflictFiles: string[], useLocal?: boolean): Promise<void>;
79
+ /**
80
+ * Collect all syncable files (all .age files + manifest.json) in the sync dir.
81
+ *
82
+ * @param syncDir - The sync directory path.
83
+ * @returns List of file paths relative to the sync dir.
84
+ */
85
+ export declare function collectSyncFiles(syncDir: string): string[];
86
+ /**
87
+ * Execute the sync command logic.
88
+ *
89
+ * Full bidirectional sync:
90
+ * 1. Validate remote URL.
91
+ * 2. Pull latest (with conflict detection).
92
+ * 3. Handle any conflicts.
93
+ * 4. Update manifest timestamp.
94
+ * 5. Commit all .age + manifest.json.
95
+ * 6. Push to remote.
96
+ *
97
+ * @param options - Sync command options.
98
+ * @returns Sync result with operation details.
99
+ */
100
+ export declare function executeSync(options?: SyncOptions): Promise<SyncResult>;
101
+ /**
102
+ * Register the `sync` command on the given Commander program.
103
+ */
104
+ export declare function registerSyncCommand(program: Command): void;
105
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,qCAAqC;IACrC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYhF;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA8BvD;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,GAAE,OAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoEhF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2D1D"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * `ctx-sync sync` command.
3
+ *
4
+ * Performs a full bidirectional sync:
5
+ * 1. Validate remote URL (transport security).
6
+ * 2. Pull latest from remote (if remote exists).
7
+ * 3. Detect and handle merge conflicts on encrypted (.age) files.
8
+ * 4. Commit all .age files + manifest.json.
9
+ * 5. Push to remote.
10
+ *
11
+ * Merge conflicts on .age files are NEVER auto-merged — the user must
12
+ * choose which version to keep (local or remote) because encrypted blobs
13
+ * cannot be meaningfully merged.
14
+ *
15
+ * @module commands/sync
16
+ */
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { STATE_FILES } from '@ctx-sync/shared';
20
+ import { simpleGit } from 'simple-git';
21
+ import { commitState, pushState } from '../core/git-sync.js';
22
+ import { validateRemoteUrl } from '../core/transport.js';
23
+ import { readManifest, writeManifest, listStateFiles } from '../core/state-manager.js';
24
+ import { getSyncDir } from './init.js';
25
+ import { withErrorHandler } from '../utils/errors.js';
26
+ /**
27
+ * Check whether the sync repo has a remote configured and validate it.
28
+ *
29
+ * @param syncDir - The sync directory path.
30
+ * @returns The remote URL, or `null` if no remote is configured.
31
+ * @throws If the remote URL uses an insecure protocol.
32
+ */
33
+ export async function validateSyncRemote(syncDir) {
34
+ const git = simpleGit(syncDir);
35
+ const remotes = await git.getRemotes(true);
36
+ const origin = remotes.find((r) => r.name === 'origin');
37
+ if (!origin) {
38
+ return null;
39
+ }
40
+ const url = origin.refs.push || origin.refs.fetch;
41
+ validateRemoteUrl(url);
42
+ return url;
43
+ }
44
+ /**
45
+ * Pull latest from the remote, detecting merge conflicts.
46
+ *
47
+ * Attempts `git pull`. If a merge conflict is detected on `.age` files,
48
+ * returns the list of conflicting files. Conflicts on .age files are
49
+ * NEVER auto-merged — the user must resolve them.
50
+ *
51
+ * @param syncDir - The sync directory path.
52
+ * @returns List of files with merge conflicts (empty if no conflicts).
53
+ */
54
+ export async function pullWithConflictDetection(syncDir) {
55
+ const git = simpleGit(syncDir);
56
+ // Verify remote exists
57
+ const remotes = await git.getRemotes(true);
58
+ const origin = remotes.find((r) => r.name === 'origin');
59
+ if (!origin) {
60
+ return { pulled: false, conflictFiles: [] };
61
+ }
62
+ const url = origin.refs.fetch || origin.refs.push;
63
+ validateRemoteUrl(url);
64
+ try {
65
+ await git.pull('origin', 'main');
66
+ return { pulled: true, conflictFiles: [] };
67
+ }
68
+ catch (err) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ // Detect merge conflicts
71
+ if (message.includes('CONFLICT') || message.includes('Merge conflict')) {
72
+ const status = await git.status();
73
+ const conflicting = status.conflicted || [];
74
+ return { pulled: true, conflictFiles: conflicting };
75
+ }
76
+ // Re-throw non-conflict errors
77
+ throw err;
78
+ }
79
+ }
80
+ /**
81
+ * Resolve merge conflicts on .age files by choosing local or remote version.
82
+ *
83
+ * Encrypted files cannot be meaningfully merged, so the user must choose
84
+ * one version. In non-interactive mode, local version is kept (safest default).
85
+ *
86
+ * @param syncDir - The sync directory path.
87
+ * @param conflictFiles - Files with conflicts.
88
+ * @param useLocal - If true, keep local version; if false, use remote version.
89
+ */
90
+ export async function resolveConflicts(syncDir, conflictFiles, useLocal = true) {
91
+ const git = simpleGit(syncDir);
92
+ for (const file of conflictFiles) {
93
+ if (useLocal) {
94
+ // Keep our version (--ours)
95
+ await git.checkout(['--ours', file]);
96
+ }
97
+ else {
98
+ // Use their version (--theirs)
99
+ await git.checkout(['--theirs', file]);
100
+ }
101
+ await git.add(file);
102
+ }
103
+ }
104
+ /**
105
+ * Collect all syncable files (all .age files + manifest.json) in the sync dir.
106
+ *
107
+ * @param syncDir - The sync directory path.
108
+ * @returns List of file paths relative to the sync dir.
109
+ */
110
+ export function collectSyncFiles(syncDir) {
111
+ const files = [];
112
+ // Add all .age files
113
+ const ageFiles = listStateFiles(syncDir);
114
+ files.push(...ageFiles);
115
+ // Add manifest.json if it exists
116
+ const manifestPath = path.join(syncDir, STATE_FILES.MANIFEST);
117
+ if (fs.existsSync(manifestPath)) {
118
+ files.push(STATE_FILES.MANIFEST);
119
+ }
120
+ return files;
121
+ }
122
+ /**
123
+ * Execute the sync command logic.
124
+ *
125
+ * Full bidirectional sync:
126
+ * 1. Validate remote URL.
127
+ * 2. Pull latest (with conflict detection).
128
+ * 3. Handle any conflicts.
129
+ * 4. Update manifest timestamp.
130
+ * 5. Commit all .age + manifest.json.
131
+ * 6. Push to remote.
132
+ *
133
+ * @param options - Sync command options.
134
+ * @returns Sync result with operation details.
135
+ */
136
+ export async function executeSync(options = {}) {
137
+ const syncDir = getSyncDir();
138
+ // Verify sync dir exists
139
+ if (!fs.existsSync(syncDir) || !fs.existsSync(path.join(syncDir, '.git'))) {
140
+ throw new Error('No sync repository found. Run `ctx-sync init` first.');
141
+ }
142
+ const result = {
143
+ pulled: false,
144
+ committed: false,
145
+ pushed: false,
146
+ commitHash: null,
147
+ fileCount: 0,
148
+ hadConflicts: false,
149
+ conflictFiles: [],
150
+ hasRemote: false,
151
+ };
152
+ // 1. Validate remote (if exists)
153
+ const remoteUrl = await validateSyncRemote(syncDir);
154
+ result.hasRemote = remoteUrl !== null;
155
+ // 2. Pull latest (if remote exists and not skipped)
156
+ if (result.hasRemote && !options.noPull) {
157
+ const pullResult = await pullWithConflictDetection(syncDir);
158
+ result.pulled = pullResult.pulled;
159
+ // 3. Handle merge conflicts
160
+ if (pullResult.conflictFiles.length > 0) {
161
+ result.hadConflicts = true;
162
+ result.conflictFiles = pullResult.conflictFiles;
163
+ // In non-interactive mode, keep local version (safest default)
164
+ // In interactive mode, this would prompt the user
165
+ const useLocal = options.noInteractive !== false;
166
+ await resolveConflicts(syncDir, pullResult.conflictFiles, useLocal);
167
+ }
168
+ }
169
+ // 4. Update manifest timestamp
170
+ const manifest = readManifest(syncDir) ?? {
171
+ version: '1.0.0',
172
+ lastSync: new Date().toISOString(),
173
+ files: {},
174
+ };
175
+ manifest.lastSync = new Date().toISOString();
176
+ writeManifest(syncDir, manifest);
177
+ // 5. Collect and commit all sync files
178
+ const files = collectSyncFiles(syncDir);
179
+ result.fileCount = files.length;
180
+ if (files.length > 0) {
181
+ const hash = await commitState(syncDir, files, 'sync: update encrypted state');
182
+ result.committed = hash !== null;
183
+ result.commitHash = hash;
184
+ }
185
+ // 6. Push to remote (if exists and not skipped)
186
+ if (result.hasRemote && !options.noPush) {
187
+ await pushState(syncDir);
188
+ result.pushed = true;
189
+ }
190
+ return result;
191
+ }
192
+ /**
193
+ * Register the `sync` command on the given Commander program.
194
+ */
195
+ export function registerSyncCommand(program) {
196
+ program
197
+ .command('sync')
198
+ .description('Sync encrypted state with remote (pull + commit + push)')
199
+ .option('--no-pull', 'Skip pulling from remote')
200
+ .option('--no-push', 'Skip pushing to remote')
201
+ .option('--no-interactive', 'Non-interactive mode (keep local on conflict)')
202
+ .action(withErrorHandler(async (opts) => {
203
+ const options = {
204
+ noPull: opts['pull'] === false,
205
+ noPush: opts['push'] === false,
206
+ noInteractive: opts['interactive'] === false,
207
+ };
208
+ const chalk = (await import('chalk')).default;
209
+ const { default: ora } = await import('ora');
210
+ const spinner = ora('Syncing...').start();
211
+ // Pull phase
212
+ if (!options.noPull) {
213
+ spinner.text = 'Pulling latest from remote...';
214
+ }
215
+ const result = await executeSync(options);
216
+ spinner.stop();
217
+ // Report results
218
+ if (result.hadConflicts) {
219
+ console.log(chalk.yellow(`⚠ Merge conflicts resolved on ${result.conflictFiles.length} file(s):`));
220
+ for (const file of result.conflictFiles) {
221
+ console.log(chalk.yellow(` - ${file}`));
222
+ }
223
+ console.log(chalk.dim(' Local version kept (encrypted files cannot be merged).'));
224
+ }
225
+ if (result.pulled) {
226
+ console.log(chalk.green('✅ Pulled latest from remote'));
227
+ }
228
+ if (result.committed) {
229
+ console.log(chalk.green(`✅ Committed ${result.fileCount} file(s)`));
230
+ }
231
+ else {
232
+ console.log(chalk.dim(' No changes to commit'));
233
+ }
234
+ if (result.pushed) {
235
+ console.log(chalk.green('✅ Pushed to remote'));
236
+ }
237
+ else if (!result.hasRemote) {
238
+ console.log(chalk.dim(' No remote configured — local only'));
239
+ }
240
+ console.log(chalk.green('\n✅ Sync complete'));
241
+ }));
242
+ }
243
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `ctx-sync team` command group.
3
+ *
4
+ * Manages team members (multi-recipient encryption):
5
+ * - `team add --name <n> --key <pubkey>` — add a team member with fingerprint verification.
6
+ * - `team remove <name>` — remove a team member and re-encrypt all state.
7
+ * - `team list` — list all team members and their public keys.
8
+ * - `team revoke <pubkey>` — immediately revoke a key and re-encrypt all state.
9
+ *
10
+ * **Security:**
11
+ * - Adding a member prompts for out-of-band fingerprint verification.
12
+ * - Removing/revoking a member triggers full re-encryption of all state files
13
+ * so the revoked key can no longer decrypt current or future data.
14
+ * - Recipients config is stored locally and NEVER synced to Git.
15
+ *
16
+ * @module commands/team
17
+ */
18
+ import type { Command } from 'commander';
19
+ /** Options for team add */
20
+ export interface TeamAddOptions {
21
+ name: string;
22
+ key: string;
23
+ /** Skip fingerprint verification prompt (for testing) */
24
+ noVerify?: boolean;
25
+ }
26
+ /** Result of team add */
27
+ export interface TeamAddResult {
28
+ name: string;
29
+ publicKey: string;
30
+ fingerprint: string;
31
+ }
32
+ /** Result of team remove / revoke */
33
+ export interface TeamRemoveResult {
34
+ name: string;
35
+ publicKey: string;
36
+ filesReEncrypted: string[];
37
+ }
38
+ /** Result of team list */
39
+ export interface TeamListResult {
40
+ ownerPublicKey: string;
41
+ members: Array<{
42
+ name: string;
43
+ publicKey: string;
44
+ fingerprint: string;
45
+ addedAt: string;
46
+ }>;
47
+ }
48
+ /**
49
+ * Execute `ctx-sync team add`.
50
+ *
51
+ * Adds a new team member to the recipients list and re-encrypts
52
+ * all state files so the new member can decrypt them.
53
+ */
54
+ export declare function executeTeamAdd(options: TeamAddOptions): Promise<TeamAddResult>;
55
+ /**
56
+ * Execute `ctx-sync team remove`.
57
+ *
58
+ * Removes a team member by name and re-encrypts all state files
59
+ * so the removed member can no longer decrypt them.
60
+ */
61
+ export declare function executeTeamRemove(name: string): Promise<TeamRemoveResult>;
62
+ /**
63
+ * Execute `ctx-sync team revoke`.
64
+ *
65
+ * Immediately revokes a team member's key and re-encrypts all
66
+ * state files. Similar to remove but uses the public key directly.
67
+ */
68
+ export declare function executeTeamRevoke(publicKey: string): Promise<TeamRemoveResult>;
69
+ /**
70
+ * Execute `ctx-sync team list`.
71
+ *
72
+ * Returns the owner's public key and all team members.
73
+ */
74
+ export declare function executeTeamList(): Promise<TeamListResult>;
75
+ /**
76
+ * Register the `ctx-sync team` command group on the given program.
77
+ */
78
+ export declare function registerTeamCommand(program: Command): void;
79
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/commands/team.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBzC,2BAA2B;AAC3B,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAgED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAcxB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,CAAC,CAc3B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAc3B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,cAAc,CAAC,CAqB/D;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6F1D"}