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,94 @@
1
+ /**
2
+ * `ctx-sync push` command.
3
+ *
4
+ * Commits all encrypted state files (.age + manifest.json) and pushes to
5
+ * the remote. Does NOT pull first — this is a one-way push operation.
6
+ *
7
+ * Validates remote URL (transport security) before every push.
8
+ *
9
+ * @module commands/push
10
+ */
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import { withErrorHandler } from '../utils/errors.js';
14
+ import { commitState, pushState } from '../core/git-sync.js';
15
+ import { readManifest, writeManifest } from '../core/state-manager.js';
16
+ import { validateSyncRemote, collectSyncFiles } from './sync.js';
17
+ import { getSyncDir } from './init.js';
18
+ /**
19
+ * Execute the push command logic.
20
+ *
21
+ * 1. Validate remote URL.
22
+ * 2. Update manifest timestamp.
23
+ * 3. Commit all .age files + manifest.json.
24
+ * 4. Push to remote.
25
+ *
26
+ * @returns Push result with operation details.
27
+ */
28
+ export async function executePush() {
29
+ const syncDir = getSyncDir();
30
+ // Verify sync dir exists
31
+ if (!fs.existsSync(syncDir) || !fs.existsSync(path.join(syncDir, '.git'))) {
32
+ throw new Error('No sync repository found. Run `ctx-sync init` first.');
33
+ }
34
+ const result = {
35
+ committed: false,
36
+ pushed: false,
37
+ commitHash: null,
38
+ fileCount: 0,
39
+ hasRemote: false,
40
+ };
41
+ // 1. Validate remote
42
+ const remoteUrl = await validateSyncRemote(syncDir);
43
+ result.hasRemote = remoteUrl !== null;
44
+ // 2. Update manifest timestamp
45
+ const manifest = readManifest(syncDir) ?? {
46
+ version: '1.0.0',
47
+ lastSync: new Date().toISOString(),
48
+ files: {},
49
+ };
50
+ manifest.lastSync = new Date().toISOString();
51
+ writeManifest(syncDir, manifest);
52
+ // 3. Collect and commit all sync files
53
+ const files = collectSyncFiles(syncDir);
54
+ result.fileCount = files.length;
55
+ if (files.length > 0) {
56
+ const hash = await commitState(syncDir, files, 'sync: push encrypted state');
57
+ result.committed = hash !== null;
58
+ result.commitHash = hash;
59
+ }
60
+ // 4. Push to remote
61
+ if (result.hasRemote) {
62
+ await pushState(syncDir);
63
+ result.pushed = true;
64
+ }
65
+ return result;
66
+ }
67
+ /**
68
+ * Register the `push` command on the given Commander program.
69
+ */
70
+ export function registerPushCommand(program) {
71
+ program
72
+ .command('push')
73
+ .description('Commit and push encrypted state to remote')
74
+ .action(withErrorHandler(async () => {
75
+ const chalk = (await import('chalk')).default;
76
+ const { default: ora } = await import('ora');
77
+ const spinner = ora('Pushing...').start();
78
+ const result = await executePush();
79
+ spinner.stop();
80
+ if (result.committed) {
81
+ console.log(chalk.green(`✅ Committed ${result.fileCount} file(s)`));
82
+ }
83
+ else {
84
+ console.log(chalk.dim(' No changes to commit'));
85
+ }
86
+ if (result.pushed) {
87
+ console.log(chalk.green('✅ Pushed to remote'));
88
+ }
89
+ else if (!result.hasRemote) {
90
+ console.log(chalk.yellow('⚠ No remote configured — committed locally only'));
91
+ }
92
+ }));
93
+ }
94
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `ctx-sync restore <project>` command.
3
+ *
4
+ * Decrypts all state files and restores a project's context on a new
5
+ * (or existing) machine:
6
+ * 1. Decrypt state.age → find the project entry.
7
+ * 2. Display project info (directory, branch, env var count).
8
+ * 3. Display mental context (if available).
9
+ * 4. Collect commands to execute (Docker services, auto-start services).
10
+ * 5. Present commands for user approval (MANDATORY — no bypass).
11
+ * 6. Execute approved commands.
12
+ * 7. Set up env vars (.env file) in the project directory.
13
+ * 8. Checkout correct git branch (if repo exists locally).
14
+ *
15
+ * **Security:** Commands are NEVER auto-executed. There is no `--yes` or
16
+ * `--no-confirm` flag. In `--no-interactive` mode, commands are displayed
17
+ * but not executed.
18
+ *
19
+ * @module commands/restore
20
+ */
21
+ import type { Command } from 'commander';
22
+ import type { Project, ProjectMentalContext } from '@ctx-sync/shared';
23
+ import type { PendingCommand, ApprovalResult } from '../core/command-validator.js';
24
+ /** Options for the restore command */
25
+ export interface RestoreOptions {
26
+ /** Non-interactive mode: display commands but skip execution */
27
+ noInteractive?: boolean;
28
+ /** Override for the prompt function (for testing) */
29
+ promptFn?: (commands: PendingCommand[]) => Promise<'all' | 'none' | 'select'>;
30
+ /** Override for the select function (for testing) */
31
+ selectFn?: (cmd: PendingCommand, index: number) => Promise<boolean>;
32
+ }
33
+ /** Result of a restore operation */
34
+ export interface RestoreResult {
35
+ /** The project that was restored */
36
+ project: Project;
37
+ /** Number of env vars available for the project */
38
+ envVarCount: number;
39
+ /** Whether env vars were written to a .env file */
40
+ envFileWritten: boolean;
41
+ /** Whether the git branch was checked out */
42
+ branchCheckedOut: boolean;
43
+ /** Mental context for the project (if available) */
44
+ mentalContext: ProjectMentalContext | null;
45
+ /** Commands that were presented for approval */
46
+ commandsPresented: PendingCommand[];
47
+ /** Approval result */
48
+ approval: ApprovalResult;
49
+ /** Commands that were executed */
50
+ executedCommands: string[];
51
+ /** Commands that failed */
52
+ failedCommands: Array<{
53
+ command: string;
54
+ error: string;
55
+ }>;
56
+ }
57
+ /**
58
+ * Collect all commands that need to be executed for a project restore.
59
+ *
60
+ * Gathers Docker service commands and auto-start service commands from
61
+ * the encrypted state files.
62
+ *
63
+ * @param projectName - The name of the project to restore.
64
+ * @param syncDir - The sync directory path.
65
+ * @param privateKey - The Age private key for decryption.
66
+ * @returns List of pending commands for approval.
67
+ */
68
+ export declare function collectRestoreCommands(projectName: string, syncDir: string, privateKey: string): Promise<PendingCommand[]>;
69
+ /**
70
+ * Write env vars to a .env file in the project directory.
71
+ *
72
+ * Creates or overwrites the .env file with all env vars for the project.
73
+ * If the project directory does not exist, skips writing.
74
+ *
75
+ * @param projectPath - The absolute path to the project directory.
76
+ * @param envVars - The decrypted env vars for the project.
77
+ * @returns Whether the file was written.
78
+ */
79
+ export declare function writeEnvFile(projectPath: string, envVars: Record<string, {
80
+ value: string;
81
+ addedAt: string;
82
+ }>): boolean;
83
+ /**
84
+ * Attempt to checkout the correct git branch in the project directory.
85
+ *
86
+ * If the project has a local git repo and the branch exists, checks it out.
87
+ * Does not fail if the branch doesn't exist or git is not available.
88
+ *
89
+ * @param projectPath - The absolute path to the project directory.
90
+ * @param branch - The branch to checkout.
91
+ * @returns Whether the branch was checked out.
92
+ */
93
+ export declare function checkoutBranch(projectPath: string, branch: string): Promise<boolean>;
94
+ /**
95
+ * Execute the restore command logic.
96
+ *
97
+ * Decrypts state, displays project context, presents commands for
98
+ * approval, and restores env vars and git branch.
99
+ *
100
+ * @param projectName - The name of the project to restore.
101
+ * @param options - Restore command options.
102
+ * @returns Restore result with all operation details.
103
+ */
104
+ export declare function executeRestore(projectName: string, options?: RestoreOptions): Promise<RestoreResult>;
105
+ /**
106
+ * Format mental context for terminal display.
107
+ *
108
+ * @param context - The mental context to display.
109
+ * @returns Formatted string for terminal output.
110
+ */
111
+ export declare function formatMentalContext(context: ProjectMentalContext): string;
112
+ /**
113
+ * Register the `restore` command on the given Commander program.
114
+ */
115
+ export declare function registerRestoreCommand(program: Command): void;
116
+ //# sourceMappingURL=restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore.d.ts","sourceRoot":"","sources":["../../src/commands/restore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAEV,OAAO,EAKP,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAQ1B,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInF,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC9E,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE;AAED,oCAAoC;AACpC,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,cAAc,EAAE,OAAO,CAAC;IACxB,6CAA6C;IAC7C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,gDAAgD;IAChD,iBAAiB,EAAE,cAAc,EAAE,CAAC;IACpC,sBAAsB;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,kCAAkC;IAClC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,2BAA2B;IAC3B,cAAc,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,EAAE,CAAC,CAwC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1D,OAAO,CAsBT;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CA0BlB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAuGxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAiDzE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkE7D"}
@@ -0,0 +1,336 @@
1
+ /**
2
+ * `ctx-sync restore <project>` command.
3
+ *
4
+ * Decrypts all state files and restores a project's context on a new
5
+ * (or existing) machine:
6
+ * 1. Decrypt state.age → find the project entry.
7
+ * 2. Display project info (directory, branch, env var count).
8
+ * 3. Display mental context (if available).
9
+ * 4. Collect commands to execute (Docker services, auto-start services).
10
+ * 5. Present commands for user approval (MANDATORY — no bypass).
11
+ * 6. Execute approved commands.
12
+ * 7. Set up env vars (.env file) in the project directory.
13
+ * 8. Checkout correct git branch (if repo exists locally).
14
+ *
15
+ * **Security:** Commands are NEVER auto-executed. There is no `--yes` or
16
+ * `--no-confirm` flag. In `--no-interactive` mode, commands are displayed
17
+ * but not executed.
18
+ *
19
+ * @module commands/restore
20
+ */
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { identityToRecipient } from 'age-encryption';
24
+ import { loadKey } from '../core/key-store.js';
25
+ import { readState } from '../core/state-manager.js';
26
+ import { formatCommandsForDisplay, presentCommandsForApproval, } from '../core/command-validator.js';
27
+ import { getConfigDir, getSyncDir } from './init.js';
28
+ import { withErrorHandler } from '../utils/errors.js';
29
+ /**
30
+ * Collect all commands that need to be executed for a project restore.
31
+ *
32
+ * Gathers Docker service commands and auto-start service commands from
33
+ * the encrypted state files.
34
+ *
35
+ * @param projectName - The name of the project to restore.
36
+ * @param syncDir - The sync directory path.
37
+ * @param privateKey - The Age private key for decryption.
38
+ * @returns List of pending commands for approval.
39
+ */
40
+ export async function collectRestoreCommands(projectName, syncDir, privateKey) {
41
+ const commands = [];
42
+ // Collect Docker service commands
43
+ const dockerState = await readState(syncDir, privateKey, 'docker-state');
44
+ if (dockerState && dockerState[projectName]) {
45
+ const projectDocker = dockerState[projectName];
46
+ if (projectDocker) {
47
+ for (const service of projectDocker.services) {
48
+ if (service.autoStart) {
49
+ commands.push({
50
+ command: `docker compose up -d ${service.name}`,
51
+ label: '🐳 Docker services',
52
+ port: service.port,
53
+ image: service.image,
54
+ cwd: projectDocker.composeFile
55
+ ? path.dirname(projectDocker.composeFile)
56
+ : undefined,
57
+ });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ // Collect auto-start service commands
63
+ const serviceState = await readState(syncDir, privateKey, 'services');
64
+ if (serviceState) {
65
+ const projectServices = serviceState.services.filter((s) => s.project === projectName && s.autoStart);
66
+ for (const service of projectServices) {
67
+ commands.push({
68
+ command: service.command,
69
+ label: '⚡ Auto-start services',
70
+ port: service.port,
71
+ });
72
+ }
73
+ }
74
+ return commands;
75
+ }
76
+ /**
77
+ * Write env vars to a .env file in the project directory.
78
+ *
79
+ * Creates or overwrites the .env file with all env vars for the project.
80
+ * If the project directory does not exist, skips writing.
81
+ *
82
+ * @param projectPath - The absolute path to the project directory.
83
+ * @param envVars - The decrypted env vars for the project.
84
+ * @returns Whether the file was written.
85
+ */
86
+ export function writeEnvFile(projectPath, envVars) {
87
+ if (!fs.existsSync(projectPath)) {
88
+ return false;
89
+ }
90
+ const lines = [];
91
+ lines.push('# Generated by ctx-sync restore');
92
+ lines.push(`# ${new Date().toISOString()}`);
93
+ lines.push('');
94
+ for (const [key, entry] of Object.entries(envVars)) {
95
+ // Quote values that contain spaces, newlines, or special characters
96
+ const needsQuotes = /[\s"'\\#]/.test(entry.value) || entry.value === '';
97
+ const escapedValue = needsQuotes
98
+ ? `"${entry.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`
99
+ : entry.value;
100
+ lines.push(`${key}=${escapedValue}`);
101
+ }
102
+ const envPath = path.join(projectPath, '.env');
103
+ fs.writeFileSync(envPath, lines.join('\n') + '\n', 'utf-8');
104
+ return true;
105
+ }
106
+ /**
107
+ * Attempt to checkout the correct git branch in the project directory.
108
+ *
109
+ * If the project has a local git repo and the branch exists, checks it out.
110
+ * Does not fail if the branch doesn't exist or git is not available.
111
+ *
112
+ * @param projectPath - The absolute path to the project directory.
113
+ * @param branch - The branch to checkout.
114
+ * @returns Whether the branch was checked out.
115
+ */
116
+ export async function checkoutBranch(projectPath, branch) {
117
+ if (!fs.existsSync(path.join(projectPath, '.git'))) {
118
+ return false;
119
+ }
120
+ if (!branch || branch === 'unknown') {
121
+ return false;
122
+ }
123
+ try {
124
+ const { simpleGit } = await import('simple-git');
125
+ const git = simpleGit(projectPath);
126
+ // Check current branch — skip if already on the right branch
127
+ const branchResult = await git.branch();
128
+ if (branchResult.current === branch) {
129
+ return true;
130
+ }
131
+ // Try to checkout the branch
132
+ await git.checkout(branch);
133
+ return true;
134
+ }
135
+ catch {
136
+ // Branch may not exist locally — that's OK
137
+ return false;
138
+ }
139
+ }
140
+ /**
141
+ * Execute the restore command logic.
142
+ *
143
+ * Decrypts state, displays project context, presents commands for
144
+ * approval, and restores env vars and git branch.
145
+ *
146
+ * @param projectName - The name of the project to restore.
147
+ * @param options - Restore command options.
148
+ * @returns Restore result with all operation details.
149
+ */
150
+ export async function executeRestore(projectName, options = {}) {
151
+ const configDir = getConfigDir();
152
+ const syncDir = getSyncDir();
153
+ // Verify sync dir exists
154
+ if (!fs.existsSync(syncDir) || !fs.existsSync(path.join(syncDir, '.git'))) {
155
+ throw new Error('No sync repository found. Run `ctx-sync init` first.');
156
+ }
157
+ // Load key
158
+ const privateKey = loadKey(configDir);
159
+ const publicKey = await identityToRecipient(privateKey);
160
+ // publicKey is available for future use (re-encryption after restore)
161
+ void publicKey;
162
+ // 1. Decrypt state and find the project
163
+ const state = await readState(syncDir, privateKey, 'state');
164
+ if (!state) {
165
+ throw new Error('No state file found. Track a project first with `ctx-sync track`.');
166
+ }
167
+ const project = state.projects.find((p) => p.name === projectName || p.id === projectName);
168
+ if (!project) {
169
+ const availableNames = state.projects.map((p) => p.name).join(', ');
170
+ throw new Error(`Project "${projectName}" not found.\n` +
171
+ (availableNames
172
+ ? `Available projects: ${availableNames}`
173
+ : 'No projects tracked yet. Run `ctx-sync track` first.'));
174
+ }
175
+ // 2. Count env vars
176
+ const envVars = await readState(syncDir, privateKey, 'env-vars');
177
+ const projectEnvVars = envVars?.[project.name] ?? {};
178
+ const envVarCount = Object.keys(projectEnvVars).length;
179
+ // 3. Load mental context
180
+ const mentalContextData = await readState(syncDir, privateKey, 'mental-context');
181
+ const mentalContext = mentalContextData?.[project.name] ?? null;
182
+ // 4. Collect commands to be executed
183
+ const commandsPresented = await collectRestoreCommands(project.name, syncDir, privateKey);
184
+ // 5. Present commands for approval
185
+ const approval = await presentCommandsForApproval(commandsPresented, {
186
+ interactive: !options.noInteractive,
187
+ promptFn: options.promptFn,
188
+ selectFn: options.selectFn,
189
+ });
190
+ // 6. Execute approved commands
191
+ const executedCommands = [];
192
+ const failedCommands = [];
193
+ for (const cmd of approval.approved) {
194
+ try {
195
+ const { execSync } = await import('node:child_process');
196
+ execSync(cmd.command, {
197
+ cwd: cmd.cwd ?? project.path,
198
+ stdio: 'pipe',
199
+ timeout: 60000,
200
+ });
201
+ executedCommands.push(cmd.command);
202
+ }
203
+ catch (err) {
204
+ const message = err instanceof Error ? err.message : String(err);
205
+ failedCommands.push({ command: cmd.command, error: message });
206
+ }
207
+ }
208
+ // 7. Write env vars to .env file
209
+ let envFileWritten = false;
210
+ if (envVarCount > 0 && fs.existsSync(project.path)) {
211
+ envFileWritten = writeEnvFile(project.path, projectEnvVars);
212
+ }
213
+ // 8. Checkout git branch
214
+ const branchCheckedOut = await checkoutBranch(project.path, project.git.branch);
215
+ return {
216
+ project,
217
+ envVarCount,
218
+ envFileWritten,
219
+ branchCheckedOut,
220
+ mentalContext,
221
+ commandsPresented,
222
+ approval,
223
+ executedCommands,
224
+ failedCommands,
225
+ };
226
+ }
227
+ /**
228
+ * Format mental context for terminal display.
229
+ *
230
+ * @param context - The mental context to display.
231
+ * @returns Formatted string for terminal output.
232
+ */
233
+ export function formatMentalContext(context) {
234
+ const lines = [];
235
+ if (context.currentTask) {
236
+ lines.push(`📝 You were working on:`);
237
+ lines.push(` "${context.currentTask}"`);
238
+ }
239
+ if (context.lastWorkingOn) {
240
+ lines.push('');
241
+ lines.push(` Last file: ${context.lastWorkingOn.file}:${context.lastWorkingOn.line}`);
242
+ if (context.lastWorkingOn.description) {
243
+ lines.push(` ${context.lastWorkingOn.description}`);
244
+ }
245
+ }
246
+ if (context.blockers.length > 0) {
247
+ lines.push('');
248
+ lines.push(' ⛔ Blockers:');
249
+ for (const blocker of context.blockers) {
250
+ lines.push(` • ${blocker.description}`);
251
+ }
252
+ }
253
+ if (context.nextSteps.length > 0) {
254
+ lines.push('');
255
+ lines.push(' Next steps:');
256
+ for (const step of context.nextSteps) {
257
+ lines.push(` • ${step}`);
258
+ }
259
+ }
260
+ if (context.relatedLinks.length > 0) {
261
+ lines.push('');
262
+ lines.push(' 🔗 Related:');
263
+ for (const link of context.relatedLinks) {
264
+ lines.push(` • ${link.title}: ${link.url}`);
265
+ }
266
+ }
267
+ if (context.breadcrumbs.length > 0) {
268
+ lines.push('');
269
+ lines.push(' 🍞 Breadcrumbs:');
270
+ for (const crumb of context.breadcrumbs) {
271
+ lines.push(` • ${crumb.note}`);
272
+ }
273
+ }
274
+ return lines.join('\n');
275
+ }
276
+ /**
277
+ * Register the `restore` command on the given Commander program.
278
+ */
279
+ export function registerRestoreCommand(program) {
280
+ program
281
+ .command('restore <project>')
282
+ .description('Restore a tracked project on this machine')
283
+ .option('--no-interactive', 'Show commands but skip execution (safe default)')
284
+ .action(withErrorHandler(async (projectName, opts) => {
285
+ const options = {
286
+ noInteractive: opts['interactive'] === false,
287
+ };
288
+ const chalk = (await import('chalk')).default;
289
+ const { default: ora } = await import('ora');
290
+ const spinner = ora('Decrypting state files...').start();
291
+ const result = await executeRestore(projectName, options);
292
+ spinner.stop();
293
+ // Display project info
294
+ console.log(chalk.green(`\n✅ Restored: ${result.project.name}`));
295
+ console.log('');
296
+ console.log(`📂 Directory: ${result.project.path}`);
297
+ console.log(`🌿 Branch: ${result.project.git.branch}`);
298
+ console.log(`🔐 Env vars: ${result.envVarCount} decrypted`);
299
+ if (result.branchCheckedOut) {
300
+ console.log(chalk.dim(' Git branch checked out'));
301
+ }
302
+ if (result.envFileWritten) {
303
+ console.log(chalk.dim(' .env file written'));
304
+ }
305
+ // Display mental context
306
+ if (result.mentalContext) {
307
+ console.log('');
308
+ console.log(formatMentalContext(result.mentalContext));
309
+ }
310
+ // Display commands
311
+ if (result.commandsPresented.length > 0) {
312
+ console.log('');
313
+ console.log(chalk.yellow('⚠️ The following commands will be executed:'));
314
+ console.log(formatCommandsForDisplay(result.commandsPresented));
315
+ console.log('');
316
+ if (result.approval.skippedAll) {
317
+ console.log(chalk.dim('Skipped (non-interactive mode)'));
318
+ }
319
+ else {
320
+ // Show execution results
321
+ for (const cmd of result.executedCommands) {
322
+ console.log(chalk.green(` ✓ ${cmd}`));
323
+ }
324
+ for (const { command, error } of result.failedCommands) {
325
+ console.log(chalk.red(` ✗ ${command}: ${error}`));
326
+ }
327
+ for (const cmd of result.approval.rejected) {
328
+ console.log(chalk.dim(` ⏭️ Skipped: ${cmd.command}`));
329
+ }
330
+ }
331
+ }
332
+ console.log('');
333
+ console.log(chalk.green('Ready to work! 🚀'));
334
+ }));
335
+ }
336
+ //# sourceMappingURL=restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../src/commands/restore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `ctx-sync service` command group.
3
+ *
4
+ * Manages running service state:
5
+ * - `service add <project> <name>` — add / update a service.
6
+ * - `service remove <project> <name>` — remove a service.
7
+ * - `service list [project]` — list tracked services.
8
+ * - `service start <project>` — present auto-start commands for approval.
9
+ *
10
+ * **Security:** `service start` commands go through the command validator.
11
+ * All start commands require explicit user confirmation — there is no
12
+ * `--yes` or `--no-confirm` bypass.
13
+ *
14
+ * @module commands/service
15
+ */
16
+ import type { Command } from 'commander';
17
+ import type { Service } from '@ctx-sync/shared';
18
+ import type { PendingCommand, ApprovalResult } from '../core/command-validator.js';
19
+ /** Options for service add */
20
+ export interface ServiceAddOptions {
21
+ port: number;
22
+ command: string;
23
+ autoStart?: boolean;
24
+ noSync?: boolean;
25
+ }
26
+ /** Result of service add */
27
+ export interface ServiceAddResult {
28
+ projectName: string;
29
+ serviceName: string;
30
+ port: number;
31
+ command: string;
32
+ autoStart: boolean;
33
+ }
34
+ /** Options for service start */
35
+ export interface ServiceStartOptions {
36
+ /** Non-interactive mode: show commands but skip execution */
37
+ noInteractive?: boolean;
38
+ /** Override prompt function (for testing) */
39
+ promptFn?: (commands: PendingCommand[]) => Promise<'all' | 'none' | 'select'>;
40
+ /** Override per-command select function (for testing) */
41
+ selectFn?: (cmd: PendingCommand, index: number) => Promise<boolean>;
42
+ }
43
+ /** Result of service start */
44
+ export interface ServiceStartResult {
45
+ projectName: string;
46
+ commandsPresented: PendingCommand[];
47
+ approval: ApprovalResult;
48
+ executedCommands: string[];
49
+ failedCommands: Array<{
50
+ command: string;
51
+ error: string;
52
+ }>;
53
+ }
54
+ /**
55
+ * Execute `ctx-sync service add <project> <name>`.
56
+ */
57
+ export declare function executeServiceAdd(project: string, name: string, options: ServiceAddOptions): Promise<ServiceAddResult>;
58
+ /**
59
+ * Execute `ctx-sync service remove <project> <name>`.
60
+ */
61
+ export declare function executeServiceRemove(project: string, name: string, noSync?: boolean): Promise<boolean>;
62
+ /**
63
+ * Build commands for service start (without executing).
64
+ */
65
+ export declare function buildServiceStartCommands(services: Service[]): PendingCommand[];
66
+ /**
67
+ * Execute `ctx-sync service start <project>`.
68
+ *
69
+ * Shows auto-start services for user approval, then executes approved ones.
70
+ */
71
+ export declare function executeServiceStart(project: string, options?: ServiceStartOptions): Promise<ServiceStartResult>;
72
+ /**
73
+ * Execute `ctx-sync service list [project]`.
74
+ */
75
+ export declare function executeServiceList(project?: string): Promise<{
76
+ project: string;
77
+ services: Service[];
78
+ }[]>;
79
+ /**
80
+ * Register the `ctx-sync service` command group on the given program.
81
+ */
82
+ export declare function registerServiceCommand(program: Command): void;
83
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/commands/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAa,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAS3D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAenF,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,gCAAgC;AAChC,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC9E,yDAAyD;IACzD,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE;AAED,8BAA8B;AAC9B,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,cAAc,EAAE,CAAC;IACpC,QAAQ,EAAE,cAAc,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3D;AAqBD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAyC3B;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,UAAQ,GACb,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAM/E;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAkD7B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,EAAE,CAAC,CAmBrD;AAID;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmH7D"}