@kodrunhq/claudefy 0.1.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/backup-manager/backup-manager.d.ts +6 -0
  4. package/dist/backup-manager/backup-manager.js +27 -0
  5. package/dist/backup-manager/backup-manager.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +296 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/config.d.ts +8 -0
  10. package/dist/commands/config.js +56 -0
  11. package/dist/commands/config.js.map +1 -0
  12. package/dist/commands/doctor.d.ts +14 -0
  13. package/dist/commands/doctor.js +64 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/hooks.d.ts +7 -0
  16. package/dist/commands/hooks.js +18 -0
  17. package/dist/commands/hooks.js.map +1 -0
  18. package/dist/commands/init.d.ts +13 -0
  19. package/dist/commands/init.js +66 -0
  20. package/dist/commands/init.js.map +1 -0
  21. package/dist/commands/join.d.ts +12 -0
  22. package/dist/commands/join.js +51 -0
  23. package/dist/commands/join.js.map +1 -0
  24. package/dist/commands/link.d.ts +12 -0
  25. package/dist/commands/link.js +34 -0
  26. package/dist/commands/link.js.map +1 -0
  27. package/dist/commands/machines.d.ts +6 -0
  28. package/dist/commands/machines.js +25 -0
  29. package/dist/commands/machines.js.map +1 -0
  30. package/dist/commands/override.d.ts +12 -0
  31. package/dist/commands/override.js +44 -0
  32. package/dist/commands/override.js.map +1 -0
  33. package/dist/commands/pull.d.ts +17 -0
  34. package/dist/commands/pull.js +220 -0
  35. package/dist/commands/pull.js.map +1 -0
  36. package/dist/commands/push.d.ts +14 -0
  37. package/dist/commands/push.js +175 -0
  38. package/dist/commands/push.js.map +1 -0
  39. package/dist/commands/status.d.ts +14 -0
  40. package/dist/commands/status.js +50 -0
  41. package/dist/commands/status.js.map +1 -0
  42. package/dist/config/config-manager.d.ts +22 -0
  43. package/dist/config/config-manager.js +118 -0
  44. package/dist/config/config-manager.js.map +1 -0
  45. package/dist/config/defaults.d.ts +7 -0
  46. package/dist/config/defaults.js +33 -0
  47. package/dist/config/defaults.js.map +1 -0
  48. package/dist/config/types.d.ts +25 -0
  49. package/dist/config/types.js +2 -0
  50. package/dist/config/types.js.map +1 -0
  51. package/dist/encryptor/encryptor.d.ts +10 -0
  52. package/dist/encryptor/encryptor.js +68 -0
  53. package/dist/encryptor/encryptor.js.map +1 -0
  54. package/dist/encryptor/passphrase.d.ts +7 -0
  55. package/dist/encryptor/passphrase.js +34 -0
  56. package/dist/encryptor/passphrase.js.map +1 -0
  57. package/dist/git-adapter/git-adapter.d.ts +19 -0
  58. package/dist/git-adapter/git-adapter.js +104 -0
  59. package/dist/git-adapter/git-adapter.js.map +1 -0
  60. package/dist/git-adapter/types.d.ts +14 -0
  61. package/dist/git-adapter/types.js +2 -0
  62. package/dist/git-adapter/types.js.map +1 -0
  63. package/dist/hook-manager/hook-manager.d.ts +11 -0
  64. package/dist/hook-manager/hook-manager.js +110 -0
  65. package/dist/hook-manager/hook-manager.js.map +1 -0
  66. package/dist/index.d.ts +2 -0
  67. package/dist/index.js +4 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/machine-registry/machine-registry.d.ts +16 -0
  70. package/dist/machine-registry/machine-registry.js +65 -0
  71. package/dist/machine-registry/machine-registry.js.map +1 -0
  72. package/dist/merger/merger.d.ts +10 -0
  73. package/dist/merger/merger.js +15 -0
  74. package/dist/merger/merger.js.map +1 -0
  75. package/dist/output.d.ts +8 -0
  76. package/dist/output.js +10 -0
  77. package/dist/output.js.map +1 -0
  78. package/dist/path-mapper/git-identity.d.ts +8 -0
  79. package/dist/path-mapper/git-identity.js +35 -0
  80. package/dist/path-mapper/git-identity.js.map +1 -0
  81. package/dist/path-mapper/path-mapper.d.ts +19 -0
  82. package/dist/path-mapper/path-mapper.js +101 -0
  83. package/dist/path-mapper/path-mapper.js.map +1 -0
  84. package/dist/repo-creator/repo-creator.d.ts +6 -0
  85. package/dist/repo-creator/repo-creator.js +52 -0
  86. package/dist/repo-creator/repo-creator.js.map +1 -0
  87. package/dist/secret-scanner/scanner.d.ts +10 -0
  88. package/dist/secret-scanner/scanner.js +48 -0
  89. package/dist/secret-scanner/scanner.js.map +1 -0
  90. package/dist/sync-filter/sync-filter.d.ts +8 -0
  91. package/dist/sync-filter/sync-filter.js +37 -0
  92. package/dist/sync-filter/sync-filter.js.map +1 -0
  93. package/dist/sync-filter/types.d.ts +13 -0
  94. package/dist/sync-filter/types.js +2 -0
  95. package/dist/sync-filter/types.js.map +1 -0
  96. package/package.json +72 -0
