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.
- package/dist/commands/audit.d.ts +76 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +367 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/config.d.ts +58 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/dir.d.ts +56 -0
- package/dist/commands/dir.d.ts.map +1 -0
- package/dist/commands/dir.js +172 -0
- package/dist/commands/dir.js.map +1 -0
- package/dist/commands/docker.d.ts +140 -0
- package/dist/commands/docker.d.ts.map +1 -0
- package/dist/commands/docker.js +380 -0
- package/dist/commands/docker.js.map +1 -0
- package/dist/commands/env.d.ts +96 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +352 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/init.d.ts +89 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/key.d.ts +92 -0
- package/dist/commands/key.d.ts.map +1 -0
- package/dist/commands/key.js +274 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/list.d.ts +38 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +84 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/note.d.ts +151 -0
- package/dist/commands/note.d.ts.map +1 -0
- package/dist/commands/note.js +411 -0
- package/dist/commands/note.js.map +1 -0
- package/dist/commands/pull.d.ts +47 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +94 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +94 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/restore.d.ts +116 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +336 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/service.d.ts +83 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +259 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/show.d.ts +63 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +243 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +53 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +150 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +105 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +243 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/team.d.ts +79 -0
- package/dist/commands/team.d.ts.map +1 -0
- package/dist/commands/team.js +233 -0
- package/dist/commands/team.js.map +1 -0
- package/dist/commands/track.d.ts +109 -0
- package/dist/commands/track.d.ts.map +1 -0
- package/dist/commands/track.js +406 -0
- package/dist/commands/track.js.map +1 -0
- package/dist/core/command-validator.d.ts +100 -0
- package/dist/core/command-validator.d.ts.map +1 -0
- package/dist/core/command-validator.js +299 -0
- package/dist/core/command-validator.js.map +1 -0
- package/dist/core/config-store.d.ts +76 -0
- package/dist/core/config-store.d.ts.map +1 -0
- package/dist/core/config-store.js +148 -0
- package/dist/core/config-store.js.map +1 -0
- package/dist/core/directories-handler.d.ts +116 -0
- package/dist/core/directories-handler.d.ts.map +1 -0
- package/dist/core/directories-handler.js +199 -0
- package/dist/core/directories-handler.js.map +1 -0
- package/dist/core/docker-handler.d.ts +183 -0
- package/dist/core/docker-handler.d.ts.map +1 -0
- package/dist/core/docker-handler.js +515 -0
- package/dist/core/docker-handler.js.map +1 -0
- package/dist/core/encryption.d.ts +79 -0
- package/dist/core/encryption.d.ts.map +1 -0
- package/dist/core/encryption.js +111 -0
- package/dist/core/encryption.js.map +1 -0
- package/dist/core/env-handler.d.ts +128 -0
- package/dist/core/env-handler.d.ts.map +1 -0
- package/dist/core/env-handler.js +272 -0
- package/dist/core/env-handler.js.map +1 -0
- package/dist/core/git-sync.d.ts +88 -0
- package/dist/core/git-sync.d.ts.map +1 -0
- package/dist/core/git-sync.js +143 -0
- package/dist/core/git-sync.js.map +1 -0
- package/dist/core/key-store.d.ts +51 -0
- package/dist/core/key-store.d.ts.map +1 -0
- package/dist/core/key-store.js +108 -0
- package/dist/core/key-store.js.map +1 -0
- package/dist/core/log-sanitizer.d.ts +72 -0
- package/dist/core/log-sanitizer.d.ts.map +1 -0
- package/dist/core/log-sanitizer.js +202 -0
- package/dist/core/log-sanitizer.js.map +1 -0
- package/dist/core/path-validator.d.ts +37 -0
- package/dist/core/path-validator.d.ts.map +1 -0
- package/dist/core/path-validator.js +127 -0
- package/dist/core/path-validator.js.map +1 -0
- package/dist/core/recipients.d.ts +99 -0
- package/dist/core/recipients.d.ts.map +1 -0
- package/dist/core/recipients.js +206 -0
- package/dist/core/recipients.js.map +1 -0
- package/dist/core/services-handler.d.ts +113 -0
- package/dist/core/services-handler.d.ts.map +1 -0
- package/dist/core/services-handler.js +176 -0
- package/dist/core/services-handler.js.map +1 -0
- package/dist/core/state-manager.d.ts +96 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +165 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/core/transport.d.ts +28 -0
- package/dist/core/transport.d.ts.map +1 -0
- package/dist/core/transport.js +79 -0
- package/dist/core/transport.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +81 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +191 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/secure-memory.d.ts +65 -0
- package/dist/utils/secure-memory.d.ts.map +1 -0
- package/dist/utils/secure-memory.js +86 -0
- package/dist/utils/secure-memory.js.map +1 -0
- 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":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgCtD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAe;IACtD,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAExD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;IAClD,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAe;IAEf,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAE/B,uBAAuB;IACvB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAExD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;IAClD,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAEjE,yBAAyB;QACzB,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACvE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;YAC5C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;QACtD,CAAC;QAED,+BAA+B;QAC/B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,aAAuB,EACvB,WAAoB,IAAI;IAExB,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,IAAI,QAAQ,EAAE,CAAC;YACb,4BAA4B;YAC5B,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,+BAA+B;YAC/B,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,qBAAqB;IACrB,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;IAExB,iCAAiC;IACjC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAuB,EAAE;IACzD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,yBAAyB;IACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CACb,sDAAsD,CACvD,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAe;QACzB,MAAM,EAAE,KAAK;QACb,SAAS,EAAE,KAAK;QAChB,MAAM,EAAE,KAAK;QACb,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,KAAK;QACnB,aAAa,EAAE,EAAE;QACjB,SAAS,EAAE,KAAK;KACjB,CAAC;IAEF,iCAAiC;IACjC,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,CAAC,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAEtC,oDAAoD;IACpD,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,MAAM,yBAAyB,CAAC,OAAO,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;QAElC,4BAA4B;QAC5B,IAAI,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;YAC3B,MAAM,CAAC,aAAa,GAAG,UAAU,CAAC,aAAa,CAAC;YAEhD,+DAA+D;YAC/D,kDAAkD;YAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC;YACjD,MAAM,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI;QACxC,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAClC,KAAK,EAAE,EAAE;KACV,CAAC;IACF,QAAQ,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAEjC,uCAAuC;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;IAEhC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,8BAA8B,CAAC,CAAC;QAC/E,MAAM,CAAC,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;QACjC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,gDAAgD;IAChD,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;SAC/C,MAAM,CAAC,WAAW,EAAE,wBAAwB,CAAC;SAC7C,MAAM,CAAC,kBAAkB,EAAE,+CAA+C,CAAC;SAC3E,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC/D,MAAM,OAAO,GAAgB;YAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK;YAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK;YAC9B,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,KAAK,KAAK;SAC7C,CAAC;QAEF,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAE7C,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,KAAK,EAAE,CAAC;QAE1C,aAAa;QACb,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,GAAG,+BAA+B,CAAC;QACjD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QAE1C,OAAO,CAAC,IAAI,EAAE,CAAC;QAEf,iBAAiB;QACjB,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,iCAAiC,MAAM,CAAC,aAAa,CAAC,MAAM,WAAW,CAAC,CACtF,CAAC;YACF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;YAC5C,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;QACtF,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,KAAK,CAAC,eAAe,MAAM,CAAC,SAAS,UAAU,CAAC,CACvD,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
|
|
@@ -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"}
|