claude-settings-sync 0.3.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/LICENSE +21 -0
- package/README.ko.md +203 -0
- package/README.md +203 -0
- package/bin/claudesync.js +2 -0
- package/dist/chunk-45GTBXRR.js +145 -0
- package/dist/chunk-45GTBXRR.js.map +1 -0
- package/dist/chunk-BRTRPVT7.js +101 -0
- package/dist/chunk-BRTRPVT7.js.map +1 -0
- package/dist/chunk-VBOSEAEH.js +81 -0
- package/dist/chunk-VBOSEAEH.js.map +1 -0
- package/dist/chunk-XTJEVOK3.js +867 -0
- package/dist/chunk-XTJEVOK3.js.map +1 -0
- package/dist/cli.js +1693 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-7KJ6CZMX.js +33 -0
- package/dist/config-7KJ6CZMX.js.map +1 -0
- package/dist/gist-AMKOY723.js +21 -0
- package/dist/gist-AMKOY723.js.map +1 -0
- package/dist/scanner-M3SQGNTI.js +11 -0
- package/dist/scanner-M3SQGNTI.js.map +1 -0
- package/package.json +42 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1693 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CATEGORIES,
|
|
4
|
+
createGist,
|
|
5
|
+
findGist,
|
|
6
|
+
getGist,
|
|
7
|
+
getGistAtRevision,
|
|
8
|
+
getHistory,
|
|
9
|
+
parseMeta,
|
|
10
|
+
updateGist
|
|
11
|
+
} from "./chunk-45GTBXRR.js";
|
|
12
|
+
import {
|
|
13
|
+
scanFiles
|
|
14
|
+
} from "./chunk-BRTRPVT7.js";
|
|
15
|
+
import {
|
|
16
|
+
ask,
|
|
17
|
+
askHidden,
|
|
18
|
+
c,
|
|
19
|
+
confirm,
|
|
20
|
+
detectLang,
|
|
21
|
+
error,
|
|
22
|
+
heading,
|
|
23
|
+
info,
|
|
24
|
+
loadLangConfig,
|
|
25
|
+
printDiff,
|
|
26
|
+
select,
|
|
27
|
+
setLang,
|
|
28
|
+
success,
|
|
29
|
+
t,
|
|
30
|
+
warn
|
|
31
|
+
} from "./chunk-XTJEVOK3.js";
|
|
32
|
+
import {
|
|
33
|
+
authFilePath,
|
|
34
|
+
autoConfigPath,
|
|
35
|
+
autoLogPath,
|
|
36
|
+
claudeDir,
|
|
37
|
+
fromGistFilename,
|
|
38
|
+
getMachineId,
|
|
39
|
+
isPathSafe,
|
|
40
|
+
lockFilePath,
|
|
41
|
+
machineName,
|
|
42
|
+
pendingNotificationsPath,
|
|
43
|
+
platformString
|
|
44
|
+
} from "./chunk-VBOSEAEH.js";
|
|
45
|
+
|
|
46
|
+
// src/core/auth.ts
|
|
47
|
+
import { readFileSync, writeFileSync, chmodSync, existsSync } from "fs";
|
|
48
|
+
import { execSync } from "child_process";
|
|
49
|
+
var KEYCHAIN_SERVICE = "claudesync";
|
|
50
|
+
var KEYCHAIN_ACCOUNT = "github-token";
|
|
51
|
+
function keychainSet(token) {
|
|
52
|
+
try {
|
|
53
|
+
execSync(
|
|
54
|
+
`security add-generic-password -U -s "${KEYCHAIN_SERVICE}" -a "${KEYCHAIN_ACCOUNT}" -w "${token}"`,
|
|
55
|
+
{ stdio: "ignore" }
|
|
56
|
+
);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function secretToolSet(token) {
|
|
63
|
+
try {
|
|
64
|
+
execSync(
|
|
65
|
+
`echo -n "${token}" | secret-tool store --label="claudesync" service claudesync account github-token`,
|
|
66
|
+
{ stdio: "ignore" }
|
|
67
|
+
);
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function fileSet(config) {
|
|
74
|
+
const path = authFilePath();
|
|
75
|
+
writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
|
|
76
|
+
chmodSync(path, 384);
|
|
77
|
+
}
|
|
78
|
+
function fileGet() {
|
|
79
|
+
const path = authFilePath();
|
|
80
|
+
if (!existsSync(path)) return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function saveToken(token) {
|
|
88
|
+
if (process.platform === "darwin") {
|
|
89
|
+
keychainSet(token);
|
|
90
|
+
} else if (process.platform === "linux") {
|
|
91
|
+
secretToolSet(token);
|
|
92
|
+
}
|
|
93
|
+
const existing = fileGet() || { token: "" };
|
|
94
|
+
existing.token = token;
|
|
95
|
+
fileSet(existing);
|
|
96
|
+
}
|
|
97
|
+
function loadConfig() {
|
|
98
|
+
return fileGet();
|
|
99
|
+
}
|
|
100
|
+
function saveConfig(config) {
|
|
101
|
+
saveToken(config.token);
|
|
102
|
+
fileSet(config);
|
|
103
|
+
}
|
|
104
|
+
async function validateToken(token) {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch("https://api.github.com/user", {
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `token ${token}`,
|
|
109
|
+
"User-Agent": "claudesync"
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return res.ok;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function deviceFlow() {
|
|
118
|
+
const CLIENT_ID = "Ov23liVJuAxxwWUqtala";
|
|
119
|
+
const codeRes = await fetch("https://github.com/login/device/code", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
Accept: "application/json",
|
|
123
|
+
"Content-Type": "application/json"
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({ client_id: CLIENT_ID, scope: "gist" })
|
|
126
|
+
});
|
|
127
|
+
const codeData = await codeRes.json();
|
|
128
|
+
console.log(`
|
|
129
|
+
${t("auth.device_url").replace("{url}", codeData.verification_uri)}`);
|
|
130
|
+
console.log(`${t("auth.device_code").replace("{code}", codeData.user_code)}
|
|
131
|
+
`);
|
|
132
|
+
const interval = (codeData.interval || 5) * 1e3;
|
|
133
|
+
const deadline = Date.now() + codeData.expires_in * 1e3;
|
|
134
|
+
while (Date.now() < deadline) {
|
|
135
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
136
|
+
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
Accept: "application/json",
|
|
140
|
+
"Content-Type": "application/json"
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify({
|
|
143
|
+
client_id: CLIENT_ID,
|
|
144
|
+
device_code: codeData.device_code,
|
|
145
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
const tokenData = await tokenRes.json();
|
|
149
|
+
if (tokenData.access_token) return tokenData.access_token;
|
|
150
|
+
if (tokenData.error === "authorization_pending") continue;
|
|
151
|
+
if (tokenData.error === "slow_down") {
|
|
152
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
throw new Error(t("auth.oauth_error").replace("{error}", String(tokenData.error)));
|
|
156
|
+
}
|
|
157
|
+
throw new Error(t("auth.device_flow_timeout"));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/commands/init.ts
|
|
161
|
+
async function runInit(options) {
|
|
162
|
+
let token;
|
|
163
|
+
if (options.useToken) {
|
|
164
|
+
token = await askHidden(t("auth.enter_token"));
|
|
165
|
+
if (!token.trim()) {
|
|
166
|
+
error(t("init.no_token_input"));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
token = token.trim();
|
|
170
|
+
} else if (options.linkGistId) {
|
|
171
|
+
const existing = loadConfig();
|
|
172
|
+
if (!existing?.token) {
|
|
173
|
+
error(t("auth.no_token"));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
token = existing.token;
|
|
177
|
+
saveConfig({
|
|
178
|
+
token,
|
|
179
|
+
gist_id: options.linkGistId,
|
|
180
|
+
machine_name: machineName()
|
|
181
|
+
});
|
|
182
|
+
success(t("init.gist_linked").replace("{id}", options.linkGistId));
|
|
183
|
+
return;
|
|
184
|
+
} else {
|
|
185
|
+
info(t("auth.device_prompt"));
|
|
186
|
+
try {
|
|
187
|
+
token = await deviceFlow();
|
|
188
|
+
} catch (err) {
|
|
189
|
+
error(err instanceof Error ? err.message : String(err));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
info(t("init.validating"));
|
|
194
|
+
const valid = await validateToken(token);
|
|
195
|
+
if (!valid) {
|
|
196
|
+
error(t("init.token_invalid"));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
info(t("init.searching_gist"));
|
|
200
|
+
const existingGist = await findGist(token);
|
|
201
|
+
const config = {
|
|
202
|
+
token,
|
|
203
|
+
gist_id: existingGist?.id,
|
|
204
|
+
machine_name: machineName()
|
|
205
|
+
};
|
|
206
|
+
saveConfig(config);
|
|
207
|
+
success(t("auth.token_saved"));
|
|
208
|
+
if (existingGist) {
|
|
209
|
+
success(t("init.gist_found").replace("{id}", existingGist.id));
|
|
210
|
+
} else {
|
|
211
|
+
warn(t("init.no_gist"));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/core/conflict.ts
|
|
216
|
+
var META_FILE = "_meta.json";
|
|
217
|
+
function compareForPush(localFiles, gist) {
|
|
218
|
+
const changes = [];
|
|
219
|
+
const localMap = new Map(localFiles.map((f) => [f.gistFilename, f]));
|
|
220
|
+
const remoteNames = new Set(
|
|
221
|
+
Object.keys(gist.files).filter((n) => n !== META_FILE)
|
|
222
|
+
);
|
|
223
|
+
for (const [gistName, local] of localMap) {
|
|
224
|
+
const remote = gist.files[gistName];
|
|
225
|
+
if (!remote) {
|
|
226
|
+
changes.push({
|
|
227
|
+
gistFilename: gistName,
|
|
228
|
+
relativePath: local.relativePath,
|
|
229
|
+
category: local.category,
|
|
230
|
+
status: "added",
|
|
231
|
+
localContent: local.content
|
|
232
|
+
});
|
|
233
|
+
} else if (remote.content !== local.content) {
|
|
234
|
+
changes.push({
|
|
235
|
+
gistFilename: gistName,
|
|
236
|
+
relativePath: local.relativePath,
|
|
237
|
+
category: local.category,
|
|
238
|
+
status: "modified",
|
|
239
|
+
localContent: local.content,
|
|
240
|
+
remoteContent: remote.content ?? void 0
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
changes.push({
|
|
244
|
+
gistFilename: gistName,
|
|
245
|
+
relativePath: local.relativePath,
|
|
246
|
+
category: local.category,
|
|
247
|
+
status: "unchanged",
|
|
248
|
+
localContent: local.content,
|
|
249
|
+
remoteContent: remote.content ?? void 0
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
remoteNames.delete(gistName);
|
|
253
|
+
}
|
|
254
|
+
const meta = parseMeta(gist);
|
|
255
|
+
for (const gistName of remoteNames) {
|
|
256
|
+
const entry = meta?.file_map[gistName];
|
|
257
|
+
changes.push({
|
|
258
|
+
gistFilename: gistName,
|
|
259
|
+
relativePath: entry?.path ?? fromGistFilename(gistName),
|
|
260
|
+
category: entry?.category ?? "settings",
|
|
261
|
+
status: "deleted",
|
|
262
|
+
remoteContent: gist.files[gistName]?.content ?? void 0
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return changes;
|
|
266
|
+
}
|
|
267
|
+
function compareForPull(gist, localFiles) {
|
|
268
|
+
const changes = [];
|
|
269
|
+
const localMap = new Map(localFiles.map((f) => [f.gistFilename, f]));
|
|
270
|
+
const meta = parseMeta(gist);
|
|
271
|
+
for (const [gistName, gistFile] of Object.entries(gist.files)) {
|
|
272
|
+
if (gistName === META_FILE) continue;
|
|
273
|
+
if (!gistFile?.content) continue;
|
|
274
|
+
const local = localMap.get(gistName);
|
|
275
|
+
const entry = meta?.file_map[gistName];
|
|
276
|
+
const relativePath = entry?.path ?? fromGistFilename(gistName);
|
|
277
|
+
const category = entry?.category ?? "settings";
|
|
278
|
+
if (!local) {
|
|
279
|
+
changes.push({
|
|
280
|
+
gistFilename: gistName,
|
|
281
|
+
relativePath,
|
|
282
|
+
category,
|
|
283
|
+
status: "added",
|
|
284
|
+
remoteContent: gistFile.content
|
|
285
|
+
});
|
|
286
|
+
} else if (local.content !== gistFile.content) {
|
|
287
|
+
changes.push({
|
|
288
|
+
gistFilename: gistName,
|
|
289
|
+
relativePath,
|
|
290
|
+
category,
|
|
291
|
+
status: "modified",
|
|
292
|
+
localContent: local.content,
|
|
293
|
+
remoteContent: gistFile.content
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
changes.push({
|
|
297
|
+
gistFilename: gistName,
|
|
298
|
+
relativePath,
|
|
299
|
+
category,
|
|
300
|
+
status: "unchanged",
|
|
301
|
+
localContent: local.content,
|
|
302
|
+
remoteContent: gistFile.content
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return changes;
|
|
307
|
+
}
|
|
308
|
+
function simpleDiff(a, b) {
|
|
309
|
+
const linesA = a.split("\n");
|
|
310
|
+
const linesB = b.split("\n");
|
|
311
|
+
const result = [];
|
|
312
|
+
const maxLen = Math.max(linesA.length, linesB.length);
|
|
313
|
+
for (let i = 0; i < maxLen; i++) {
|
|
314
|
+
const lineA = linesA[i];
|
|
315
|
+
const lineB = linesB[i];
|
|
316
|
+
if (lineA === void 0) {
|
|
317
|
+
result.push(`+${lineB}`);
|
|
318
|
+
} else if (lineB === void 0) {
|
|
319
|
+
result.push(`-${lineA}`);
|
|
320
|
+
} else if (lineA !== lineB) {
|
|
321
|
+
result.push(`-${lineA}`);
|
|
322
|
+
result.push(`+${lineB}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/core/redactor.ts
|
|
329
|
+
var SECRET_PATTERNS = [
|
|
330
|
+
{ name: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/ },
|
|
331
|
+
{ name: "AWS Secret Key", regex: /(?:aws_secret_access_key|secret_key)\s*[=:]\s*\S+/i },
|
|
332
|
+
{ name: "GitHub Token", regex: /gh[pousr]_[A-Za-z0-9_]{36,}/ },
|
|
333
|
+
{ name: "GitHub Classic Token", regex: /ghp_[A-Za-z0-9]{36,}/ },
|
|
334
|
+
{ name: "Anthropic API Key", regex: /sk-ant-[A-Za-z0-9-_]{20,}/ },
|
|
335
|
+
{ name: "OpenAI API Key", regex: /sk-[A-Za-z0-9]{32,}/ },
|
|
336
|
+
{ name: "Slack Token", regex: /xox[bprs]-[A-Za-z0-9-]+/ },
|
|
337
|
+
{ name: "Slack Webhook", regex: /hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/ },
|
|
338
|
+
{ name: "Private Key", regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/ },
|
|
339
|
+
{ name: "Generic Secret", regex: /(?:secret|password|passwd|token|api_key|apikey|auth)\s*[=:]\s*['"][^'"]{8,}['"]/i },
|
|
340
|
+
{ name: "Bearer Token", regex: /Bearer\s+[A-Za-z0-9\-._~+/]+=*/i },
|
|
341
|
+
{ name: "Basic Auth", regex: /Basic\s+[A-Za-z0-9+/]+=*/i },
|
|
342
|
+
{ name: "Connection String", regex: /(?:mongodb|postgres|mysql|redis):\/\/[^\s]+/i },
|
|
343
|
+
{ name: "npm Token", regex: /\/\/registry\.npmjs\.org\/:_authToken=\S+/ },
|
|
344
|
+
{ name: "Hex Secret (32+)", regex: /(?:secret|key|token|password)\s*[=:]\s*[0-9a-f]{32,}/i }
|
|
345
|
+
];
|
|
346
|
+
var REDACT_COMMENT = "# claudesync:redact";
|
|
347
|
+
function detectSecrets(content) {
|
|
348
|
+
const matches = [];
|
|
349
|
+
const lines = content.split("\n");
|
|
350
|
+
for (let i = 0; i < lines.length; i++) {
|
|
351
|
+
const line = lines[i];
|
|
352
|
+
if (line.includes(REDACT_COMMENT)) {
|
|
353
|
+
matches.push({
|
|
354
|
+
line: i + 1,
|
|
355
|
+
pattern: "Explicit redact marker",
|
|
356
|
+
match: line.trim(),
|
|
357
|
+
lineContent: line
|
|
358
|
+
});
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
for (const { name, regex } of SECRET_PATTERNS) {
|
|
362
|
+
const m = line.match(regex);
|
|
363
|
+
if (m) {
|
|
364
|
+
matches.push({
|
|
365
|
+
line: i + 1,
|
|
366
|
+
pattern: name,
|
|
367
|
+
match: m[0].slice(0, 20) + (m[0].length > 20 ? "..." : ""),
|
|
368
|
+
lineContent: line
|
|
369
|
+
});
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (!matches.some((m) => m.line === i + 1)) {
|
|
374
|
+
const tokens = line.match(/[A-Za-z0-9+/=_\-]{32,}/g);
|
|
375
|
+
if (tokens) {
|
|
376
|
+
for (const token of tokens) {
|
|
377
|
+
if (shannonEntropy(token) > 4.5) {
|
|
378
|
+
matches.push({
|
|
379
|
+
line: i + 1,
|
|
380
|
+
pattern: "High entropy string",
|
|
381
|
+
match: token.slice(0, 20) + "...",
|
|
382
|
+
lineContent: line
|
|
383
|
+
});
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return matches;
|
|
391
|
+
}
|
|
392
|
+
function redactContent(content, secrets) {
|
|
393
|
+
const lines = content.split("\n");
|
|
394
|
+
const secretLines = new Set(secrets.map((s) => s.line));
|
|
395
|
+
return lines.map((line, i) => {
|
|
396
|
+
if (secretLines.has(i + 1)) {
|
|
397
|
+
return "# [REDACTED by claudesync]";
|
|
398
|
+
}
|
|
399
|
+
return line;
|
|
400
|
+
}).join("\n");
|
|
401
|
+
}
|
|
402
|
+
function shannonEntropy(str) {
|
|
403
|
+
const freq = /* @__PURE__ */ new Map();
|
|
404
|
+
for (const ch of str) {
|
|
405
|
+
freq.set(ch, (freq.get(ch) || 0) + 1);
|
|
406
|
+
}
|
|
407
|
+
let entropy = 0;
|
|
408
|
+
const len = str.length;
|
|
409
|
+
for (const count of freq.values()) {
|
|
410
|
+
const p = count / len;
|
|
411
|
+
entropy -= p * Math.log2(p);
|
|
412
|
+
}
|
|
413
|
+
return entropy;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/core/crypto.ts
|
|
417
|
+
import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "crypto";
|
|
418
|
+
var MAGIC_PREFIX = "CLAUDESYNC_ENC:";
|
|
419
|
+
var ALGORITHM = "aes-256-gcm";
|
|
420
|
+
var KEY_LENGTH = 32;
|
|
421
|
+
var IV_LENGTH = 16;
|
|
422
|
+
var SALT_LENGTH = 32;
|
|
423
|
+
var AUTH_TAG_LENGTH = 16;
|
|
424
|
+
var SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
|
|
425
|
+
function deriveKey(passphrase, salt) {
|
|
426
|
+
return scryptSync(passphrase, salt, KEY_LENGTH, SCRYPT_PARAMS);
|
|
427
|
+
}
|
|
428
|
+
function encrypt(content, passphrase) {
|
|
429
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
430
|
+
const key = deriveKey(passphrase, salt);
|
|
431
|
+
const iv = randomBytes(IV_LENGTH);
|
|
432
|
+
const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
433
|
+
const encrypted = Buffer.concat([cipher.update(content, "utf-8"), cipher.final()]);
|
|
434
|
+
const authTag = cipher.getAuthTag();
|
|
435
|
+
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
|
|
436
|
+
return MAGIC_PREFIX + combined.toString("base64");
|
|
437
|
+
}
|
|
438
|
+
function decrypt(encoded, passphrase) {
|
|
439
|
+
if (!encoded.startsWith(MAGIC_PREFIX)) throw new Error("Not encrypted by claudesync");
|
|
440
|
+
const combined = Buffer.from(encoded.slice(MAGIC_PREFIX.length), "base64");
|
|
441
|
+
const salt = combined.subarray(0, SALT_LENGTH);
|
|
442
|
+
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
443
|
+
const authTag = combined.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
444
|
+
const ciphertext = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
445
|
+
const key = deriveKey(passphrase, salt);
|
|
446
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
447
|
+
decipher.setAuthTag(authTag);
|
|
448
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
449
|
+
return decrypted.toString("utf-8");
|
|
450
|
+
}
|
|
451
|
+
function isEncrypted(content) {
|
|
452
|
+
return content.startsWith(MAGIC_PREFIX);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/commands/push.ts
|
|
456
|
+
async function runPush(options) {
|
|
457
|
+
const config = loadConfig();
|
|
458
|
+
if (!config?.token) {
|
|
459
|
+
error(t("auth.no_token"));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
info(t("push.scanning"));
|
|
463
|
+
const files = scanFiles(options.only);
|
|
464
|
+
if (files.length === 0) {
|
|
465
|
+
warn(t("push.no_files"));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const processedFiles = [];
|
|
469
|
+
for (const file of files) {
|
|
470
|
+
const secrets = detectSecrets(file.content);
|
|
471
|
+
if (secrets.length > 0) {
|
|
472
|
+
warn(t("push.secret_found").replace("{path}", file.relativePath).replace("{count}", String(secrets.length)));
|
|
473
|
+
for (const s of secrets) {
|
|
474
|
+
console.log(` ${c.dim(`L${s.line}`)} ${c.yellow(s.pattern)}: ${s.match}`);
|
|
475
|
+
}
|
|
476
|
+
const redacted = redactContent(file.content, secrets);
|
|
477
|
+
processedFiles.push({ ...file, content: redacted });
|
|
478
|
+
} else {
|
|
479
|
+
processedFiles.push(file);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const encryptedFiles = /* @__PURE__ */ new Set();
|
|
483
|
+
const finalFiles = options.encrypt ? processedFiles.map((f) => {
|
|
484
|
+
encryptedFiles.add(f.gistFilename);
|
|
485
|
+
return { ...f, content: encrypt(f.content, config.token) };
|
|
486
|
+
}) : processedFiles;
|
|
487
|
+
if (config.gist_id) {
|
|
488
|
+
try {
|
|
489
|
+
const gist = await getGist(config.token, config.gist_id);
|
|
490
|
+
const changes = compareForPush(finalFiles, gist);
|
|
491
|
+
const modified = changes.filter((c2) => c2.status !== "unchanged");
|
|
492
|
+
if (modified.length === 0) {
|
|
493
|
+
success(t("push.no_changes"));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
heading(t("push.summary"));
|
|
497
|
+
for (const change of modified) {
|
|
498
|
+
const icon = change.status === "added" ? c.green("+") : change.status === "deleted" ? c.red("-") : c.yellow("~");
|
|
499
|
+
console.log(` ${icon} [${change.category}] ${change.relativePath}`);
|
|
500
|
+
if (change.status === "modified" && change.localContent && change.remoteContent) {
|
|
501
|
+
const diff = simpleDiff(change.remoteContent, change.localContent);
|
|
502
|
+
if (diff.length > 0 && diff.length <= 20) {
|
|
503
|
+
printDiff(change.relativePath, diff);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
console.log();
|
|
508
|
+
if (!options.force) {
|
|
509
|
+
const ok = await confirm(t("push.confirm"));
|
|
510
|
+
if (!ok) {
|
|
511
|
+
warn(t("push.cancelled"));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const deletedFiles = changes.filter((c2) => c2.status === "deleted").map((c2) => c2.gistFilename);
|
|
516
|
+
const filesToUpload = finalFiles.filter(
|
|
517
|
+
(f) => changes.some((c2) => c2.gistFilename === f.gistFilename && c2.status !== "unchanged")
|
|
518
|
+
);
|
|
519
|
+
await updateGist(config.token, config.gist_id, filesToUpload, deletedFiles, encryptedFiles, options.message);
|
|
520
|
+
success(t("push.success"));
|
|
521
|
+
} catch (err) {
|
|
522
|
+
if (String(err).includes("404")) {
|
|
523
|
+
info(t("push.gist_not_found_creating"));
|
|
524
|
+
const newGist = await createGist(config.token, finalFiles, encryptedFiles, options.message);
|
|
525
|
+
config.gist_id = newGist.id;
|
|
526
|
+
saveConfig(config);
|
|
527
|
+
success(`${t("push.success")} (${newGist.html_url})`);
|
|
528
|
+
} else {
|
|
529
|
+
throw err;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
heading(t("push.uploading_new").replace("{count}", String(finalFiles.length)));
|
|
534
|
+
for (const f of finalFiles) {
|
|
535
|
+
console.log(` [${f.category}] ${f.relativePath}`);
|
|
536
|
+
}
|
|
537
|
+
console.log();
|
|
538
|
+
if (!options.force) {
|
|
539
|
+
const ok = await confirm(t("push.confirm"));
|
|
540
|
+
if (!ok) {
|
|
541
|
+
warn(t("push.cancelled"));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
info(t("push.creating_gist"));
|
|
546
|
+
const newGist = await createGist(config.token, finalFiles, encryptedFiles, options.message);
|
|
547
|
+
config.gist_id = newGist.id;
|
|
548
|
+
saveConfig(config);
|
|
549
|
+
success(`${t("push.success")} (${newGist.html_url})`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/commands/pull.ts
|
|
554
|
+
import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, copyFileSync } from "fs";
|
|
555
|
+
import { join, dirname } from "path";
|
|
556
|
+
async function runPull(options) {
|
|
557
|
+
const config = loadConfig();
|
|
558
|
+
if (!config?.token) {
|
|
559
|
+
error(t("auth.no_token"));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (!config.gist_id) {
|
|
563
|
+
error(t("pull.no_gist"));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
info(t("pull.fetching"));
|
|
567
|
+
const gist = await getGist(config.token, config.gist_id);
|
|
568
|
+
const localFiles = scanFiles(options.only);
|
|
569
|
+
const changes = compareForPull(gist, localFiles);
|
|
570
|
+
const modified = changes.filter((c2) => c2.status !== "unchanged");
|
|
571
|
+
const meta = parseMeta(gist);
|
|
572
|
+
const toApply = options.only ? modified.filter((c2) => c2.category === options.only) : modified;
|
|
573
|
+
if (toApply.length === 0) {
|
|
574
|
+
success(t("pull.no_changes"));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
await applyChanges(toApply, config.token, options.force ?? false, meta);
|
|
578
|
+
}
|
|
579
|
+
async function applyChanges(changes, token, force, meta) {
|
|
580
|
+
heading(t("pull.changes_heading"));
|
|
581
|
+
for (const change of changes) {
|
|
582
|
+
const icon = change.status === "added" ? c.green(t("pull.icon_added")) : change.status === "modified" ? c.yellow(t("pull.icon_modified")) : c.red(t("pull.icon_deleted"));
|
|
583
|
+
console.log(` ${icon} [${change.category}] ${change.relativePath}`);
|
|
584
|
+
if (change.status === "modified" && change.localContent && change.remoteContent) {
|
|
585
|
+
const diff = simpleDiff(change.localContent, change.remoteContent);
|
|
586
|
+
if (diff.length > 0 && diff.length <= 30) {
|
|
587
|
+
printDiff(change.relativePath, diff);
|
|
588
|
+
} else if (diff.length > 30) {
|
|
589
|
+
console.log(c.dim(` (${t("pull.diff_truncated").replace("{count}", String(diff.length))})`));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
console.log();
|
|
594
|
+
if (!force) {
|
|
595
|
+
const ok = await confirm(t("pull.confirm"));
|
|
596
|
+
if (!ok) {
|
|
597
|
+
warn(t("pull.cancelled"));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const base = claudeDir();
|
|
602
|
+
let applied = 0;
|
|
603
|
+
for (const change of changes) {
|
|
604
|
+
if (!isPathSafe(change.relativePath)) {
|
|
605
|
+
error(`${t("error.path_unsafe")} ${change.relativePath}`);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const targetPath = join(base, change.relativePath);
|
|
609
|
+
if (change.status === "deleted") {
|
|
610
|
+
warn(t("pull.skip_deleted").replace("{path}", change.relativePath));
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
let content = change.remoteContent ?? "";
|
|
614
|
+
const metaEntry = meta?.file_map[change.gistFilename];
|
|
615
|
+
const needsDecrypt = metaEntry?.encrypted || isEncrypted(content);
|
|
616
|
+
if (needsDecrypt) {
|
|
617
|
+
try {
|
|
618
|
+
content = decrypt(content, token);
|
|
619
|
+
} catch {
|
|
620
|
+
error(t("pull.decrypt_failed").replace("{path}", change.relativePath));
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (existsSync2(targetPath)) {
|
|
625
|
+
copyFileSync(targetPath, targetPath + ".bak");
|
|
626
|
+
}
|
|
627
|
+
const dir = dirname(targetPath);
|
|
628
|
+
if (!existsSync2(dir)) {
|
|
629
|
+
mkdirSync(dir, { recursive: true });
|
|
630
|
+
}
|
|
631
|
+
writeFileSync2(targetPath, content, "utf-8");
|
|
632
|
+
applied++;
|
|
633
|
+
}
|
|
634
|
+
success(`${t("pull.success")} (${t("pull.applied").replace("{count}", String(applied))})`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/commands/diff.ts
|
|
638
|
+
async function runDiff(options) {
|
|
639
|
+
const config = loadConfig();
|
|
640
|
+
if (!config?.token) {
|
|
641
|
+
error(t("auth.no_token"));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (!config.gist_id) {
|
|
645
|
+
error(t("diff.no_gist"));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
info(t("diff.fetching"));
|
|
649
|
+
const gist = await getGist(config.token, config.gist_id);
|
|
650
|
+
const localFiles = scanFiles(options.only);
|
|
651
|
+
const changes = compareForPull(gist, localFiles);
|
|
652
|
+
const modified = changes.filter((ch) => ch.status !== "unchanged");
|
|
653
|
+
if (modified.length === 0) {
|
|
654
|
+
console.log(t("diff.no_diff"));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
for (const change of modified) {
|
|
658
|
+
const icon = change.status === "added" ? c.green("NEW") : change.status === "modified" ? c.yellow("MOD") : c.red("DEL");
|
|
659
|
+
console.log(`
|
|
660
|
+
${icon} [${change.category}] ${change.relativePath}`);
|
|
661
|
+
if (change.status === "modified" && change.localContent && change.remoteContent) {
|
|
662
|
+
const diff = simpleDiff(change.localContent, change.remoteContent);
|
|
663
|
+
printDiff(change.relativePath, diff);
|
|
664
|
+
} else if (change.status === "added" && change.remoteContent) {
|
|
665
|
+
const lines = change.remoteContent.split("\n").map((l) => `+${l}`);
|
|
666
|
+
printDiff(change.relativePath, lines);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
console.log(`
|
|
670
|
+
${t("diff.total").replace("{count}", String(modified.length))}`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/commands/status.ts
|
|
674
|
+
async function runStatus() {
|
|
675
|
+
const config = loadConfig();
|
|
676
|
+
heading(t("status.title"));
|
|
677
|
+
if (config?.token) {
|
|
678
|
+
console.log(` ${t("status.auth_label")}: ${c.green(t("status.authenticated"))}`);
|
|
679
|
+
} else {
|
|
680
|
+
console.log(` ${t("status.auth_label")}: ${c.red(t("status.not_authenticated"))}`);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (config.gist_id) {
|
|
684
|
+
console.log(` ${t("status.gist_label")}: ${c.green(config.gist_id)}`);
|
|
685
|
+
try {
|
|
686
|
+
const gist = await getGist(config.token, config.gist_id);
|
|
687
|
+
const meta = parseMeta(gist);
|
|
688
|
+
if (meta) {
|
|
689
|
+
console.log(` ${t("status.last_sync")}`);
|
|
690
|
+
console.log(` ${t("status.machine")}: ${meta.last_sync.machine}`);
|
|
691
|
+
console.log(` ${t("status.host")}: ${meta.last_sync.hostname}`);
|
|
692
|
+
console.log(` ${t("status.platform")}: ${meta.last_sync.platform}`);
|
|
693
|
+
console.log(` ${t("status.time")}: ${meta.last_sync.timestamp}`);
|
|
694
|
+
console.log(` ${t("status.file_count")}: ${meta.last_sync.file_count}`);
|
|
695
|
+
}
|
|
696
|
+
const fileCount = Object.keys(gist.files).filter((n) => n !== "_meta.json").length;
|
|
697
|
+
console.log(` ${t("status.remote_files").replace("{count}", String(fileCount))}`);
|
|
698
|
+
} catch {
|
|
699
|
+
console.log(` ${c.yellow(t("status.gist_fetch_failed"))}`);
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
console.log(` ${t("status.gist_label")}: ${c.yellow(t("status.no_gist"))}`);
|
|
703
|
+
}
|
|
704
|
+
console.log(` ${t("status.current_machine")}: ${machineName()}`);
|
|
705
|
+
console.log(` ${t("status.platform")}: ${platformString()}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/commands/history.ts
|
|
709
|
+
var MAX_REVISIONS = 10;
|
|
710
|
+
async function runHistory() {
|
|
711
|
+
const config = loadConfig();
|
|
712
|
+
if (!config?.token) {
|
|
713
|
+
error(t("auth.no_token"));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (!config.gist_id) {
|
|
717
|
+
error(t("history.no_gist"));
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const allRevisions = await getHistory(config.token, config.gist_id);
|
|
721
|
+
if (allRevisions.length === 0) {
|
|
722
|
+
console.log(t("history.empty"));
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const revisions = allRevisions.slice(0, MAX_REVISIONS);
|
|
726
|
+
info(t("history.loading").replace("{count}", String(revisions.length)));
|
|
727
|
+
const messages = await Promise.all(
|
|
728
|
+
revisions.map(async (rev) => {
|
|
729
|
+
try {
|
|
730
|
+
const gist = await getGistAtRevision(config.token, config.gist_id, rev.version);
|
|
731
|
+
const meta = parseMeta(gist);
|
|
732
|
+
return meta?.last_sync.message ?? null;
|
|
733
|
+
} catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
})
|
|
737
|
+
);
|
|
738
|
+
heading(t("history.title"));
|
|
739
|
+
for (let i = 0; i < revisions.length; i++) {
|
|
740
|
+
const rev = revisions[i];
|
|
741
|
+
const date = new Date(rev.committed_at).toLocaleString();
|
|
742
|
+
const shortSha = rev.version.slice(0, 8);
|
|
743
|
+
const stats = `+${rev.change_status.additions} -${rev.change_status.deletions}`;
|
|
744
|
+
const msg = messages[i];
|
|
745
|
+
const prefix = i === 0 ? c.green("\u25CF") : c.dim("\u25CB");
|
|
746
|
+
const msgStr = msg ? ` ${c.cyan(msg)}` : "";
|
|
747
|
+
console.log(` ${prefix} ${c.bold(shortSha)} ${c.dim(date)} ${c.yellow(stats)}${msgStr}`);
|
|
748
|
+
}
|
|
749
|
+
if (allRevisions.length > MAX_REVISIONS) {
|
|
750
|
+
console.log(c.dim(` ${t("history.more").replace("{count}", String(allRevisions.length - MAX_REVISIONS))}`));
|
|
751
|
+
}
|
|
752
|
+
console.log(`
|
|
753
|
+
${t("history.total").replace("{count}", String(allRevisions.length))}`);
|
|
754
|
+
console.log(c.dim(t("history.rollback_hint")));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/commands/rollback.ts
|
|
758
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3, copyFileSync as copyFileSync2 } from "fs";
|
|
759
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
760
|
+
var META_FILE2 = "_meta.json";
|
|
761
|
+
async function runRollback(version) {
|
|
762
|
+
const config = loadConfig();
|
|
763
|
+
if (!config?.token) {
|
|
764
|
+
error(t("auth.no_token"));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (!config.gist_id) {
|
|
768
|
+
error(t("rollback.no_gist"));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const revisions = await getHistory(config.token, config.gist_id);
|
|
772
|
+
const matched = revisions.find((r) => r.version.startsWith(version));
|
|
773
|
+
if (!matched) {
|
|
774
|
+
error(t("rollback.not_found").replace("{version}", version));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const fullSha = matched.version;
|
|
778
|
+
info(t("rollback.restoring").replace("{sha}", fullSha.slice(0, 8)).replace("{date}", new Date(matched.committed_at).toLocaleString()));
|
|
779
|
+
const gist = await getGistAtRevision(config.token, config.gist_id, fullSha);
|
|
780
|
+
const meta = parseMeta(gist);
|
|
781
|
+
const localFiles = scanFiles();
|
|
782
|
+
const localMap = new Map(localFiles.map((f) => [f.gistFilename, f]));
|
|
783
|
+
const base = claudeDir();
|
|
784
|
+
const filesToApply = [];
|
|
785
|
+
heading(t("rollback.target_heading"));
|
|
786
|
+
for (const [gistName, gistFile] of Object.entries(gist.files)) {
|
|
787
|
+
if (gistName === META_FILE2 || !gistFile?.content) continue;
|
|
788
|
+
const entry = meta?.file_map[gistName];
|
|
789
|
+
const relativePath = entry?.path ?? fromGistFilename(gistName);
|
|
790
|
+
const local = localMap.get(gistName);
|
|
791
|
+
if (!local) {
|
|
792
|
+
console.log(` ${c.green("+")} ${relativePath} ${t("rollback.new_file")}`);
|
|
793
|
+
} else if (local.content !== gistFile.content) {
|
|
794
|
+
console.log(` ${c.yellow("~")} ${relativePath}`);
|
|
795
|
+
const diff = simpleDiff(local.content, gistFile.content);
|
|
796
|
+
if (diff.length <= 20) {
|
|
797
|
+
printDiff(relativePath, diff);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
let content = gistFile.content;
|
|
803
|
+
const needsDecrypt = entry?.encrypted || isEncrypted(content);
|
|
804
|
+
if (needsDecrypt) {
|
|
805
|
+
try {
|
|
806
|
+
content = decrypt(content, config.token);
|
|
807
|
+
} catch {
|
|
808
|
+
error(t("rollback.decrypt_failed").replace("{path}", relativePath));
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
filesToApply.push({ relativePath, content });
|
|
813
|
+
}
|
|
814
|
+
if (filesToApply.length === 0) {
|
|
815
|
+
success(t("rollback.no_changes"));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
console.log(`
|
|
819
|
+
${t("rollback.files_changed").replace("{count}", String(filesToApply.length))}`);
|
|
820
|
+
const ok = await confirm(t("rollback.confirm"));
|
|
821
|
+
if (!ok) {
|
|
822
|
+
warn(t("rollback.cancelled"));
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
for (const { relativePath, content } of filesToApply) {
|
|
826
|
+
if (!isPathSafe(relativePath)) {
|
|
827
|
+
error(`${t("error.path_unsafe")} ${relativePath}`);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
const targetPath = join2(base, relativePath);
|
|
831
|
+
if (existsSync3(targetPath)) {
|
|
832
|
+
copyFileSync2(targetPath, targetPath + ".bak");
|
|
833
|
+
}
|
|
834
|
+
const dir = dirname2(targetPath);
|
|
835
|
+
if (!existsSync3(dir)) {
|
|
836
|
+
mkdirSync2(dir, { recursive: true });
|
|
837
|
+
}
|
|
838
|
+
writeFileSync3(targetPath, content, "utf-8");
|
|
839
|
+
}
|
|
840
|
+
success(t("rollback.success").replace("{sha}", fullSha.slice(0, 8)));
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/core/auto-config.ts
|
|
844
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync4, unlinkSync, existsSync as existsSync4, chmodSync as chmodSync2 } from "fs";
|
|
845
|
+
function saveAutoConfig(config) {
|
|
846
|
+
const path = autoConfigPath();
|
|
847
|
+
writeFileSync4(path, JSON.stringify(config, null, 2), "utf-8");
|
|
848
|
+
chmodSync2(path, 384);
|
|
849
|
+
}
|
|
850
|
+
function loadAutoConfig() {
|
|
851
|
+
const path = autoConfigPath();
|
|
852
|
+
if (!existsSync4(path)) return null;
|
|
853
|
+
try {
|
|
854
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
855
|
+
} catch {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function removeAutoConfig() {
|
|
860
|
+
const path = autoConfigPath();
|
|
861
|
+
if (existsSync4(path)) unlinkSync(path);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/core/scheduler.ts
|
|
865
|
+
import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
866
|
+
import { execSync as execSync2 } from "child_process";
|
|
867
|
+
import { join as join3 } from "path";
|
|
868
|
+
import { homedir } from "os";
|
|
869
|
+
var LAUNCHD_LABEL = "com.claudesync.auto";
|
|
870
|
+
var SYSTEMD_SERVICE = "claudesync-auto";
|
|
871
|
+
function buildLaunchdPlist(cliArgs, intervalSeconds) {
|
|
872
|
+
const argsXml = [...cliArgs, "auto-run"].map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
873
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
874
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
875
|
+
<plist version="1.0">
|
|
876
|
+
<dict>
|
|
877
|
+
<key>Label</key>
|
|
878
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
879
|
+
<key>ProgramArguments</key>
|
|
880
|
+
<array>
|
|
881
|
+
${argsXml}
|
|
882
|
+
</array>
|
|
883
|
+
<key>StartInterval</key>
|
|
884
|
+
<integer>${intervalSeconds}</integer>
|
|
885
|
+
<key>RunAtLoad</key>
|
|
886
|
+
<true/>
|
|
887
|
+
<key>StandardOutPath</key>
|
|
888
|
+
<string>${join3(homedir(), ".claudesync", "launchd-stdout.log")}</string>
|
|
889
|
+
<key>StandardErrorPath</key>
|
|
890
|
+
<string>${join3(homedir(), ".claudesync", "launchd-stderr.log")}</string>
|
|
891
|
+
</dict>
|
|
892
|
+
</plist>`;
|
|
893
|
+
}
|
|
894
|
+
function buildSystemdService(cliArgs) {
|
|
895
|
+
const execStart = [...cliArgs, "auto-run"].join(" ");
|
|
896
|
+
return `[Unit]
|
|
897
|
+
Description=claudesync auto sync
|
|
898
|
+
After=network-online.target
|
|
899
|
+
Wants=network-online.target
|
|
900
|
+
|
|
901
|
+
[Service]
|
|
902
|
+
Type=oneshot
|
|
903
|
+
ExecStart=${execStart}
|
|
904
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
905
|
+
|
|
906
|
+
[Install]
|
|
907
|
+
WantedBy=default.target`;
|
|
908
|
+
}
|
|
909
|
+
function buildSystemdTimer(intervalSeconds) {
|
|
910
|
+
return `[Unit]
|
|
911
|
+
Description=claudesync auto sync timer
|
|
912
|
+
|
|
913
|
+
[Timer]
|
|
914
|
+
OnBootSec=60s
|
|
915
|
+
OnUnitActiveSec=${intervalSeconds}s
|
|
916
|
+
Persistent=true
|
|
917
|
+
|
|
918
|
+
[Install]
|
|
919
|
+
WantedBy=timers.target`;
|
|
920
|
+
}
|
|
921
|
+
function buildSchtasksXml(cliArgs, intervalSeconds) {
|
|
922
|
+
const command = cliArgs[0];
|
|
923
|
+
const args = [...cliArgs.slice(1), "auto-run"].join(" ");
|
|
924
|
+
const minutes = Math.max(1, Math.round(intervalSeconds / 60));
|
|
925
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
926
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
927
|
+
<RegistrationInfo>
|
|
928
|
+
<Description>claudesync-auto automatic sync</Description>
|
|
929
|
+
</RegistrationInfo>
|
|
930
|
+
<Triggers>
|
|
931
|
+
<TimeTrigger>
|
|
932
|
+
<Repetition>
|
|
933
|
+
<Interval>PT${minutes}M</Interval>
|
|
934
|
+
<StopAtDurationEnd>false</StopAtDurationEnd>
|
|
935
|
+
</Repetition>
|
|
936
|
+
<StartBoundary>2026-01-01T00:00:00</StartBoundary>
|
|
937
|
+
<Enabled>true</Enabled>
|
|
938
|
+
</TimeTrigger>
|
|
939
|
+
</Triggers>
|
|
940
|
+
<Actions>
|
|
941
|
+
<Exec>
|
|
942
|
+
<Command>${command}</Command>
|
|
943
|
+
<Arguments>${args}</Arguments>
|
|
944
|
+
</Exec>
|
|
945
|
+
</Actions>
|
|
946
|
+
<Settings>
|
|
947
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
948
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
949
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
950
|
+
</Settings>
|
|
951
|
+
</Task>`;
|
|
952
|
+
}
|
|
953
|
+
function resolveCliArgs() {
|
|
954
|
+
try {
|
|
955
|
+
const bin = execSync2("which claudesync", { encoding: "utf-8" }).trim();
|
|
956
|
+
return [bin];
|
|
957
|
+
} catch {
|
|
958
|
+
}
|
|
959
|
+
const scriptPath = process.argv[1];
|
|
960
|
+
if (scriptPath) {
|
|
961
|
+
return [process.execPath, scriptPath];
|
|
962
|
+
}
|
|
963
|
+
throw new Error(t("error.cli_not_found"));
|
|
964
|
+
}
|
|
965
|
+
function registerScheduler(intervalSeconds) {
|
|
966
|
+
const cliArgs = resolveCliArgs();
|
|
967
|
+
if (process.platform === "darwin") {
|
|
968
|
+
registerLaunchd(cliArgs, intervalSeconds);
|
|
969
|
+
} else if (process.platform === "linux") {
|
|
970
|
+
registerSystemd(cliArgs, intervalSeconds);
|
|
971
|
+
} else if (process.platform === "win32") {
|
|
972
|
+
registerSchtasks(cliArgs, intervalSeconds);
|
|
973
|
+
} else {
|
|
974
|
+
throw new Error(t("error.unsupported_platform").replace("{platform}", process.platform));
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function unregisterScheduler() {
|
|
978
|
+
if (process.platform === "darwin") {
|
|
979
|
+
unregisterLaunchd();
|
|
980
|
+
} else if (process.platform === "linux") {
|
|
981
|
+
unregisterSystemd();
|
|
982
|
+
} else if (process.platform === "win32") {
|
|
983
|
+
unregisterSchtasks();
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
function launchdPlistPath() {
|
|
987
|
+
return join3(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
988
|
+
}
|
|
989
|
+
function registerLaunchd(cliArgs, intervalSeconds) {
|
|
990
|
+
const plistDir = join3(homedir(), "Library", "LaunchAgents");
|
|
991
|
+
if (!existsSync5(plistDir)) mkdirSync3(plistDir, { recursive: true });
|
|
992
|
+
const plistPath = launchdPlistPath();
|
|
993
|
+
try {
|
|
994
|
+
execSync2(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
writeFileSync5(plistPath, buildLaunchdPlist(cliArgs, intervalSeconds), "utf-8");
|
|
998
|
+
execSync2(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
999
|
+
}
|
|
1000
|
+
function unregisterLaunchd() {
|
|
1001
|
+
const plistPath = launchdPlistPath();
|
|
1002
|
+
if (existsSync5(plistPath)) {
|
|
1003
|
+
try {
|
|
1004
|
+
execSync2(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
unlinkSync2(plistPath);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function systemdDir() {
|
|
1011
|
+
return join3(homedir(), ".config", "systemd", "user");
|
|
1012
|
+
}
|
|
1013
|
+
function registerSystemd(cliArgs, intervalSeconds) {
|
|
1014
|
+
const dir = systemdDir();
|
|
1015
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
1016
|
+
writeFileSync5(join3(dir, `${SYSTEMD_SERVICE}.service`), buildSystemdService(cliArgs), "utf-8");
|
|
1017
|
+
writeFileSync5(join3(dir, `${SYSTEMD_SERVICE}.timer`), buildSystemdTimer(intervalSeconds), "utf-8");
|
|
1018
|
+
execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
1019
|
+
execSync2(`systemctl --user enable --now ${SYSTEMD_SERVICE}.timer`, { stdio: "ignore" });
|
|
1020
|
+
}
|
|
1021
|
+
function unregisterSystemd() {
|
|
1022
|
+
const dir = systemdDir();
|
|
1023
|
+
try {
|
|
1024
|
+
execSync2(`systemctl --user disable --now ${SYSTEMD_SERVICE}.timer`, { stdio: "ignore" });
|
|
1025
|
+
} catch {
|
|
1026
|
+
}
|
|
1027
|
+
const servicePath = join3(dir, `${SYSTEMD_SERVICE}.service`);
|
|
1028
|
+
const timerPath = join3(dir, `${SYSTEMD_SERVICE}.timer`);
|
|
1029
|
+
if (existsSync5(servicePath)) unlinkSync2(servicePath);
|
|
1030
|
+
if (existsSync5(timerPath)) unlinkSync2(timerPath);
|
|
1031
|
+
try {
|
|
1032
|
+
execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function registerSchtasks(cliArgs, intervalSeconds) {
|
|
1037
|
+
const xmlPath = join3(homedir(), ".claudesync", "schtask.xml");
|
|
1038
|
+
writeFileSync5(xmlPath, buildSchtasksXml(cliArgs, intervalSeconds), "utf-16le");
|
|
1039
|
+
execSync2(`schtasks /Create /TN "claudesync-auto" /XML "${xmlPath}" /F`, { stdio: "ignore" });
|
|
1040
|
+
unlinkSync2(xmlPath);
|
|
1041
|
+
}
|
|
1042
|
+
function unregisterSchtasks() {
|
|
1043
|
+
try {
|
|
1044
|
+
execSync2('schtasks /Delete /TN "claudesync-auto" /F', { stdio: "ignore" });
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/commands/auto.ts
|
|
1050
|
+
import { hostname } from "os";
|
|
1051
|
+
var MIN_INTERVAL_SECONDS = 60;
|
|
1052
|
+
function parseInterval(input) {
|
|
1053
|
+
const match = input.trim().match(/^(\d+)\s*(s|m|h|d)$/i);
|
|
1054
|
+
if (!match) return null;
|
|
1055
|
+
const value = parseInt(match[1], 10);
|
|
1056
|
+
const unit = match[2].toLowerCase();
|
|
1057
|
+
switch (unit) {
|
|
1058
|
+
case "s":
|
|
1059
|
+
return value;
|
|
1060
|
+
case "m":
|
|
1061
|
+
return value * 60;
|
|
1062
|
+
case "h":
|
|
1063
|
+
return value * 3600;
|
|
1064
|
+
case "d":
|
|
1065
|
+
return value * 86400;
|
|
1066
|
+
default:
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function formatInterval(seconds) {
|
|
1071
|
+
if (seconds < 60) return `${seconds}s`;
|
|
1072
|
+
if (seconds < 3600) return `${seconds / 60}m`;
|
|
1073
|
+
if (seconds < 86400) return `${seconds / 3600}h`;
|
|
1074
|
+
return `${seconds / 86400}d`;
|
|
1075
|
+
}
|
|
1076
|
+
async function runAuto() {
|
|
1077
|
+
const config = loadConfig();
|
|
1078
|
+
if (!config?.token) {
|
|
1079
|
+
error(t("auth.no_token"));
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (!config.gist_id) {
|
|
1083
|
+
error(t("auto.no_gist"));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
heading(t("auto.title"));
|
|
1087
|
+
const dirIdx = await select(t("auto.select_direction"), [
|
|
1088
|
+
t("auto.direction_push"),
|
|
1089
|
+
t("auto.direction_pull")
|
|
1090
|
+
]);
|
|
1091
|
+
const direction = dirIdx === 0 ? "push" : "pull";
|
|
1092
|
+
let primaryDevice;
|
|
1093
|
+
if (direction === "push") {
|
|
1094
|
+
try {
|
|
1095
|
+
const gist = await getGist(config.token, config.gist_id);
|
|
1096
|
+
const meta = parseMeta(gist);
|
|
1097
|
+
if (meta?.primary_device) {
|
|
1098
|
+
const current = meta.primary_device;
|
|
1099
|
+
const isMe = current.machine_id === getMachineId();
|
|
1100
|
+
if (!isMe) {
|
|
1101
|
+
console.log();
|
|
1102
|
+
warn(
|
|
1103
|
+
t("auto.primary_warning").replace("{machine}", current.machine).replace("{hostname}", current.hostname)
|
|
1104
|
+
);
|
|
1105
|
+
const ok = await confirm(t("auto.confirm_primary_change"), false);
|
|
1106
|
+
if (!ok) {
|
|
1107
|
+
warn(t("auto.setup_cancelled"));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
primaryDevice = {
|
|
1115
|
+
machine_id: getMachineId(),
|
|
1116
|
+
machine: machineName(),
|
|
1117
|
+
hostname: hostname(),
|
|
1118
|
+
platform: platformString(),
|
|
1119
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
let conflictPolicy;
|
|
1123
|
+
if (direction === "pull") {
|
|
1124
|
+
const policyIdx = await select(t("auto.select_conflict_policy"), [
|
|
1125
|
+
t("auto.policy_overwrite"),
|
|
1126
|
+
t("auto.policy_skip"),
|
|
1127
|
+
t("auto.policy_backup")
|
|
1128
|
+
]);
|
|
1129
|
+
conflictPolicy = ["overwrite", "skip", "backup"][policyIdx];
|
|
1130
|
+
}
|
|
1131
|
+
let intervalSeconds = null;
|
|
1132
|
+
while (intervalSeconds === null) {
|
|
1133
|
+
const input = await ask(t("auto.select_interval") + " ");
|
|
1134
|
+
intervalSeconds = parseInterval(input);
|
|
1135
|
+
if (intervalSeconds === null) {
|
|
1136
|
+
warn(t("auto.interval_invalid"));
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (intervalSeconds < MIN_INTERVAL_SECONDS) {
|
|
1140
|
+
warn(t("auto.interval_too_short"));
|
|
1141
|
+
intervalSeconds = null;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
console.log();
|
|
1145
|
+
const allCats = [...CATEGORIES];
|
|
1146
|
+
console.log(`${t("auto.select_categories")}`);
|
|
1147
|
+
console.log(` ${c.dim(allCats.join(", "))}`);
|
|
1148
|
+
const catInput = await ask("> ");
|
|
1149
|
+
let selectedCategories = catInput.trim() ? catInput.split(",").map((s) => s.trim()).filter((s) => allCats.includes(s)) : [...allCats];
|
|
1150
|
+
if (catInput.trim() && selectedCategories.length === 0) {
|
|
1151
|
+
warn(t("auto.invalid_categories"));
|
|
1152
|
+
selectedCategories = [...allCats];
|
|
1153
|
+
}
|
|
1154
|
+
const encrypt2 = await confirm(t("auto.select_encrypt"), false);
|
|
1155
|
+
const autoConfig = {
|
|
1156
|
+
direction,
|
|
1157
|
+
interval_seconds: intervalSeconds,
|
|
1158
|
+
categories: selectedCategories,
|
|
1159
|
+
encrypt: encrypt2,
|
|
1160
|
+
enabled: true,
|
|
1161
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1162
|
+
...conflictPolicy && { conflict_policy: conflictPolicy }
|
|
1163
|
+
};
|
|
1164
|
+
saveAutoConfig(autoConfig);
|
|
1165
|
+
try {
|
|
1166
|
+
unregisterScheduler();
|
|
1167
|
+
registerScheduler(intervalSeconds);
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
error(t("auto.scheduler_failed").replace("{error}", err instanceof Error ? err.message : String(err)));
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (direction === "push" && primaryDevice) {
|
|
1173
|
+
try {
|
|
1174
|
+
const { scanFiles: scanFiles2 } = await import("./scanner-M3SQGNTI.js");
|
|
1175
|
+
const files = scanFiles2();
|
|
1176
|
+
const { updateGist: updateGistFn } = await import("./gist-AMKOY723.js");
|
|
1177
|
+
await updateGistFn(config.token, config.gist_id, files, void 0, void 0, "auto-sync: primary device registered", primaryDevice);
|
|
1178
|
+
} catch {
|
|
1179
|
+
warn(t("auto.primary_register_failed"));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
console.log();
|
|
1183
|
+
success(t("auto.enabled"));
|
|
1184
|
+
console.log(` ${c.bold(t("auto.summary_direction") + ":")} ${direction}`);
|
|
1185
|
+
console.log(` ${c.bold(t("auto.summary_interval") + ":")} ${formatInterval(intervalSeconds)}`);
|
|
1186
|
+
console.log(` ${c.bold(t("auto.summary_categories") + ":")} ${selectedCategories.join(", ")}`);
|
|
1187
|
+
console.log(` ${c.bold(t("auto.summary_encrypt") + ":")} ${encrypt2 ? t("auto.yes") : t("auto.no")}`);
|
|
1188
|
+
if (conflictPolicy) {
|
|
1189
|
+
console.log(` ${c.bold(t("auto.summary_conflict") + ":")} ${conflictPolicy}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/core/lock.ts
|
|
1194
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync3, unlinkSync as unlinkSync3, existsSync as existsSync6 } from "fs";
|
|
1195
|
+
var STALE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1196
|
+
function acquireLock() {
|
|
1197
|
+
const path = lockFilePath();
|
|
1198
|
+
if (existsSync6(path)) {
|
|
1199
|
+
try {
|
|
1200
|
+
const data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
1201
|
+
const age = Date.now() - data.timestamp;
|
|
1202
|
+
if (age > STALE_TIMEOUT_MS) {
|
|
1203
|
+
unlinkSync3(path);
|
|
1204
|
+
} else {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
} catch {
|
|
1208
|
+
unlinkSync3(path);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const lockData = { pid: process.pid, timestamp: Date.now() };
|
|
1212
|
+
writeFileSync6(path, JSON.stringify(lockData), "utf-8");
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
function releaseLock() {
|
|
1216
|
+
const path = lockFilePath();
|
|
1217
|
+
if (existsSync6(path)) {
|
|
1218
|
+
unlinkSync3(path);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/core/auto-log.ts
|
|
1223
|
+
import { appendFileSync, readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
|
|
1224
|
+
function appendLog(direction, status, message) {
|
|
1225
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1226
|
+
const line = `${timestamp} [${direction}] [${status}] ${message}
|
|
1227
|
+
`;
|
|
1228
|
+
appendFileSync(autoLogPath(), line, "utf-8");
|
|
1229
|
+
}
|
|
1230
|
+
function readRecentLogs(count) {
|
|
1231
|
+
const path = autoLogPath();
|
|
1232
|
+
if (!existsSync7(path)) return [];
|
|
1233
|
+
const content = readFileSync4(path, "utf-8").trim();
|
|
1234
|
+
if (!content) return [];
|
|
1235
|
+
const lines = content.split("\n");
|
|
1236
|
+
return lines.slice(-count);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/core/notify.ts
|
|
1240
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, existsSync as existsSync8 } from "fs";
|
|
1241
|
+
import { execFileSync } from "child_process";
|
|
1242
|
+
function sendOsNotification(title, message) {
|
|
1243
|
+
try {
|
|
1244
|
+
if (process.platform === "darwin") {
|
|
1245
|
+
execFileSync("osascript", [
|
|
1246
|
+
"-e",
|
|
1247
|
+
`display notification "${message}" with title "${title}"`
|
|
1248
|
+
], { stdio: "ignore" });
|
|
1249
|
+
} else if (process.platform === "linux") {
|
|
1250
|
+
execFileSync("notify-send", [title, message], { stdio: "ignore" });
|
|
1251
|
+
} else if (process.platform === "win32") {
|
|
1252
|
+
const ps = [
|
|
1253
|
+
"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
|
|
1254
|
+
`$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)`,
|
|
1255
|
+
`$textNodes = $template.GetElementsByTagName('text')`,
|
|
1256
|
+
`$textNodes.Item(0).AppendChild($template.CreateTextNode('${title}')) | Out-Null`,
|
|
1257
|
+
`$textNodes.Item(1).AppendChild($template.CreateTextNode('${message}')) | Out-Null`,
|
|
1258
|
+
`$toast = [Windows.UI.Notifications.ToastNotification]::new($template)`,
|
|
1259
|
+
`[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('claudesync').Show($toast)`
|
|
1260
|
+
].join("; ");
|
|
1261
|
+
execFileSync("powershell", ["-Command", ps], { stdio: "ignore" });
|
|
1262
|
+
}
|
|
1263
|
+
} catch {
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function addPendingNotification(level, message) {
|
|
1267
|
+
const notifications = getPendingNotifications();
|
|
1268
|
+
notifications.push({ level, message, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1269
|
+
writeFileSync7(pendingNotificationsPath(), JSON.stringify(notifications, null, 2), "utf-8");
|
|
1270
|
+
}
|
|
1271
|
+
function getPendingNotifications() {
|
|
1272
|
+
const path = pendingNotificationsPath();
|
|
1273
|
+
if (!existsSync8(path)) return [];
|
|
1274
|
+
try {
|
|
1275
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
1276
|
+
} catch {
|
|
1277
|
+
return [];
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function clearPendingNotifications() {
|
|
1281
|
+
const path = pendingNotificationsPath();
|
|
1282
|
+
if (existsSync8(path)) unlinkSync4(path);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/commands/auto-run.ts
|
|
1286
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync4, existsSync as existsSync9, copyFileSync as copyFileSync3 } from "fs";
|
|
1287
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
1288
|
+
import { hostname as hostname2 } from "os";
|
|
1289
|
+
async function runAutoRun() {
|
|
1290
|
+
const autoConfig = loadAutoConfig();
|
|
1291
|
+
if (!autoConfig || !autoConfig.enabled) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const authConfig = loadConfig();
|
|
1295
|
+
if (!authConfig?.token || !authConfig.gist_id) {
|
|
1296
|
+
appendLog(autoConfig.direction, "error", "No auth config or gist_id");
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (!acquireLock()) {
|
|
1300
|
+
appendLog(autoConfig.direction, "skipped", "Lock held by another process");
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
try {
|
|
1304
|
+
if (autoConfig.direction === "push") {
|
|
1305
|
+
await autoRunPush(autoConfig, authConfig.token, authConfig.gist_id);
|
|
1306
|
+
} else {
|
|
1307
|
+
await autoRunPull(autoConfig, authConfig.token, authConfig.gist_id);
|
|
1308
|
+
}
|
|
1309
|
+
} catch (err) {
|
|
1310
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1311
|
+
appendLog(autoConfig.direction, "error", message);
|
|
1312
|
+
const failMsg = t("auto_run.failed").replace("{direction}", autoConfig.direction).replace("{message}", message);
|
|
1313
|
+
sendOsNotification("claudesync", failMsg);
|
|
1314
|
+
addPendingNotification("error", failMsg);
|
|
1315
|
+
} finally {
|
|
1316
|
+
releaseLock();
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
async function autoRunPush(autoConfig, token, gistId) {
|
|
1320
|
+
try {
|
|
1321
|
+
const gist = await getGist(token, gistId);
|
|
1322
|
+
const meta = parseMeta(gist);
|
|
1323
|
+
if (meta?.primary_device) {
|
|
1324
|
+
const primary = meta.primary_device;
|
|
1325
|
+
if (primary.machine_id !== getMachineId()) {
|
|
1326
|
+
appendLog("push", "skipped", `Not primary device (primary: ${primary.machine})`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
let files = scanFiles();
|
|
1333
|
+
files = files.filter((f) => autoConfig.categories.includes(f.category));
|
|
1334
|
+
if (files.length === 0) {
|
|
1335
|
+
appendLog("push", "skipped", "No files to sync");
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const processedFiles = files.map((file) => {
|
|
1339
|
+
const secrets = detectSecrets(file.content);
|
|
1340
|
+
if (secrets.length > 0) {
|
|
1341
|
+
return { ...file, content: redactContent(file.content, secrets) };
|
|
1342
|
+
}
|
|
1343
|
+
return file;
|
|
1344
|
+
});
|
|
1345
|
+
const encryptedFiles = /* @__PURE__ */ new Set();
|
|
1346
|
+
const finalFiles = autoConfig.encrypt ? processedFiles.map((f) => {
|
|
1347
|
+
encryptedFiles.add(f.gistFilename);
|
|
1348
|
+
return { ...f, content: encrypt(f.content, token) };
|
|
1349
|
+
}) : processedFiles;
|
|
1350
|
+
try {
|
|
1351
|
+
const gist = await getGist(token, gistId);
|
|
1352
|
+
const changes = compareForPush(finalFiles, gist);
|
|
1353
|
+
const modified = changes.filter((c2) => c2.status !== "unchanged");
|
|
1354
|
+
if (modified.length === 0) {
|
|
1355
|
+
appendLog("push", "skipped", "No changes detected");
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const deletedFiles = changes.filter((c2) => c2.status === "deleted").map((c2) => c2.gistFilename);
|
|
1359
|
+
const filesToUpload = finalFiles.filter(
|
|
1360
|
+
(f) => changes.some((c2) => c2.gistFilename === f.gistFilename && c2.status !== "unchanged")
|
|
1361
|
+
);
|
|
1362
|
+
const primaryDevice = {
|
|
1363
|
+
machine_id: getMachineId(),
|
|
1364
|
+
machine: machineName(),
|
|
1365
|
+
hostname: hostname2(),
|
|
1366
|
+
platform: platformString(),
|
|
1367
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1368
|
+
};
|
|
1369
|
+
await updateGist(token, gistId, filesToUpload, deletedFiles, encryptedFiles, "auto-sync push", primaryDevice);
|
|
1370
|
+
appendLog("push", "success", t("auto_run.push_synced").replace("{count}", String(modified.length)));
|
|
1371
|
+
} catch (err) {
|
|
1372
|
+
if (String(err).includes("404")) {
|
|
1373
|
+
const primaryDevice = {
|
|
1374
|
+
machine: machineName(),
|
|
1375
|
+
hostname: hostname2(),
|
|
1376
|
+
platform: platformString(),
|
|
1377
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1378
|
+
};
|
|
1379
|
+
const newGist = await createGist(token, finalFiles, encryptedFiles, "auto-sync push (new gist)", primaryDevice);
|
|
1380
|
+
appendLog("push", "success", `Created new gist: ${newGist.id}`);
|
|
1381
|
+
} else {
|
|
1382
|
+
throw err;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
async function autoRunPull(autoConfig, token, gistId) {
|
|
1387
|
+
const gist = await getGist(token, gistId);
|
|
1388
|
+
const localFiles = scanFiles();
|
|
1389
|
+
const filteredLocal = localFiles.filter((f) => autoConfig.categories.includes(f.category));
|
|
1390
|
+
const changes = compareForPull(gist, filteredLocal);
|
|
1391
|
+
const modified = changes.filter((c2) => c2.status !== "unchanged");
|
|
1392
|
+
const meta = parseMeta(gist);
|
|
1393
|
+
const toApply = modified.filter((c2) => autoConfig.categories.includes(c2.category));
|
|
1394
|
+
if (toApply.length === 0) {
|
|
1395
|
+
appendLog("pull", "skipped", "No changes detected");
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const base = claudeDir();
|
|
1399
|
+
let applied = 0;
|
|
1400
|
+
let skipped = 0;
|
|
1401
|
+
for (const change of toApply) {
|
|
1402
|
+
if (!isPathSafe(change.relativePath)) continue;
|
|
1403
|
+
if (change.status === "deleted") continue;
|
|
1404
|
+
const targetPath = join4(base, change.relativePath);
|
|
1405
|
+
let content = change.remoteContent ?? "";
|
|
1406
|
+
const metaEntry = meta?.file_map[change.gistFilename];
|
|
1407
|
+
const needsDecrypt = metaEntry?.encrypted || isEncrypted(content);
|
|
1408
|
+
if (needsDecrypt) {
|
|
1409
|
+
try {
|
|
1410
|
+
content = decrypt(content, token);
|
|
1411
|
+
} catch {
|
|
1412
|
+
appendLog("pull", "error", `Decrypt failed: ${change.relativePath}`);
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
if (change.status === "modified" && autoConfig.conflict_policy) {
|
|
1417
|
+
switch (autoConfig.conflict_policy) {
|
|
1418
|
+
case "skip":
|
|
1419
|
+
if (change.localContent && change.localContent !== content) {
|
|
1420
|
+
skipped++;
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
break;
|
|
1424
|
+
case "backup":
|
|
1425
|
+
if (existsSync9(targetPath)) {
|
|
1426
|
+
copyFileSync3(targetPath, targetPath + ".bak");
|
|
1427
|
+
}
|
|
1428
|
+
break;
|
|
1429
|
+
case "overwrite":
|
|
1430
|
+
break;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
const dir = dirname3(targetPath);
|
|
1434
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
1435
|
+
writeFileSync8(targetPath, content, "utf-8");
|
|
1436
|
+
applied++;
|
|
1437
|
+
}
|
|
1438
|
+
const message = skipped > 0 ? t("auto_run.pull_applied_skipped").replace("{applied}", String(applied)).replace("{skipped}", String(skipped)) : t("auto_run.pull_applied").replace("{applied}", String(applied));
|
|
1439
|
+
appendLog("pull", "success", message);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/commands/auto-disable.ts
|
|
1443
|
+
async function runAutoDisable() {
|
|
1444
|
+
const config = loadAutoConfig();
|
|
1445
|
+
if (!config) {
|
|
1446
|
+
warn(t("auto.not_configured"));
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
try {
|
|
1450
|
+
unregisterScheduler();
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
removeAutoConfig();
|
|
1454
|
+
success(t("auto.disabled"));
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// src/commands/auto-status.ts
|
|
1458
|
+
function formatInterval2(seconds) {
|
|
1459
|
+
if (seconds < 60) return t("auto.interval_seconds").replace("{n}", String(seconds));
|
|
1460
|
+
if (seconds < 3600) return t("auto.interval_minutes").replace("{n}", String(Math.round(seconds / 60)));
|
|
1461
|
+
if (seconds < 86400) return t("auto.interval_hours").replace("{n}", String(Math.round(seconds / 3600)));
|
|
1462
|
+
return t("auto.interval_days").replace("{n}", String(Math.round(seconds / 86400)));
|
|
1463
|
+
}
|
|
1464
|
+
async function runAutoStatus() {
|
|
1465
|
+
const autoConfig = loadAutoConfig();
|
|
1466
|
+
heading(t("auto.status_title"));
|
|
1467
|
+
if (!autoConfig) {
|
|
1468
|
+
warn(t("auto.not_configured"));
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
console.log(` ${c.bold(t("auto.status_label") + ":")} ${autoConfig.enabled ? c.green(t("auto.status_enabled")) : c.red(t("auto.status_disabled"))}`);
|
|
1472
|
+
console.log(` ${c.bold(t("auto.summary_direction") + ":")} ${autoConfig.direction}`);
|
|
1473
|
+
console.log(` ${c.bold(t("auto.summary_interval") + ":")} ${formatInterval2(autoConfig.interval_seconds)}`);
|
|
1474
|
+
console.log(` ${c.bold(t("auto.summary_categories") + ":")} ${autoConfig.categories.join(", ")}`);
|
|
1475
|
+
console.log(` ${c.bold(t("auto.summary_encrypt") + ":")} ${autoConfig.encrypt ? t("auto.yes") : t("auto.no")}`);
|
|
1476
|
+
if (autoConfig.conflict_policy) {
|
|
1477
|
+
console.log(` ${c.bold(t("auto.summary_conflict") + ":")} ${autoConfig.conflict_policy}`);
|
|
1478
|
+
}
|
|
1479
|
+
console.log(` ${c.bold(t("auto.created_label") + ":")} ${autoConfig.created_at}`);
|
|
1480
|
+
const authConfig = loadConfig();
|
|
1481
|
+
if (authConfig?.token && authConfig.gist_id) {
|
|
1482
|
+
try {
|
|
1483
|
+
const gist = await getGist(authConfig.token, authConfig.gist_id);
|
|
1484
|
+
const meta = parseMeta(gist);
|
|
1485
|
+
if (meta?.primary_device) {
|
|
1486
|
+
const pd = meta.primary_device;
|
|
1487
|
+
const isMe = pd.machine_id === getMachineId();
|
|
1488
|
+
console.log();
|
|
1489
|
+
console.log(` ${c.bold(t("auto.primary_device_label") + ":")}`);
|
|
1490
|
+
console.log(` ${t("status.machine")}: ${pd.machine} ${isMe ? c.green(t("auto.this_machine")) : ""}`);
|
|
1491
|
+
console.log(` ${t("status.host")}: ${pd.hostname}`);
|
|
1492
|
+
console.log(` ${t("status.platform")}: ${pd.platform}`);
|
|
1493
|
+
console.log(` ${t("auto.since_label")}: ${pd.registered_at}`);
|
|
1494
|
+
}
|
|
1495
|
+
} catch {
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const logs = readRecentLogs(5);
|
|
1499
|
+
if (logs.length > 0) {
|
|
1500
|
+
console.log();
|
|
1501
|
+
console.log(` ${c.bold(t("auto.recent_logs_label") + ":")}`);
|
|
1502
|
+
for (const log of logs) {
|
|
1503
|
+
const colored = log.replace(/\[success\]/g, c.green("[success]")).replace(/\[error\]/g, c.red("[error]")).replace(/\[skipped\]/g, c.yellow("[skipped]"));
|
|
1504
|
+
console.log(` ${colored}`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// src/cli.ts
|
|
1510
|
+
var VERSION = "0.1.0";
|
|
1511
|
+
function parseArgs(argv) {
|
|
1512
|
+
const args = argv.slice(2);
|
|
1513
|
+
const flags = {};
|
|
1514
|
+
const positional = [];
|
|
1515
|
+
let command;
|
|
1516
|
+
for (let i = 0; i < args.length; i++) {
|
|
1517
|
+
const arg = args[i];
|
|
1518
|
+
if (arg.startsWith("--")) {
|
|
1519
|
+
const eqIdx = arg.indexOf("=");
|
|
1520
|
+
if (eqIdx !== -1) {
|
|
1521
|
+
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
1522
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1523
|
+
flags[arg.slice(2)] = args[i + 1];
|
|
1524
|
+
i++;
|
|
1525
|
+
} else {
|
|
1526
|
+
flags[arg.slice(2)] = true;
|
|
1527
|
+
}
|
|
1528
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
1529
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
1530
|
+
flags[arg.slice(1)] = args[i + 1];
|
|
1531
|
+
i++;
|
|
1532
|
+
} else {
|
|
1533
|
+
flags[arg.slice(1)] = true;
|
|
1534
|
+
}
|
|
1535
|
+
} else {
|
|
1536
|
+
if (!command) {
|
|
1537
|
+
command = arg;
|
|
1538
|
+
} else {
|
|
1539
|
+
positional.push(arg);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return { command, flags, positional };
|
|
1544
|
+
}
|
|
1545
|
+
function printHelp() {
|
|
1546
|
+
console.log(`
|
|
1547
|
+
${t("app.name")} v${VERSION}
|
|
1548
|
+
${t("app.description")}
|
|
1549
|
+
|
|
1550
|
+
${t("help.usage")}
|
|
1551
|
+
|
|
1552
|
+
${t("help.commands")}
|
|
1553
|
+
init ${t("help.init")}
|
|
1554
|
+
init --token ${t("help.init_token")}
|
|
1555
|
+
link <gist-id> ${t("help.link")}
|
|
1556
|
+
push ${t("help.push")}
|
|
1557
|
+
pull ${t("help.pull")}
|
|
1558
|
+
diff ${t("help.diff")}
|
|
1559
|
+
status ${t("help.status")}
|
|
1560
|
+
list ${t("help.list")}
|
|
1561
|
+
history ${t("help.history")}
|
|
1562
|
+
rollback <version> ${t("help.rollback")}
|
|
1563
|
+
auto ${t("help.auto")}
|
|
1564
|
+
auto disable ${t("help.auto_disable")}
|
|
1565
|
+
auto status ${t("help.auto_status")}
|
|
1566
|
+
config <key> <value> ${t("help.config")}
|
|
1567
|
+
|
|
1568
|
+
${t("help.options")}
|
|
1569
|
+
-m, --message <msg> ${t("help.opt_message")}
|
|
1570
|
+
--only <category> ${t("help.opt_only")}
|
|
1571
|
+
--force ${t("help.opt_force")}
|
|
1572
|
+
--encrypt ${t("help.opt_encrypt")}
|
|
1573
|
+
--lang=ko|en ${t("help.opt_lang")}
|
|
1574
|
+
-h, --help ${t("help.opt_help")}
|
|
1575
|
+
-v, --version ${t("help.opt_version")}
|
|
1576
|
+
`);
|
|
1577
|
+
}
|
|
1578
|
+
function parseCategory(value) {
|
|
1579
|
+
if (typeof value === "string" && CATEGORIES.includes(value)) {
|
|
1580
|
+
return value;
|
|
1581
|
+
}
|
|
1582
|
+
return void 0;
|
|
1583
|
+
}
|
|
1584
|
+
async function main() {
|
|
1585
|
+
const { command, flags, positional } = parseArgs(process.argv);
|
|
1586
|
+
const langFlag = flags["lang"];
|
|
1587
|
+
if (langFlag === "ko" || langFlag === "en") {
|
|
1588
|
+
setLang(langFlag);
|
|
1589
|
+
} else {
|
|
1590
|
+
const saved = loadLangConfig();
|
|
1591
|
+
setLang(saved ?? detectLang());
|
|
1592
|
+
}
|
|
1593
|
+
if (flags["v"] || flags["version"]) {
|
|
1594
|
+
console.log(VERSION);
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (!command || flags["h"] || flags["help"]) {
|
|
1598
|
+
printHelp();
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const only = parseCategory(flags["only"]);
|
|
1602
|
+
const force = !!flags["force"];
|
|
1603
|
+
const encrypt2 = !!flags["encrypt"];
|
|
1604
|
+
const message = typeof flags["m"] === "string" ? flags["m"] : typeof flags["message"] === "string" ? flags["message"] : void 0;
|
|
1605
|
+
try {
|
|
1606
|
+
if (command !== "auto-run") {
|
|
1607
|
+
const notifications = getPendingNotifications();
|
|
1608
|
+
if (notifications.length > 0) {
|
|
1609
|
+
for (const n of notifications) {
|
|
1610
|
+
if (n.level === "error") error(n.message);
|
|
1611
|
+
else if (n.level === "warning") warn(n.message);
|
|
1612
|
+
else info(n.message);
|
|
1613
|
+
}
|
|
1614
|
+
clearPendingNotifications();
|
|
1615
|
+
console.log();
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
switch (command) {
|
|
1619
|
+
case "init":
|
|
1620
|
+
await runInit({ useToken: !!flags["token"] });
|
|
1621
|
+
break;
|
|
1622
|
+
case "link":
|
|
1623
|
+
if (!positional[0]) {
|
|
1624
|
+
error(t("cli.link_usage"));
|
|
1625
|
+
process.exit(1);
|
|
1626
|
+
}
|
|
1627
|
+
await runInit({ linkGistId: positional[0] });
|
|
1628
|
+
break;
|
|
1629
|
+
case "push":
|
|
1630
|
+
await runPush({ only, force, encrypt: encrypt2, message });
|
|
1631
|
+
break;
|
|
1632
|
+
case "pull":
|
|
1633
|
+
await runPull({ only, force });
|
|
1634
|
+
break;
|
|
1635
|
+
case "diff":
|
|
1636
|
+
await runDiff({ only });
|
|
1637
|
+
break;
|
|
1638
|
+
case "status":
|
|
1639
|
+
await runStatus();
|
|
1640
|
+
break;
|
|
1641
|
+
case "list": {
|
|
1642
|
+
const { scanFiles: scanFiles2 } = await import("./scanner-M3SQGNTI.js");
|
|
1643
|
+
const files = scanFiles2(only);
|
|
1644
|
+
if (files.length === 0) {
|
|
1645
|
+
console.log(t("cli.list_empty"));
|
|
1646
|
+
} else {
|
|
1647
|
+
for (const f of files) {
|
|
1648
|
+
console.log(` [${f.category}] ${f.relativePath}`);
|
|
1649
|
+
}
|
|
1650
|
+
console.log(`
|
|
1651
|
+
${t("cli.list_total").replace("{count}", String(files.length))}`);
|
|
1652
|
+
}
|
|
1653
|
+
break;
|
|
1654
|
+
}
|
|
1655
|
+
case "history":
|
|
1656
|
+
await runHistory();
|
|
1657
|
+
break;
|
|
1658
|
+
case "rollback":
|
|
1659
|
+
if (!positional[0]) {
|
|
1660
|
+
error(t("cli.rollback_usage"));
|
|
1661
|
+
process.exit(1);
|
|
1662
|
+
}
|
|
1663
|
+
await runRollback(positional[0]);
|
|
1664
|
+
break;
|
|
1665
|
+
case "auto":
|
|
1666
|
+
if (positional[0] === "disable") {
|
|
1667
|
+
await runAutoDisable();
|
|
1668
|
+
} else if (positional[0] === "status") {
|
|
1669
|
+
await runAutoStatus();
|
|
1670
|
+
} else {
|
|
1671
|
+
await runAuto();
|
|
1672
|
+
}
|
|
1673
|
+
break;
|
|
1674
|
+
case "config": {
|
|
1675
|
+
const { runConfig } = await import("./config-7KJ6CZMX.js");
|
|
1676
|
+
runConfig(positional);
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
case "auto-run":
|
|
1680
|
+
await runAutoRun();
|
|
1681
|
+
break;
|
|
1682
|
+
default:
|
|
1683
|
+
error(t("cli.unknown_command").replace("{command}", command));
|
|
1684
|
+
printHelp();
|
|
1685
|
+
process.exit(1);
|
|
1686
|
+
}
|
|
1687
|
+
} catch (err) {
|
|
1688
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1689
|
+
process.exit(1);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
main();
|
|
1693
|
+
//# sourceMappingURL=cli.js.map
|