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,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync track` command.
|
|
3
|
+
*
|
|
4
|
+
* Detects the current project's Git state, validates the path,
|
|
5
|
+
* encrypts the project entry, and writes it to `state.age`.
|
|
6
|
+
*
|
|
7
|
+
* Phase 16 enhancements: Step-by-step wizard with auto-detection,
|
|
8
|
+
* .env import, Docker tracking, mental context, and summary.
|
|
9
|
+
* `--yes` flag skips interactive confirmations (but NOT restore commands).
|
|
10
|
+
*
|
|
11
|
+
* @module commands/track
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import * as crypto from 'node:crypto';
|
|
17
|
+
import { STATE_FILES } from '@ctx-sync/shared';
|
|
18
|
+
import { identityToRecipient } from 'age-encryption';
|
|
19
|
+
import { validateProjectPath } from '../core/path-validator.js';
|
|
20
|
+
import { loadKey } from '../core/key-store.js';
|
|
21
|
+
import { readState, writeState } from '../core/state-manager.js';
|
|
22
|
+
import { commitState } from '../core/git-sync.js';
|
|
23
|
+
import { getConfigDir, getSyncDir } from './init.js';
|
|
24
|
+
import { withErrorHandler } from '../utils/errors.js';
|
|
25
|
+
/**
|
|
26
|
+
* Detect Git information for a project directory.
|
|
27
|
+
*
|
|
28
|
+
* @param projectPath - Absolute path to the project directory.
|
|
29
|
+
* @returns Git metadata (branch, remote, uncommitted, stash count).
|
|
30
|
+
*/
|
|
31
|
+
export async function detectGitInfo(projectPath) {
|
|
32
|
+
const defaultGit = {
|
|
33
|
+
branch: 'unknown',
|
|
34
|
+
remote: '',
|
|
35
|
+
hasUncommitted: false,
|
|
36
|
+
stashCount: 0,
|
|
37
|
+
};
|
|
38
|
+
const gitDir = path.join(projectPath, '.git');
|
|
39
|
+
if (!fs.existsSync(gitDir)) {
|
|
40
|
+
return defaultGit;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const { simpleGit } = await import('simple-git');
|
|
44
|
+
const git = simpleGit(projectPath);
|
|
45
|
+
// Branch
|
|
46
|
+
const branchResult = await git.branch();
|
|
47
|
+
const branch = branchResult.current || 'unknown';
|
|
48
|
+
// Remote
|
|
49
|
+
const remotes = await git.getRemotes(true);
|
|
50
|
+
const origin = remotes.find((r) => r.name === 'origin');
|
|
51
|
+
const remote = origin?.refs?.fetch ?? '';
|
|
52
|
+
// Uncommitted changes
|
|
53
|
+
const status = await git.status();
|
|
54
|
+
const hasUncommitted = !status.isClean();
|
|
55
|
+
// Stash count
|
|
56
|
+
let stashCount = 0;
|
|
57
|
+
try {
|
|
58
|
+
const stashList = await git.stashList();
|
|
59
|
+
stashCount = stashList.total;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// stash list can fail on repos with no stashes — that's fine
|
|
63
|
+
}
|
|
64
|
+
return { branch, remote, hasUncommitted, stashCount };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return defaultGit;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Auto-detect project name from Git remote URL or directory name.
|
|
72
|
+
*
|
|
73
|
+
* Priority:
|
|
74
|
+
* 1. Explicit `--name` flag.
|
|
75
|
+
* 2. Repository name from Git remote URL (e.g., `origin`).
|
|
76
|
+
* 3. Directory name.
|
|
77
|
+
*
|
|
78
|
+
* @param projectPath - Absolute path to the project directory.
|
|
79
|
+
* @param gitRemote - The Git remote fetch URL (empty if none).
|
|
80
|
+
* @returns The detected project name.
|
|
81
|
+
*/
|
|
82
|
+
export function detectProjectName(projectPath, gitRemote) {
|
|
83
|
+
// Try to extract repo name from Git remote URL
|
|
84
|
+
if (gitRemote) {
|
|
85
|
+
// SSH format: git@github.com:user/repo-name.git
|
|
86
|
+
const sshMatch = /\/([^/]+?)(?:\.git)?$/.exec(gitRemote);
|
|
87
|
+
if (sshMatch?.[1]) {
|
|
88
|
+
return sshMatch[1];
|
|
89
|
+
}
|
|
90
|
+
// HTTPS format: https://github.com/user/repo-name.git
|
|
91
|
+
const httpsMatch = /\/([^/]+?)(?:\.git)?$/.exec(gitRemote);
|
|
92
|
+
if (httpsMatch?.[1]) {
|
|
93
|
+
return httpsMatch[1];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Fall back to directory name
|
|
97
|
+
return path.basename(projectPath);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Derive the machine ID for the current host.
|
|
101
|
+
*
|
|
102
|
+
* Uses hostname as a simple machine identifier.
|
|
103
|
+
* A more robust approach (hardware ID) can be added later.
|
|
104
|
+
*/
|
|
105
|
+
function getMachineInfo() {
|
|
106
|
+
const hostname = os.hostname();
|
|
107
|
+
return {
|
|
108
|
+
id: crypto.createHash('sha256').update(hostname).digest('hex').slice(0, 16),
|
|
109
|
+
hostname,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Run the interactive wizard to collect user choices.
|
|
114
|
+
*
|
|
115
|
+
* @param context - Information about the detected project state.
|
|
116
|
+
* @returns User's wizard answers.
|
|
117
|
+
*/
|
|
118
|
+
async function runWizard(context) {
|
|
119
|
+
const Enquirer = (await import('enquirer')).default;
|
|
120
|
+
const enquirer = new Enquirer();
|
|
121
|
+
const answers = {
|
|
122
|
+
importEnv: false,
|
|
123
|
+
trackDocker: false,
|
|
124
|
+
currentTask: '',
|
|
125
|
+
nextSteps: [],
|
|
126
|
+
confirmCommit: true,
|
|
127
|
+
};
|
|
128
|
+
// Step 1: Confirm or change project name (already auto-detected)
|
|
129
|
+
// This is shown via console output, not a prompt
|
|
130
|
+
// Step 2: .env import prompt
|
|
131
|
+
if (context.envFileFound) {
|
|
132
|
+
const envResponse = await enquirer.prompt({
|
|
133
|
+
type: 'confirm',
|
|
134
|
+
name: 'importEnv',
|
|
135
|
+
message: 'Found .env file. Import environment variables?',
|
|
136
|
+
initial: true,
|
|
137
|
+
});
|
|
138
|
+
const envVal = String(envResponse.importEnv ?? '');
|
|
139
|
+
answers.importEnv = envVal === 'true' || envVal.toLowerCase() === 'yes';
|
|
140
|
+
}
|
|
141
|
+
// Step 3: Docker tracking prompt
|
|
142
|
+
if (context.dockerComposeFound) {
|
|
143
|
+
const dockerResponse = await enquirer.prompt({
|
|
144
|
+
type: 'confirm',
|
|
145
|
+
name: 'trackDocker',
|
|
146
|
+
message: 'Found Docker Compose. Track Docker services?',
|
|
147
|
+
initial: true,
|
|
148
|
+
});
|
|
149
|
+
const dVal = String(dockerResponse.trackDocker ?? '');
|
|
150
|
+
answers.trackDocker = dVal === 'true' || dVal.toLowerCase() === 'yes';
|
|
151
|
+
}
|
|
152
|
+
// Step 4: Mental context — current task
|
|
153
|
+
const taskResponse = await enquirer.prompt({
|
|
154
|
+
type: 'input',
|
|
155
|
+
name: 'currentTask',
|
|
156
|
+
message: 'What are you working on? (press Enter to skip)',
|
|
157
|
+
});
|
|
158
|
+
if (taskResponse.currentTask?.trim()) {
|
|
159
|
+
answers.currentTask = taskResponse.currentTask.trim();
|
|
160
|
+
}
|
|
161
|
+
// Step 5: Mental context — next steps
|
|
162
|
+
if (answers.currentTask) {
|
|
163
|
+
const stepsResponse = await enquirer.prompt({
|
|
164
|
+
type: 'input',
|
|
165
|
+
name: 'nextSteps',
|
|
166
|
+
message: 'Next steps? (comma-separated, or press Enter to skip)',
|
|
167
|
+
});
|
|
168
|
+
if (stepsResponse.nextSteps?.trim()) {
|
|
169
|
+
answers.nextSteps = stepsResponse.nextSteps
|
|
170
|
+
.split(',')
|
|
171
|
+
.map((s) => s.trim())
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Step 6: Summary and confirmation
|
|
176
|
+
answers.confirmCommit = true;
|
|
177
|
+
return answers;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Execute the track command logic.
|
|
181
|
+
*
|
|
182
|
+
* 1. Resolve and validate the project path.
|
|
183
|
+
* 2. Auto-detect project name from Git remote / directory.
|
|
184
|
+
* 3. Detect Git info, .env, docker-compose.
|
|
185
|
+
* 4. Run interactive wizard (if not --yes or --no-interactive).
|
|
186
|
+
* 5. Build or update the Project entry.
|
|
187
|
+
* 6. Optionally import .env, track Docker, set mental context.
|
|
188
|
+
* 7. Encrypt and write state.age.
|
|
189
|
+
* 8. Optionally commit to the sync repo.
|
|
190
|
+
*
|
|
191
|
+
* @param options - Track command options.
|
|
192
|
+
* @returns Track result including the project entry.
|
|
193
|
+
*/
|
|
194
|
+
export async function executeTrack(options) {
|
|
195
|
+
// 1. Resolve and validate path
|
|
196
|
+
const rawPath = options.path ?? process.cwd();
|
|
197
|
+
const projectPath = validateProjectPath(rawPath);
|
|
198
|
+
// 2. Load key and derive public key
|
|
199
|
+
const configDir = getConfigDir();
|
|
200
|
+
const syncDir = getSyncDir();
|
|
201
|
+
const privateKey = loadKey(configDir);
|
|
202
|
+
const publicKey = await identityToRecipient(privateKey);
|
|
203
|
+
// 3. Detect Git info
|
|
204
|
+
const gitInfo = await detectGitInfo(projectPath);
|
|
205
|
+
// 4. Auto-detect project name
|
|
206
|
+
const projectName = options.name ?? detectProjectName(projectPath, gitInfo.remote);
|
|
207
|
+
// 5. Check for .env and docker-compose
|
|
208
|
+
const envFileFound = fs.existsSync(path.join(projectPath, '.env'));
|
|
209
|
+
const dockerComposeFound = fs.existsSync(path.join(projectPath, 'docker-compose.yml')) ||
|
|
210
|
+
fs.existsSync(path.join(projectPath, 'docker-compose.yaml')) ||
|
|
211
|
+
fs.existsSync(path.join(projectPath, 'compose.yml')) ||
|
|
212
|
+
fs.existsSync(path.join(projectPath, 'compose.yaml'));
|
|
213
|
+
// 6. Read existing state (or create new)
|
|
214
|
+
const existingState = await readState(syncDir, privateKey, 'state');
|
|
215
|
+
const machine = getMachineInfo();
|
|
216
|
+
const state = existingState ?? {
|
|
217
|
+
machine,
|
|
218
|
+
projects: [],
|
|
219
|
+
};
|
|
220
|
+
// Ensure machine info is current
|
|
221
|
+
state.machine = machine;
|
|
222
|
+
// 7. Find or create project entry
|
|
223
|
+
const existingIndex = state.projects.findIndex((p) => p.path === projectPath);
|
|
224
|
+
const isNew = existingIndex === -1;
|
|
225
|
+
const existingProject = isNew ? undefined : state.projects[existingIndex];
|
|
226
|
+
const project = {
|
|
227
|
+
id: existingProject?.id ?? crypto.randomUUID(),
|
|
228
|
+
name: projectName,
|
|
229
|
+
path: projectPath,
|
|
230
|
+
git: gitInfo,
|
|
231
|
+
lastAccessed: new Date().toISOString(),
|
|
232
|
+
};
|
|
233
|
+
if (isNew) {
|
|
234
|
+
state.projects.push(project);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
state.projects[existingIndex] = project;
|
|
238
|
+
}
|
|
239
|
+
// 8. Run wizard (unless --yes or --no-interactive)
|
|
240
|
+
let wizardAnswers = null;
|
|
241
|
+
let envVarsImported = 0;
|
|
242
|
+
let dockerServicesTracked = 0;
|
|
243
|
+
let mentalContextSet = false;
|
|
244
|
+
const isInteractive = !options.yes && !options.noInteractive;
|
|
245
|
+
const autoAcceptAll = options.yes === true;
|
|
246
|
+
if (options.wizardPromptFn) {
|
|
247
|
+
// Testing override
|
|
248
|
+
wizardAnswers = await options.wizardPromptFn({
|
|
249
|
+
projectName,
|
|
250
|
+
projectPath,
|
|
251
|
+
gitBranch: gitInfo.branch,
|
|
252
|
+
envFileFound,
|
|
253
|
+
dockerComposeFound,
|
|
254
|
+
isNew,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else if (isInteractive && isNew) {
|
|
258
|
+
// Interactive wizard for new projects only
|
|
259
|
+
wizardAnswers = await runWizard({
|
|
260
|
+
projectName,
|
|
261
|
+
projectPath,
|
|
262
|
+
gitBranch: gitInfo.branch,
|
|
263
|
+
envFileFound,
|
|
264
|
+
dockerComposeFound,
|
|
265
|
+
isNew,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else if (autoAcceptAll) {
|
|
269
|
+
// --yes mode: auto-accept .env import and Docker tracking, skip mental context
|
|
270
|
+
wizardAnswers = {
|
|
271
|
+
importEnv: envFileFound,
|
|
272
|
+
trackDocker: dockerComposeFound,
|
|
273
|
+
currentTask: '',
|
|
274
|
+
nextSteps: [],
|
|
275
|
+
confirmCommit: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// 9. Process wizard answers — import .env
|
|
279
|
+
if (wizardAnswers?.importEnv && envFileFound) {
|
|
280
|
+
try {
|
|
281
|
+
const envFilePath = path.join(projectPath, '.env');
|
|
282
|
+
const content = fs.readFileSync(envFilePath, 'utf-8');
|
|
283
|
+
const { parseEnvFile, importEnvVars } = await import('../core/env-handler.js');
|
|
284
|
+
const vars = parseEnvFile(content);
|
|
285
|
+
envVarsImported = await importEnvVars(projectName, vars, syncDir, publicKey, privateKey);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// .env import failure is non-fatal
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// 10. Process wizard answers — track Docker
|
|
292
|
+
if (wizardAnswers?.trackDocker && dockerComposeFound) {
|
|
293
|
+
try {
|
|
294
|
+
const { buildDockerStateEntry, saveDockerState } = await import('../core/docker-handler.js');
|
|
295
|
+
const entry = buildDockerStateEntry(projectName, projectPath);
|
|
296
|
+
if (entry) {
|
|
297
|
+
await saveDockerState(syncDir, projectName, entry, publicKey, privateKey);
|
|
298
|
+
dockerServicesTracked = entry.services.length;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Docker tracking failure is non-fatal
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// 11. Process wizard answers — set mental context
|
|
306
|
+
if (wizardAnswers?.currentTask) {
|
|
307
|
+
try {
|
|
308
|
+
const { mergeContext } = await import('./note.js');
|
|
309
|
+
const existingMentalContext = await readState(syncDir, privateKey, 'mental-context');
|
|
310
|
+
const mcData = existingMentalContext ?? {};
|
|
311
|
+
const existing = mcData[projectName] ?? null;
|
|
312
|
+
const merged = mergeContext(existing, {
|
|
313
|
+
currentTask: wizardAnswers.currentTask,
|
|
314
|
+
nextSteps: wizardAnswers.nextSteps,
|
|
315
|
+
});
|
|
316
|
+
mcData[projectName] = merged;
|
|
317
|
+
await writeState(syncDir, mcData, publicKey, 'mental-context');
|
|
318
|
+
mentalContextSet = true;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Mental context failure is non-fatal
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// 12. Write encrypted state
|
|
325
|
+
await writeState(syncDir, state, publicKey, 'state');
|
|
326
|
+
// 13. Collect all files that need committing
|
|
327
|
+
const filesToCommit = [STATE_FILES.STATE, STATE_FILES.MANIFEST];
|
|
328
|
+
if (envVarsImported > 0) {
|
|
329
|
+
filesToCommit.push(STATE_FILES.ENV_VARS);
|
|
330
|
+
}
|
|
331
|
+
if (dockerServicesTracked > 0) {
|
|
332
|
+
filesToCommit.push(STATE_FILES.DOCKER_STATE);
|
|
333
|
+
}
|
|
334
|
+
if (mentalContextSet) {
|
|
335
|
+
filesToCommit.push(STATE_FILES.MENTAL_CONTEXT);
|
|
336
|
+
}
|
|
337
|
+
// 14. Optionally commit
|
|
338
|
+
if (!options.noSync && (wizardAnswers?.confirmCommit !== false)) {
|
|
339
|
+
await commitState(syncDir, filesToCommit, `feat: ${isNew ? 'track' : 'update'} project ${projectName}`);
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
project,
|
|
343
|
+
isNew,
|
|
344
|
+
envFileFound,
|
|
345
|
+
dockerComposeFound,
|
|
346
|
+
envVarsImported,
|
|
347
|
+
dockerServicesTracked,
|
|
348
|
+
mentalContextSet,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Register the `track` command on the given Commander program.
|
|
353
|
+
*/
|
|
354
|
+
export function registerTrackCommand(program) {
|
|
355
|
+
program
|
|
356
|
+
.command('track')
|
|
357
|
+
.description('Track the current project directory')
|
|
358
|
+
.option('-n, --name <name>', 'Project name (default: auto-detect from Git remote)')
|
|
359
|
+
.option('-p, --path <path>', 'Project path (default: current directory)')
|
|
360
|
+
.option('--no-sync', 'Skip syncing to Git after tracking')
|
|
361
|
+
.option('-y, --yes', 'Accept all defaults without prompting')
|
|
362
|
+
.option('--no-interactive', 'Skip all interactive prompts')
|
|
363
|
+
.action(withErrorHandler(async (opts) => {
|
|
364
|
+
const options = {
|
|
365
|
+
name: opts['name'],
|
|
366
|
+
path: opts['path'],
|
|
367
|
+
noSync: opts['sync'] === false,
|
|
368
|
+
yes: opts['yes'],
|
|
369
|
+
noInteractive: opts['interactive'] === false,
|
|
370
|
+
};
|
|
371
|
+
const result = await executeTrack(options);
|
|
372
|
+
const chalk = (await import('chalk')).default;
|
|
373
|
+
if (result.isNew) {
|
|
374
|
+
console.log(chalk.green('✅ Tracking project: ') + result.project.name);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
console.log(chalk.green('✅ Updated project: ') + result.project.name);
|
|
378
|
+
}
|
|
379
|
+
console.log(` Path: ${result.project.path}`);
|
|
380
|
+
console.log(` Branch: ${result.project.git.branch}`);
|
|
381
|
+
if (result.project.git.hasUncommitted) {
|
|
382
|
+
console.log(chalk.yellow(' ⚠ Uncommitted changes detected'));
|
|
383
|
+
}
|
|
384
|
+
if (result.project.git.stashCount > 0) {
|
|
385
|
+
console.log(chalk.yellow(` ⚠ ${result.project.git.stashCount} stash(es)`));
|
|
386
|
+
}
|
|
387
|
+
// Wizard results
|
|
388
|
+
if (result.envVarsImported > 0) {
|
|
389
|
+
console.log(chalk.green(` ✅ Imported ${result.envVarsImported} env vars (all encrypted)`));
|
|
390
|
+
}
|
|
391
|
+
else if (result.envFileFound) {
|
|
392
|
+
console.log(chalk.cyan(' ℹ .env file found — use `ctx-sync env import` to track environment variables'));
|
|
393
|
+
}
|
|
394
|
+
if (result.dockerServicesTracked > 0) {
|
|
395
|
+
console.log(chalk.green(` ✅ Tracking ${result.dockerServicesTracked} Docker service(s)`));
|
|
396
|
+
}
|
|
397
|
+
else if (result.dockerComposeFound) {
|
|
398
|
+
console.log(chalk.cyan(' ℹ Docker Compose found — use `ctx-sync docker track` to track services'));
|
|
399
|
+
}
|
|
400
|
+
if (result.mentalContextSet) {
|
|
401
|
+
console.log(chalk.green(' ✅ Mental context saved'));
|
|
402
|
+
}
|
|
403
|
+
console.log(chalk.dim('\n State encrypted and saved to state.age'));
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/commands/track.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command validator module.
|
|
3
|
+
*
|
|
4
|
+
* Validates shell commands for suspicious patterns before execution.
|
|
5
|
+
* Used by the `restore` command to prevent remote code execution (RCE)
|
|
6
|
+
* via a compromised Git repo injecting malicious commands into the
|
|
7
|
+
* encrypted state.
|
|
8
|
+
*
|
|
9
|
+
* **Critical security property:** No command is ever executed without
|
|
10
|
+
* explicit user confirmation. There is no `--yes` or `--no-confirm`
|
|
11
|
+
* flag — command confirmation cannot be bypassed.
|
|
12
|
+
*
|
|
13
|
+
* @module core/command-validator
|
|
14
|
+
*/
|
|
15
|
+
/** Result of command validation */
|
|
16
|
+
export interface ValidationResult {
|
|
17
|
+
/** Whether the command matches a suspicious pattern */
|
|
18
|
+
suspicious: boolean;
|
|
19
|
+
/** Human-readable reason why the command is suspicious (empty if safe) */
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
/** A command pending approval, with metadata for display */
|
|
23
|
+
export interface PendingCommand {
|
|
24
|
+
/** The shell command string */
|
|
25
|
+
command: string;
|
|
26
|
+
/** Display label for the command category (e.g. "Docker service", "Auto-start service") */
|
|
27
|
+
label: string;
|
|
28
|
+
/** Optional working directory */
|
|
29
|
+
cwd?: string;
|
|
30
|
+
/** Optional port */
|
|
31
|
+
port?: number;
|
|
32
|
+
/** Optional Docker image name */
|
|
33
|
+
image?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Result of presenting commands for approval */
|
|
36
|
+
export interface ApprovalResult {
|
|
37
|
+
/** Commands that were approved */
|
|
38
|
+
approved: PendingCommand[];
|
|
39
|
+
/** Commands that were rejected */
|
|
40
|
+
rejected: PendingCommand[];
|
|
41
|
+
/** Whether all commands were skipped (non-interactive mode) */
|
|
42
|
+
skippedAll: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validate a shell command for suspicious patterns.
|
|
46
|
+
*
|
|
47
|
+
* Checks the command string against known dangerous patterns. This is a
|
|
48
|
+
* defence-in-depth measure — the primary protection is the mandatory
|
|
49
|
+
* user confirmation before any command executes.
|
|
50
|
+
*
|
|
51
|
+
* @param cmd - The shell command string to validate.
|
|
52
|
+
* @returns Validation result with suspicious flag and reason.
|
|
53
|
+
*/
|
|
54
|
+
export declare function validateCommand(cmd: string): ValidationResult;
|
|
55
|
+
/**
|
|
56
|
+
* Validate a Docker image name for suspicious patterns.
|
|
57
|
+
*
|
|
58
|
+
* Flags images from unofficial registries or suspicious names.
|
|
59
|
+
* Trusted images come from well-known publishers (e.g. `postgres`,
|
|
60
|
+
* `redis`, `node`) without a registry prefix, or from trusted registries
|
|
61
|
+
* like `docker.io`.
|
|
62
|
+
*
|
|
63
|
+
* @param image - The Docker image name (e.g. 'postgres:15', 'evil.com/malware:latest').
|
|
64
|
+
* @returns Validation result with warning flag and reason.
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateDockerImage(image: string): ValidationResult;
|
|
67
|
+
/**
|
|
68
|
+
* Format commands for display to the user before execution.
|
|
69
|
+
*
|
|
70
|
+
* Groups commands by category (Docker services, auto-start services)
|
|
71
|
+
* and adds warning indicators for suspicious commands.
|
|
72
|
+
*
|
|
73
|
+
* @param commands - The list of commands pending approval.
|
|
74
|
+
* @returns Formatted string for terminal display.
|
|
75
|
+
*/
|
|
76
|
+
export declare function formatCommandsForDisplay(commands: PendingCommand[]): string;
|
|
77
|
+
/**
|
|
78
|
+
* Present commands for user approval.
|
|
79
|
+
*
|
|
80
|
+
* In interactive mode, displays commands and prompts the user to approve
|
|
81
|
+
* all, reject all, or select individually.
|
|
82
|
+
*
|
|
83
|
+
* In non-interactive mode, displays commands but does NOT execute any —
|
|
84
|
+
* this is the safe default.
|
|
85
|
+
*
|
|
86
|
+
* **Security:** There is no `--yes` or `--no-confirm` flag. Command
|
|
87
|
+
* confirmation cannot be bypassed programmatically.
|
|
88
|
+
*
|
|
89
|
+
* @param commands - The list of commands to present.
|
|
90
|
+
* @param options - Display options.
|
|
91
|
+
* @param options.interactive - Whether to prompt for approval (false = show only).
|
|
92
|
+
* @param options.promptFn - Optional override for the approval prompt (for testing).
|
|
93
|
+
* @returns The approval result (which commands were approved/rejected).
|
|
94
|
+
*/
|
|
95
|
+
export declare function presentCommandsForApproval(commands: PendingCommand[], options?: {
|
|
96
|
+
interactive?: boolean;
|
|
97
|
+
promptFn?: (commands: PendingCommand[]) => Promise<'all' | 'none' | 'select'>;
|
|
98
|
+
selectFn?: (cmd: PendingCommand, index: number) => Promise<boolean>;
|
|
99
|
+
}): Promise<ApprovalResult>;
|
|
100
|
+
//# sourceMappingURL=command-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-validator.d.ts","sourceRoot":"","sources":["../../src/core/command-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,kCAAkC;IAClC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;CACrB;AAiGD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAc7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAoCnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAsD3E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE,GACL,OAAO,CAAC,cAAc,CAAC,CAyDzB"}
|