@@ -0,0 +1,220 @@
1
+ import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { ConfigManager } from "../config/config-manager.js";
5
+ import { GitAdapter } from "../git-adapter/git-adapter.js";
6
+ import { PathMapper } from "../path-mapper/path-mapper.js";
7
+ import { MachineRegistry } from "../machine-registry/machine-registry.js";
8
+ import { Encryptor } from "../encryptor/encryptor.js";
9
+ import { Merger } from "../merger/merger.js";
10
+ import { BackupManager } from "../backup-manager/backup-manager.js";
11
+ import { output } from "../output.js";
12
+ export class PullCommand {
13
+ homeDir;
14
+ claudeDir;
15
+ configManager;
16
+ constructor(homeDir) {
17
+ this.homeDir = homeDir;
18
+ this.claudeDir = join(homeDir, ".claude");
19
+ this.configManager = new ConfigManager(homeDir);
20
+ }
21
+ async execute(options) {
22
+ const config = await this.configManager.load();
23
+ const result = { overrideDetected: false, filesUpdated: 0 };
24
+ // 1. Initialize git adapter and pull
25
+ const claudefyDir = join(this.homeDir, ".claudefy");
26
+ const gitAdapter = new GitAdapter(claudefyDir);
27
+ await gitAdapter.initStore(config.backend.url);
28
+ try {
29
+ await gitAdapter.pull();
30
+ }
31
+ catch {
32
+ // Fresh store with no remote history yet
33
+ }
34
+ const storePath = gitAdapter.getStorePath();
35
+ const configDir = join(storePath, "config");
36
+ const unknownDir = join(storePath, "unknown");
37
+ const resolvedClaudeDir = resolve(this.claudeDir);
38
+ // 2. Check for override marker
39
+ const override = await gitAdapter.checkOverrideMarker();
40
+ if (override) {
41
+ result.overrideDetected = true;
42
+ if (!options.quiet) {
43
+ output.warn(`Override detected from machine: ${override.machine} at ${override.timestamp}`);
44
+ }
45
+ // Create backup before applying override (skip if .claude doesn't exist yet)
46
+ if (existsSync(this.claudeDir)) {
47
+ const backupManager = new BackupManager(claudefyDir);
48
+ result.backupPath = await backupManager.createBackup(this.claudeDir, "pre-override");
49
+ }
50
+ if (!options.quiet && result.backupPath) {
51
+ output.info(`Backup created at: ${result.backupPath}`);
52
+ }
53
+ // Remove override marker
54
+ await gitAdapter.removeOverrideMarker();
55
+ await gitAdapter.commitAndPush("pull: acknowledge override");
56
+ }
57
+ // 3. Decrypt .age files if encryption is enabled
58
+ if (config.encryption.enabled && !options.skipEncryption) {
59
+ if (!options.passphrase) {
60
+ throw new Error("Encryption is enabled but CLAUDEFY_PASSPHRASE env var is not set.");
61
+ }
62
+ const encryptor = new Encryptor(options.passphrase);
63
+ // Decrypt sensitive config files
64
+ const filesToDecrypt = ["settings.json.age", "history.jsonl.age"];
65
+ for (const fileName of filesToDecrypt) {
66
+ const filePath = join(configDir, fileName);
67
+ if (existsSync(filePath)) {
68
+ const outputPath = filePath.replace(/\.age$/, "");
69
+ await encryptor.decryptFile(filePath, outputPath);
70
+ await rm(filePath);
71
+ }
72
+ }
73
+ // Decrypt all .age files in unknown/
74
+ if (existsSync(unknownDir)) {
75
+ await encryptor.decryptDirectory(unknownDir);
76
+ }
77
+ }
78
+ // 4. Remap paths (canonical → local)
79
+ const links = await this.configManager.getLinks();
80
+ const pathMapper = new PathMapper(links);
81
+ // settings.json
82
+ const remoteSettingsPath = join(configDir, "settings.json");
83
+ if (existsSync(remoteSettingsPath)) {
84
+ const settings = JSON.parse(await readFile(remoteSettingsPath, "utf-8"));
85
+ const remapped = pathMapper.remapSettingsPaths(settings, this.claudeDir);
86
+ await writeFile(remoteSettingsPath, JSON.stringify(remapped, null, 2));
87
+ }
88
+ // installed_plugins.json
89
+ const pluginsJsonPath = join(configDir, "plugins", "installed_plugins.json");
90
+ if (existsSync(pluginsJsonPath)) {
91
+ const plugins = JSON.parse(await readFile(pluginsJsonPath, "utf-8"));
92
+ const remapped = pathMapper.remapPluginPaths(plugins, this.claudeDir);
93
+ await writeFile(pluginsJsonPath, JSON.stringify(remapped, null, 2));
94
+ }
95
+ // known_marketplaces.json
96
+ const marketplacesPath = join(configDir, "plugins", "known_marketplaces.json");
97
+ if (existsSync(marketplacesPath)) {
98
+ const mp = JSON.parse(await readFile(marketplacesPath, "utf-8"));
99
+ const remapped = pathMapper.remapPluginPaths(mp, this.claudeDir);
100
+ await writeFile(marketplacesPath, JSON.stringify(remapped, null, 2));
101
+ }
102
+ // history.jsonl
103
+ const historyPath = join(configDir, "history.jsonl");
104
+ if (existsSync(historyPath)) {
105
+ const content = await readFile(historyPath, "utf-8");
106
+ const remapped = content
107
+ .split("\n")
108
+ .filter(Boolean)
109
+ .map((line) => pathMapper.remapJsonlLine(line))
110
+ .join("\n") + "\n";
111
+ await writeFile(historyPath, remapped);
112
+ }
113
+ // projects/ directory renaming (canonical → local)
114
+ const projectsDir = join(configDir, "projects");
115
+ if (existsSync(projectsDir)) {
116
+ const projectDirs = await readdir(projectsDir);
117
+ for (const dirName of projectDirs) {
118
+ const localName = pathMapper.remapDirName(dirName);
119
+ if (localName) {
120
+ const destPath = resolve(join(projectsDir, localName));
121
+ // Path containment: ensure renamed dir stays within projects/
122
+ if (!destPath.startsWith(resolve(projectsDir) + "/")) {
123
+ console.warn(`Skipping directory rename "${dirName}" → "${localName}": path escapes projects directory`);
124
+ continue;
125
+ }
126
+ await rename(join(projectsDir, dirName), destPath);
127
+ }
128
+ }
129
+ }
130
+ // 5. Merge and copy to ~/.claude
131
+ const merger = new Merger();
132
+ await mkdir(this.claudeDir, { recursive: true });
133
+ // 5a. Deep merge settings.json
134
+ if (existsSync(remoteSettingsPath)) {
135
+ const localSettingsPath = join(this.claudeDir, "settings.json");
136
+ const remoteSettings = JSON.parse(await readFile(remoteSettingsPath, "utf-8"));
137
+ // Security: strip hooks from remote settings to prevent remote hook injection.
138
+ // Local hooks should never be overwritten by remote data, as an attacker with
139
+ // store access could inject arbitrary shell commands via SessionStart/SessionEnd hooks.
140
+ delete remoteSettings.hooks;
141
+ if (existsSync(localSettingsPath) && !result.overrideDetected) {
142
+ const localSettings = JSON.parse(await readFile(localSettingsPath, "utf-8"));
143
+ const merged = merger.deepMergeJson(localSettings, remoteSettings);
144
+ await writeFile(localSettingsPath, JSON.stringify(merged, null, 2));
145
+ }
146
+ else {
147
+ await writeFile(localSettingsPath, JSON.stringify(remoteSettings, null, 2));
148
+ }
149
+ result.filesUpdated++;
150
+ }
151
+ // 5b. Copy remaining config items (remote overwrites local)
152
+ if (existsSync(configDir)) {
153
+ const entries = await readdir(configDir, { withFileTypes: true });
154
+ for (const entry of entries) {
155
+ if (entry.name === "settings.json")
156
+ continue; // Already handled
157
+ // Security: skip symlinks to prevent path traversal attacks
158
+ if (entry.isSymbolicLink()) {
159
+ console.warn(`Skipping symlink in config store: ${entry.name}`);
160
+ continue;
161
+ }
162
+ const src = join(configDir, entry.name);
163
+ const dest = resolve(join(this.claudeDir, entry.name));
164
+ // Path containment: ensure destination stays within ~/.claude/
165
+ if (!dest.startsWith(resolvedClaudeDir + "/") && dest !== resolvedClaudeDir) {
166
+ console.warn(`Skipping "${entry.name}": resolved path escapes ~/.claude/`);
167
+ continue;
168
+ }
169
+ await cp(src, dest, { recursive: true, force: true });
170
+ result.filesUpdated++;
171
+ }
172
+ }
173
+ // 5c. Copy unknown items back
174
+ if (existsSync(unknownDir)) {
175
+ const entries = await readdir(unknownDir, { withFileTypes: true });
176
+ for (const entry of entries) {
177
+ // Security: skip symlinks to prevent path traversal attacks
178
+ if (entry.isSymbolicLink()) {
179
+ console.warn(`Skipping symlink in unknown store: ${entry.name}`);
180
+ continue;
181
+ }
182
+ const src = join(unknownDir, entry.name);
183
+ const dest = resolve(join(this.claudeDir, entry.name));
184
+ // Path containment: ensure destination stays within ~/.claude/
185
+ if (!dest.startsWith(resolvedClaudeDir + "/") && dest !== resolvedClaudeDir) {
186
+ console.warn(`Skipping "${entry.name}": resolved path escapes ~/.claude/`);
187
+ continue;
188
+ }
189
+ await cp(src, dest, { recursive: true, force: true });
190
+ result.filesUpdated++;
191
+ }
192
+ }
193
+ // 6. Re-encrypt decrypted files in the store before committing,
194
+ // so plaintext is never committed to git history.
195
+ if (config.encryption.enabled && !options.skipEncryption) {
196
+ const encryptor = new Encryptor(options.passphrase);
197
+ const filesToReencrypt = ["settings.json", "history.jsonl"];
198
+ for (const fileName of filesToReencrypt) {
199
+ const filePath = join(configDir, fileName);
200
+ if (existsSync(filePath)) {
201
+ await encryptor.encryptFile(filePath, filePath + ".age");
202
+ await rm(filePath);
203
+ }
204
+ }
205
+ // Re-encrypt all plaintext files in unknown/
206
+ if (existsSync(unknownDir)) {
207
+ await encryptor.encryptDirectory(unknownDir);
208
+ }
209
+ }
210
+ // 7. Update machine registry last sync time and commit
211
+ const registry = new MachineRegistry(join(storePath, "manifest.json"));
212
+ await registry.updateLastSync(config.machineId);
213
+ await gitAdapter.commitAndPush(`sync: pull on ${config.machineId}`);
214
+ if (!options.quiet) {
215
+ output.success(`Pull complete. ${result.filesUpdated} items updated.`);
216
+ }
217
+ return result;
218
+ }
219
+ }
220
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface PushOptions {
2
+ quiet: boolean;
3
+ skipEncryption?: boolean;
4
+ skipSecretScan?: boolean;
5
+ passphrase?: string;
6
+ }
7
+ export declare class PushCommand {
8
+ private homeDir;
9
+ private claudeDir;
10
+ private configManager;
11
+ constructor(homeDir: string);
12
+ execute(options: PushOptions): Promise<void>;
13
+ private collectFiles;
14
+ }
@@ -0,0 +1,175 @@
1
+ import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { hostname, platform } from "node:os";
5
+ import { ConfigManager } from "../config/config-manager.js";
6
+ import { SyncFilter } from "../sync-filter/sync-filter.js";
7
+ import { GitAdapter } from "../git-adapter/git-adapter.js";
8
+ import { PathMapper } from "../path-mapper/path-mapper.js";
9
+ import { MachineRegistry } from "../machine-registry/machine-registry.js";
10
+ import { Encryptor } from "../encryptor/encryptor.js";
11
+ import { SecretScanner } from "../secret-scanner/scanner.js";
12
+ import { output } from "../output.js";
13
+ export class PushCommand {
14
+ homeDir;
15
+ claudeDir;
16
+ configManager;
17
+ constructor(homeDir) {
18
+ this.homeDir = homeDir;
19
+ this.claudeDir = join(homeDir, ".claude");
20
+ this.configManager = new ConfigManager(homeDir);
21
+ }
22
+ async execute(options) {
23
+ const config = await this.configManager.load();
24
+ const syncFilterConfig = await this.configManager.getSyncFilter();
25
+ const syncFilter = new SyncFilter(syncFilterConfig);
26
+ // 1. Classify ~/.claude contents
27
+ if (!existsSync(this.claudeDir)) {
28
+ throw new Error(`No ${this.claudeDir} directory found. Nothing to push.`);
29
+ }
30
+ const classification = await syncFilter.classify(this.claudeDir);
31
+ const willEncrypt = config.encryption.enabled && !options.skipEncryption;
32
+ if (!options.quiet) {
33
+ const unknownLabel = willEncrypt ? "unknown (encrypted)" : "unknown";
34
+ output.info(`Syncing: ${classification.allowlist.length} allowed, ` +
35
+ `${classification.unknown.length} ${unknownLabel}, ` +
36
+ `${classification.denylist.length} denied`);
37
+ }
38
+ // 2. Initialize git adapter
39
+ const gitAdapter = new GitAdapter(join(this.homeDir, ".claudefy"));
40
+ await gitAdapter.initStore(config.backend.url);
41
+ try {
42
+ await gitAdapter.pull();
43
+ }
44
+ catch {
45
+ // Fresh store with no remote history yet — safe to continue
46
+ }
47
+ const storePath = gitAdapter.getStorePath();
48
+ const configDir = join(storePath, "config");
49
+ const unknownDir = join(storePath, "unknown");
50
+ // 3. Clean existing config and unknown dirs in store
51
+ if (existsSync(configDir)) {
52
+ await rm(configDir, { recursive: true });
53
+ }
54
+ if (existsSync(unknownDir)) {
55
+ await rm(unknownDir, { recursive: true });
56
+ }
57
+ await mkdir(configDir, { recursive: true });
58
+ await mkdir(unknownDir, { recursive: true });
59
+ // 4. Copy allowlisted items
60
+ for (const item of classification.allowlist) {
61
+ const src = join(this.claudeDir, item.name);
62
+ const dest = join(configDir, item.name);
63
+ await cp(src, dest, { recursive: true });
64
+ }
65
+ // 5. Copy unknown items
66
+ for (const item of classification.unknown) {
67
+ const src = join(this.claudeDir, item.name);
68
+ const dest = join(unknownDir, item.name);
69
+ await cp(src, dest, { recursive: true });
70
+ }
71
+ // 6. Normalize paths in known files
72
+ const links = await this.configManager.getLinks();
73
+ const pathMapper = new PathMapper(links);
74
+ // settings.json
75
+ const settingsPath = join(configDir, "settings.json");
76
+ if (existsSync(settingsPath)) {
77
+ const settings = JSON.parse(await readFile(settingsPath, "utf-8"));
78
+ const normalized = pathMapper.normalizeSettingsPaths(settings, this.claudeDir);
79
+ await writeFile(settingsPath, JSON.stringify(normalized, null, 2));
80
+ }
81
+ // installed_plugins.json
82
+ const pluginsJsonPath = join(configDir, "plugins", "installed_plugins.json");
83
+ if (existsSync(pluginsJsonPath)) {
84
+ const plugins = JSON.parse(await readFile(pluginsJsonPath, "utf-8"));
85
+ const normalized = pathMapper.normalizePluginPaths(plugins, this.claudeDir);
86
+ await writeFile(pluginsJsonPath, JSON.stringify(normalized, null, 2));
87
+ }
88
+ // known_marketplaces.json
89
+ const marketplacesPath = join(configDir, "plugins", "known_marketplaces.json");
90
+ if (existsSync(marketplacesPath)) {
91
+ const mp = JSON.parse(await readFile(marketplacesPath, "utf-8"));
92
+ const normalized = pathMapper.normalizePluginPaths(mp, this.claudeDir);
93
+ await writeFile(marketplacesPath, JSON.stringify(normalized, null, 2));
94
+ }
95
+ // history.jsonl
96
+ const historyPath = join(configDir, "history.jsonl");
97
+ if (existsSync(historyPath)) {
98
+ const content = await readFile(historyPath, "utf-8");
99
+ const normalized = content
100
+ .split("\n")
101
+ .filter(Boolean)
102
+ .map((line) => pathMapper.normalizeJsonlLine(line))
103
+ .join("\n") + "\n";
104
+ await writeFile(historyPath, normalized);
105
+ }
106
+ // projects/ directory renaming
107
+ const projectsDir = join(configDir, "projects");
108
+ if (existsSync(projectsDir)) {
109
+ const projectDirs = await readdir(projectsDir);
110
+ for (const dirName of projectDirs) {
111
+ const canonicalId = pathMapper.normalizeDirName(dirName);
112
+ if (canonicalId) {
113
+ await rename(join(projectsDir, dirName), join(projectsDir, canonicalId));
114
+ }
115
+ }
116
+ }
117
+ // 7. Scan for secrets before committing
118
+ if (!options.skipSecretScan) {
119
+ const scanner = new SecretScanner();
120
+ const filesToScan = await this.collectFiles(configDir);
121
+ const unknownFiles = await this.collectFiles(unknownDir);
122
+ filesToScan.push(...unknownFiles);
123
+ const findings = await scanner.scanFiles(filesToScan);
124
+ if (findings.length > 0) {
125
+ const details = findings.map((f) => ` ${f.file}:${f.line} [${f.pattern}]`).join("\n");
126
+ throw new Error(`Secret scan detected ${findings.length} potential secret(s):\n${details}\n\nAborting push. Use --skip-secret-scan to bypass scanning.`);
127
+ }
128
+ }
129
+ // 8. Encrypt files if encryption is enabled
130
+ if (config.encryption.enabled && !options.skipEncryption) {
131
+ if (!options.passphrase) {
132
+ throw new Error("Encryption is enabled but CLAUDEFY_PASSPHRASE env var is not set.");
133
+ }
134
+ const encryptor = new Encryptor(options.passphrase);
135
+ // Encrypt sensitive config files
136
+ const filesToEncrypt = ["settings.json", "history.jsonl"];
137
+ for (const fileName of filesToEncrypt) {
138
+ const filePath = join(configDir, fileName);
139
+ if (existsSync(filePath)) {
140
+ await encryptor.encryptFile(filePath, filePath + ".age");
141
+ await rm(filePath);
142
+ }
143
+ }
144
+ // Encrypt all files in unknown/
145
+ if (existsSync(unknownDir)) {
146
+ await encryptor.encryptDirectory(unknownDir);
147
+ }
148
+ }
149
+ // 9. Update machine registry
150
+ const registry = new MachineRegistry(join(storePath, "manifest.json"));
151
+ await registry.register(config.machineId, hostname(), platform());
152
+ // 10. Commit and push
153
+ await gitAdapter.commitAndPush(`sync: push from ${config.machineId}`);
154
+ if (!options.quiet) {
155
+ output.success("Push complete.");
156
+ }
157
+ }
158
+ async collectFiles(dirPath) {
159
+ if (!existsSync(dirPath))
160
+ return [];
161
+ const results = [];
162
+ const entries = await readdir(dirPath, { withFileTypes: true });
163
+ for (const entry of entries) {
164
+ const fullPath = join(dirPath, entry.name);
165
+ if (entry.isDirectory()) {
166
+ results.push(...(await this.collectFiles(fullPath)));
167
+ }
168
+ else {
169
+ results.push(fullPath);
170
+ }
171
+ }
172
+ return results;
173
+ }
174
+ }
175
+ //# 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,14 @@
1
+ export interface StatusResult {
2
+ initialized: boolean;
3
+ machineId?: string;
4
+ backendUrl?: string;
5
+ localFiles: string[];
6
+ syncedFiles: string[];
7
+ deniedFiles: string[];
8
+ unknownFiles: string[];
9
+ }
10
+ export declare class StatusCommand {
11
+ private homeDir;
12
+ constructor(homeDir: string);
13
+ execute(): Promise<StatusResult>;
14
+ }
@@ -0,0 +1,50 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { ConfigManager } from "../config/config-manager.js";
5
+ import { SyncFilter } from "../sync-filter/sync-filter.js";
6
+ export class StatusCommand {
7
+ homeDir;
8
+ constructor(homeDir) {
9
+ this.homeDir = homeDir;
10
+ }
11
+ async execute() {
12
+ const configManager = new ConfigManager(this.homeDir);
13
+ if (!configManager.isInitialized()) {
14
+ return {
15
+ initialized: false,
16
+ localFiles: [],
17
+ syncedFiles: [],
18
+ deniedFiles: [],
19
+ unknownFiles: [],
20
+ };
21
+ }
22
+ const config = await configManager.load();
23
+ const claudeDir = join(this.homeDir, ".claude");
24
+ if (!existsSync(claudeDir)) {
25
+ return {
26
+ initialized: true,
27
+ machineId: config.machineId,
28
+ backendUrl: config.backend.url,
29
+ localFiles: [],
30
+ syncedFiles: [],
31
+ deniedFiles: [],
32
+ unknownFiles: [],
33
+ };
34
+ }
35
+ const syncFilterConfig = await configManager.getSyncFilter();
36
+ const syncFilter = new SyncFilter(syncFilterConfig);
37
+ const localFiles = await readdir(claudeDir);
38
+ const classification = await syncFilter.classify(claudeDir);
39
+ return {
40
+ initialized: true,
41
+ machineId: config.machineId,
42
+ backendUrl: config.backend.url,
43
+ localFiles,
44
+ syncedFiles: classification.allowlist.map((i) => i.name),
45
+ deniedFiles: classification.denylist.map((i) => i.name),
46
+ unknownFiles: classification.unknown.map((i) => i.name),
47
+ };
48
+ }
49
+ }
50
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAY3D,MAAM,OAAO,aAAa;IAChB,OAAO,CAAS;IAExB,YAAY,OAAe;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEtD,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC;YACnC,OAAO;gBACL,WAAW,EAAE,KAAK;gBAClB,UAAU,EAAE,EAAE;gBACd,WAAW,EAAE,EAAE;gBACf,WAAW,EAAE,EAAE;gBACf,YAAY,EAAE,EAAE;aACjB,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEhD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,WAAW,EAAE,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG;gBAC9B,UAAU,EAAE,EAAE;gBACd,WAAW,EAAE,EAAE;gBACf,WAAW,EAAE,EAAE;gBACf,YAAY,EAAE,EAAE;aACjB,CAAC;QACJ,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,CAAC;QAC7D,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAE5D,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG;YAC9B,UAAU;YACV,WAAW,EAAE,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACxD,WAAW,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACvD,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SACxD,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import type { ClaudefyConfig, LinksConfig, SyncFilterConfig } from "./types.js";
2
+ export declare class ConfigManager {
3
+ private baseDir;
4
+ private configDir;
5
+ constructor(homeDir: string);
6
+ initialize(backendUrl: string): Promise<ClaudefyConfig>;
7
+ load(): Promise<ClaudefyConfig>;
8
+ set(key: string, value: unknown): Promise<void>;
9
+ addLink(alias: string, localPath: string, meta: {
10
+ canonicalId: string;
11
+ gitRemote: string | null;
12
+ }): Promise<void>;
13
+ removeLink(alias: string): Promise<void>;
14
+ getLinks(): Promise<LinksConfig>;
15
+ getSyncFilter(): Promise<SyncFilterConfig>;
16
+ setFilterOverride(name: string, tier: "allow" | "deny"): Promise<void>;
17
+ isInitialized(): boolean;
18
+ getConfigDir(): string;
19
+ private saveConfig;
20
+ private saveLinks;
21
+ private saveSyncFilter;
22
+ }
@@ -0,0 +1,118 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { randomUUID } from "node:crypto";
5
+ import { hostname } from "node:os";
6
+ import { CLAUDEFY_DIR, CONFIG_FILE, LINKS_FILE, SYNC_FILTER_FILE, MACHINE_ID_FILE, DEFAULT_SYNC_FILTER, } from "./defaults.js";
7
+ export class ConfigManager {
8
+ baseDir;
9
+ configDir;
10
+ constructor(homeDir) {
11
+ this.baseDir = homeDir;
12
+ this.configDir = join(homeDir, CLAUDEFY_DIR);
13
+ }
14
+ async initialize(backendUrl) {
15
+ if (this.isInitialized()) {
16
+ throw new Error("claudefy is already initialized. Use 'load()' to read existing config.");
17
+ }
18
+ await mkdir(this.configDir, { recursive: true });
19
+ await mkdir(join(this.configDir, "backups"), { recursive: true });
20
+ const machineId = `${hostname()}-${randomUUID().slice(0, 8)}`.toLowerCase();
21
+ await writeFile(join(this.configDir, MACHINE_ID_FILE), machineId);
22
+ const config = {
23
+ version: 1,
24
+ backend: { type: "git", url: backendUrl },
25
+ encryption: {
26
+ enabled: true,
27
+ useKeychain: false,
28
+ cacheDuration: "0",
29
+ },
30
+ machineId,
31
+ };
32
+ await this.saveConfig(config);
33
+ await this.saveLinks({});
34
+ await this.saveSyncFilter({ ...DEFAULT_SYNC_FILTER });
35
+ return config;
36
+ }
37
+ async load() {
38
+ const raw = await readFile(join(this.configDir, CONFIG_FILE), "utf-8");
39
+ return JSON.parse(raw);
40
+ }
41
+ async set(key, value) {
42
+ const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
43
+ const parts = key.split(".");
44
+ for (const part of parts) {
45
+ if (FORBIDDEN_KEYS.includes(part)) {
46
+ throw new Error(`Forbidden config key segment: "${part}"`);
47
+ }
48
+ }
49
+ const config = await this.load();
50
+ let obj = config;
51
+ for (let i = 0; i < parts.length - 1; i++) {
52
+ const next = obj[parts[i]];
53
+ if (next === undefined || next === null || typeof next !== "object") {
54
+ throw new Error(`Invalid config key: "${key}" — "${parts[i]}" is not an object`);
55
+ }
56
+ obj = next;
57
+ }
58
+ obj[parts[parts.length - 1]] = value;
59
+ await this.saveConfig(config);
60
+ }
61
+ async addLink(alias, localPath, meta) {
62
+ const links = await this.getLinks();
63
+ links[alias] = {
64
+ localPath,
65
+ canonicalId: meta.canonicalId,
66
+ gitRemote: meta.gitRemote,
67
+ detectedAt: new Date().toISOString(),
68
+ };
69
+ await this.saveLinks(links);
70
+ }
71
+ async removeLink(alias) {
72
+ const links = await this.getLinks();
73
+ delete links[alias];
74
+ await this.saveLinks(links);
75
+ }
76
+ async getLinks() {
77
+ const path = join(this.configDir, LINKS_FILE);
78
+ if (!existsSync(path))
79
+ return {};
80
+ const raw = await readFile(path, "utf-8");
81
+ return JSON.parse(raw);
82
+ }
83
+ async getSyncFilter() {
84
+ const path = join(this.configDir, SYNC_FILTER_FILE);
85
+ if (!existsSync(path))
86
+ return { ...DEFAULT_SYNC_FILTER };
87
+ const raw = await readFile(path, "utf-8");
88
+ return JSON.parse(raw);
89
+ }
90
+ async setFilterOverride(name, tier) {
91
+ const filter = await this.getSyncFilter();
92
+ filter.denylist = filter.denylist.filter((d) => d !== name);
93
+ filter.allowlist = filter.allowlist.filter((a) => a !== name);
94
+ if (tier === "allow") {
95
+ filter.allowlist.push(name);
96
+ }
97
+ else {
98
+ filter.denylist.push(name);
99
+ }
100
+ await this.saveSyncFilter(filter);
101
+ }
102
+ isInitialized() {
103
+ return existsSync(join(this.configDir, CONFIG_FILE));
104
+ }
105
+ getConfigDir() {
106
+ return this.configDir;
107
+ }
108
+ async saveConfig(config) {
109
+ await writeFile(join(this.configDir, CONFIG_FILE), JSON.stringify(config, null, 2));
110
+ }
111
+ async saveLinks(links) {
112
+ await writeFile(join(this.configDir, LINKS_FILE), JSON.stringify(links, null, 2));
113
+ }
114
+ async saveSyncFilter(filter) {
115
+ await writeFile(join(this.configDir, SYNC_FILTER_FILE), JSON.stringify(filter, null, 2));
116
+ }
117
+ }
118
+ //# sourceMappingURL=config-manager.js.map