ccsini 0.1.52 → 0.1.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2218 -2180
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28020,7 +28020,7 @@ var {
|
|
|
28020
28020
|
} = import__.default;
|
|
28021
28021
|
|
|
28022
28022
|
// src/version.ts
|
|
28023
|
-
var VERSION = "0.1.
|
|
28023
|
+
var VERSION = "0.1.54";
|
|
28024
28024
|
|
|
28025
28025
|
// src/commands/init.ts
|
|
28026
28026
|
init_source();
|
|
@@ -29755,6 +29755,7 @@ async function uninstallLinux() {
|
|
|
29755
29755
|
}
|
|
29756
29756
|
|
|
29757
29757
|
// src/commands/init.ts
|
|
29758
|
+
import { spawn as spawn2 } from "child_process";
|
|
29758
29759
|
function registerInitCommand(program2) {
|
|
29759
29760
|
program2.command("init").description("Connect this device to your ccsini account").option("--token <token>", "Setup token from dashboard").action(async (opts) => {
|
|
29760
29761
|
const configDir = getConfigDir();
|
|
@@ -29933,12 +29934,25 @@ function registerInitCommand(program2) {
|
|
|
29933
29934
|
try {
|
|
29934
29935
|
await installHeartbeatScheduler();
|
|
29935
29936
|
} catch {}
|
|
29937
|
+
saveSpinner.text = "Starting background daemon...";
|
|
29938
|
+
let daemonStarted = false;
|
|
29939
|
+
try {
|
|
29940
|
+
const isWin = platform2() === "win32";
|
|
29941
|
+
const child = spawn2("ccsini", ["daemon", "_run"], {
|
|
29942
|
+
detached: true,
|
|
29943
|
+
stdio: "ignore",
|
|
29944
|
+
...isWin ? { shell: true } : {}
|
|
29945
|
+
});
|
|
29946
|
+
child.unref();
|
|
29947
|
+
daemonStarted = true;
|
|
29948
|
+
} catch {}
|
|
29936
29949
|
saveSpinner.succeed("Setup complete!");
|
|
29937
29950
|
console.log(source_default.green(`
|
|
29938
29951
|
\u2713 Encryption keys generated`));
|
|
29939
29952
|
console.log(source_default.green(" \u2713 Device registered"));
|
|
29940
29953
|
console.log(source_default.green(" \u2713 Claude Code hooks installed"));
|
|
29941
29954
|
console.log(source_default.green(" \u2713 Heartbeat scheduler installed"));
|
|
29955
|
+
console.log(source_default.green(daemonStarted ? " \u2713 Background daemon started" : " \u2717 Daemon not started (run 'ccsini daemon start')"));
|
|
29942
29956
|
console.log(source_default.green(" \u2713 Auto-sync is active"));
|
|
29943
29957
|
console.log(source_default.dim(`
|
|
29944
29958
|
Just use 'claude' as normal. Sync happens automatically.
|
|
@@ -29962,1065 +29976,283 @@ function detectPlatform() {
|
|
|
29962
29976
|
init_auth();
|
|
29963
29977
|
init_auth();
|
|
29964
29978
|
init_src();
|
|
29979
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
29980
|
+
import { join as join12 } from "path";
|
|
29981
|
+
import { platform as platform4 } from "os";
|
|
29982
|
+
import { spawn as spawn3 } from "child_process";
|
|
29983
|
+
|
|
29984
|
+
// src/daemon/runner.ts
|
|
29965
29985
|
import { readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
29966
|
-
import { join as
|
|
29967
|
-
|
|
29968
|
-
|
|
29969
|
-
|
|
29970
|
-
|
|
29971
|
-
|
|
29972
|
-
|
|
29973
|
-
|
|
29986
|
+
import { join as join11 } from "path";
|
|
29987
|
+
import { platform as platform3 } from "os";
|
|
29988
|
+
import { appendFileSync } from "fs";
|
|
29989
|
+
init_auth();
|
|
29990
|
+
|
|
29991
|
+
// ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
29992
|
+
import { EventEmitter } from "events";
|
|
29993
|
+
import { stat as statcb, Stats } from "fs";
|
|
29994
|
+
import { readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
29995
|
+
import * as sp2 from "path";
|
|
29996
|
+
|
|
29997
|
+
// ../../node_modules/.bun/readdirp@5.0.0/node_modules/readdirp/index.js
|
|
29998
|
+
import { lstat, readdir as readdir2, realpath, stat as stat2 } from "fs/promises";
|
|
29999
|
+
import { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from "path";
|
|
30000
|
+
import { Readable } from "stream";
|
|
30001
|
+
var EntryTypes = {
|
|
30002
|
+
FILE_TYPE: "files",
|
|
30003
|
+
DIR_TYPE: "directories",
|
|
30004
|
+
FILE_DIR_TYPE: "files_directories",
|
|
30005
|
+
EVERYTHING_TYPE: "all"
|
|
30006
|
+
};
|
|
30007
|
+
var defaultOptions = {
|
|
30008
|
+
root: ".",
|
|
30009
|
+
fileFilter: (_entryInfo) => true,
|
|
30010
|
+
directoryFilter: (_entryInfo) => true,
|
|
30011
|
+
type: EntryTypes.FILE_TYPE,
|
|
30012
|
+
lstat: false,
|
|
30013
|
+
depth: 2147483648,
|
|
30014
|
+
alwaysStat: false,
|
|
30015
|
+
highWaterMark: 4096
|
|
30016
|
+
};
|
|
30017
|
+
Object.freeze(defaultOptions);
|
|
30018
|
+
var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
|
|
30019
|
+
var NORMAL_FLOW_ERRORS = new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
|
|
30020
|
+
var ALL_TYPES = [
|
|
30021
|
+
EntryTypes.DIR_TYPE,
|
|
30022
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
30023
|
+
EntryTypes.FILE_DIR_TYPE,
|
|
30024
|
+
EntryTypes.FILE_TYPE
|
|
30025
|
+
];
|
|
30026
|
+
var DIR_TYPES = new Set([
|
|
30027
|
+
EntryTypes.DIR_TYPE,
|
|
30028
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
30029
|
+
EntryTypes.FILE_DIR_TYPE
|
|
30030
|
+
]);
|
|
30031
|
+
var FILE_TYPES = new Set([
|
|
30032
|
+
EntryTypes.EVERYTHING_TYPE,
|
|
30033
|
+
EntryTypes.FILE_DIR_TYPE,
|
|
30034
|
+
EntryTypes.FILE_TYPE
|
|
30035
|
+
]);
|
|
30036
|
+
var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
|
|
30037
|
+
var wantBigintFsStats = process.platform === "win32";
|
|
30038
|
+
var emptyFn = (_entryInfo) => true;
|
|
30039
|
+
var normalizeFilter = (filter2) => {
|
|
30040
|
+
if (filter2 === undefined)
|
|
30041
|
+
return emptyFn;
|
|
30042
|
+
if (typeof filter2 === "function")
|
|
30043
|
+
return filter2;
|
|
30044
|
+
if (typeof filter2 === "string") {
|
|
30045
|
+
const fl = filter2.trim();
|
|
30046
|
+
return (entry) => entry.basename === fl;
|
|
29974
30047
|
}
|
|
29975
|
-
|
|
29976
|
-
|
|
29977
|
-
|
|
29978
|
-
|
|
29979
|
-
|
|
29980
|
-
|
|
29981
|
-
|
|
29982
|
-
|
|
29983
|
-
|
|
29984
|
-
|
|
29985
|
-
|
|
29986
|
-
|
|
29987
|
-
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
29996
|
-
|
|
29997
|
-
|
|
29998
|
-
|
|
29999
|
-
|
|
30000
|
-
|
|
30001
|
-
|
|
30002
|
-
|
|
30003
|
-
|
|
30004
|
-
}
|
|
30005
|
-
|
|
30006
|
-
|
|
30007
|
-
const
|
|
30008
|
-
if (
|
|
30048
|
+
if (Array.isArray(filter2)) {
|
|
30049
|
+
const trItems = filter2.map((item) => item.trim());
|
|
30050
|
+
return (entry) => trItems.some((f) => entry.basename === f);
|
|
30051
|
+
}
|
|
30052
|
+
return emptyFn;
|
|
30053
|
+
};
|
|
30054
|
+
|
|
30055
|
+
class ReaddirpStream extends Readable {
|
|
30056
|
+
parents;
|
|
30057
|
+
reading;
|
|
30058
|
+
parent;
|
|
30059
|
+
_stat;
|
|
30060
|
+
_maxDepth;
|
|
30061
|
+
_wantsDir;
|
|
30062
|
+
_wantsFile;
|
|
30063
|
+
_wantsEverything;
|
|
30064
|
+
_root;
|
|
30065
|
+
_isDirent;
|
|
30066
|
+
_statsProp;
|
|
30067
|
+
_rdOptions;
|
|
30068
|
+
_fileFilter;
|
|
30069
|
+
_directoryFilter;
|
|
30070
|
+
constructor(options = {}) {
|
|
30071
|
+
super({
|
|
30072
|
+
objectMode: true,
|
|
30073
|
+
autoDestroy: true,
|
|
30074
|
+
highWaterMark: options.highWaterMark
|
|
30075
|
+
});
|
|
30076
|
+
const opts = { ...defaultOptions, ...options };
|
|
30077
|
+
const { root, type } = opts;
|
|
30078
|
+
this._fileFilter = normalizeFilter(opts.fileFilter);
|
|
30079
|
+
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
30080
|
+
const statMethod = opts.lstat ? lstat : stat2;
|
|
30081
|
+
if (wantBigintFsStats) {
|
|
30082
|
+
this._stat = (path2) => statMethod(path2, { bigint: true });
|
|
30083
|
+
} else {
|
|
30084
|
+
this._stat = statMethod;
|
|
30085
|
+
}
|
|
30086
|
+
this._maxDepth = opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
|
|
30087
|
+
this._wantsDir = type ? DIR_TYPES.has(type) : false;
|
|
30088
|
+
this._wantsFile = type ? FILE_TYPES.has(type) : false;
|
|
30089
|
+
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
|
|
30090
|
+
this._root = presolve(root);
|
|
30091
|
+
this._isDirent = !opts.alwaysStat;
|
|
30092
|
+
this._statsProp = this._isDirent ? "dirent" : "stats";
|
|
30093
|
+
this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
|
|
30094
|
+
this.parents = [this._exploreDir(root, 1)];
|
|
30095
|
+
this.reading = false;
|
|
30096
|
+
this.parent = undefined;
|
|
30097
|
+
}
|
|
30098
|
+
async _read(batch) {
|
|
30099
|
+
if (this.reading)
|
|
30009
30100
|
return;
|
|
30101
|
+
this.reading = true;
|
|
30010
30102
|
try {
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30021
|
-
|
|
30022
|
-
|
|
30023
|
-
|
|
30024
|
-
|
|
30103
|
+
while (!this.destroyed && batch > 0) {
|
|
30104
|
+
const par = this.parent;
|
|
30105
|
+
const fil = par && par.files;
|
|
30106
|
+
if (fil && fil.length > 0) {
|
|
30107
|
+
const { path: path2, depth } = par;
|
|
30108
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path2));
|
|
30109
|
+
const awaited = await Promise.all(slice);
|
|
30110
|
+
for (const entry of awaited) {
|
|
30111
|
+
if (!entry)
|
|
30112
|
+
continue;
|
|
30113
|
+
if (this.destroyed)
|
|
30114
|
+
return;
|
|
30115
|
+
const entryType = await this._getEntryType(entry);
|
|
30116
|
+
if (entryType === "directory" && this._directoryFilter(entry)) {
|
|
30117
|
+
if (depth <= this._maxDepth) {
|
|
30118
|
+
this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
|
|
30119
|
+
}
|
|
30120
|
+
if (this._wantsDir) {
|
|
30121
|
+
this.push(entry);
|
|
30122
|
+
batch--;
|
|
30123
|
+
}
|
|
30124
|
+
} else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
|
|
30125
|
+
if (this._wantsFile) {
|
|
30126
|
+
this.push(entry);
|
|
30127
|
+
batch--;
|
|
30128
|
+
}
|
|
30129
|
+
}
|
|
30130
|
+
}
|
|
30131
|
+
} else {
|
|
30132
|
+
const parent = this.parents.pop();
|
|
30133
|
+
if (!parent) {
|
|
30134
|
+
this.push(null);
|
|
30135
|
+
break;
|
|
30136
|
+
}
|
|
30137
|
+
this.parent = await parent;
|
|
30138
|
+
if (this.destroyed)
|
|
30139
|
+
return;
|
|
30025
30140
|
}
|
|
30026
|
-
console.log(msg);
|
|
30027
|
-
}
|
|
30028
|
-
if (result.conflicts.length > 0) {
|
|
30029
|
-
console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
|
|
30030
30141
|
}
|
|
30031
|
-
} catch {
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
|
|
30035
|
-
if (!await configExists(configDir)) {
|
|
30036
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30037
|
-
process.exit(1);
|
|
30142
|
+
} catch (error) {
|
|
30143
|
+
this.destroy(error);
|
|
30144
|
+
} finally {
|
|
30145
|
+
this.reading = false;
|
|
30038
30146
|
}
|
|
30039
|
-
|
|
30040
|
-
|
|
30041
|
-
|
|
30042
|
-
type: "password",
|
|
30043
|
-
name: "password",
|
|
30044
|
-
message: "Encryption password:",
|
|
30045
|
-
mask: "*"
|
|
30046
|
-
}
|
|
30047
|
-
]);
|
|
30048
|
-
const config = await loadKeys(configDir);
|
|
30049
|
-
const masterKey = await deriveKeyFromPassphrase(password, config.salt);
|
|
30050
|
-
await writeFile8(join9(configDir, ".cached-key"), masterKey, {
|
|
30051
|
-
mode: 384
|
|
30052
|
-
});
|
|
30053
|
-
console.log("Unlocked. Auto-sync is now active.");
|
|
30054
|
-
});
|
|
30055
|
-
program2.command("heartbeat").description("Send heartbeat to keep device online (runs via scheduled task)").action(async () => {
|
|
30056
|
-
const configDir = getConfigDir();
|
|
30057
|
-
if (!await configExists(configDir))
|
|
30058
|
-
return;
|
|
30059
|
-
try {
|
|
30060
|
-
const client = await getAuthenticatedClient(configDir);
|
|
30061
|
-
await client.heartbeat();
|
|
30062
|
-
} catch {}
|
|
30063
|
-
});
|
|
30064
|
-
program2.command("lock").description("Remove cached encryption key").action(async () => {
|
|
30065
|
-
const configDir = getConfigDir();
|
|
30066
|
-
const { rm } = await import("fs/promises");
|
|
30147
|
+
}
|
|
30148
|
+
async _exploreDir(path2, depth) {
|
|
30149
|
+
let files;
|
|
30067
30150
|
try {
|
|
30068
|
-
await
|
|
30069
|
-
|
|
30070
|
-
|
|
30071
|
-
console.log("Already locked.");
|
|
30151
|
+
files = await readdir2(path2, this._rdOptions);
|
|
30152
|
+
} catch (error) {
|
|
30153
|
+
this._onError(error);
|
|
30072
30154
|
}
|
|
30073
|
-
|
|
30074
|
-
}
|
|
30075
|
-
|
|
30076
|
-
|
|
30077
|
-
|
|
30078
|
-
import { access as access2 } from "fs/promises";
|
|
30079
|
-
import { join as join10 } from "path";
|
|
30080
|
-
function registerDoctorCommand(program2) {
|
|
30081
|
-
program2.command("doctor").description("Diagnose ccsini setup and connectivity").action(async () => {
|
|
30082
|
-
const configDir = getConfigDir();
|
|
30083
|
-
const claudeDir = getClaudeDir();
|
|
30084
|
-
let allGood = true;
|
|
30085
|
-
console.log(source_default.bold(`
|
|
30086
|
-
ccsini doctor
|
|
30087
|
-
`));
|
|
30155
|
+
return { files, depth, path: path2 };
|
|
30156
|
+
}
|
|
30157
|
+
async _formatEntry(dirent, path2) {
|
|
30158
|
+
let entry;
|
|
30159
|
+
const basename = this._isDirent ? dirent.name : dirent;
|
|
30088
30160
|
try {
|
|
30089
|
-
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30093
|
-
|
|
30094
|
-
}
|
|
30095
|
-
if (await configExists(configDir)) {
|
|
30096
|
-
console.log(source_default.green(" \u2713 ccsini initialized"));
|
|
30097
|
-
} else {
|
|
30098
|
-
console.log(source_default.red(" \u2717 ccsini not initialized (run 'ccsini init')"));
|
|
30099
|
-
allGood = false;
|
|
30161
|
+
const fullPath = presolve(pjoin(path2, basename));
|
|
30162
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename };
|
|
30163
|
+
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
30164
|
+
} catch (err) {
|
|
30165
|
+
this._onError(err);
|
|
30100
30166
|
return;
|
|
30101
30167
|
}
|
|
30102
|
-
|
|
30103
|
-
|
|
30104
|
-
|
|
30105
|
-
|
|
30106
|
-
|
|
30107
|
-
if (healthy) {
|
|
30108
|
-
console.log(source_default.green(" \u2713 Hooks format valid"));
|
|
30109
|
-
} else {
|
|
30110
|
-
for (const issue of issues) {
|
|
30111
|
-
console.log(source_default.red(` \u2717 ${issue}`));
|
|
30112
|
-
}
|
|
30113
|
-
console.log(source_default.yellow(" Run 'ccsini hooks fix' to repair"));
|
|
30114
|
-
allGood = false;
|
|
30115
|
-
}
|
|
30168
|
+
return entry;
|
|
30169
|
+
}
|
|
30170
|
+
_onError(err) {
|
|
30171
|
+
if (isNormalFlowError(err) && !this.destroyed) {
|
|
30172
|
+
this.emit("warn", err);
|
|
30116
30173
|
} else {
|
|
30117
|
-
|
|
30118
|
-
allGood = false;
|
|
30119
|
-
}
|
|
30120
|
-
try {
|
|
30121
|
-
await access2(join10(configDir, ".cached-key"));
|
|
30122
|
-
console.log(source_default.green(" \u2713 Auto-sync unlocked"));
|
|
30123
|
-
} catch {
|
|
30124
|
-
console.log(source_default.yellow(" \u26A0 Auto-sync locked (run 'ccsini unlock')"));
|
|
30125
|
-
}
|
|
30126
|
-
try {
|
|
30127
|
-
const start = Date.now();
|
|
30128
|
-
const res = await fetch(`${config.apiUrl}/health`);
|
|
30129
|
-
const latency = Date.now() - start;
|
|
30130
|
-
if (res.ok) {
|
|
30131
|
-
console.log(source_default.green(` \u2713 Server reachable (${latency}ms)`));
|
|
30132
|
-
} else {
|
|
30133
|
-
console.log(source_default.red(" \u2717 Server returned error"));
|
|
30134
|
-
allGood = false;
|
|
30135
|
-
}
|
|
30136
|
-
} catch {
|
|
30137
|
-
console.log(source_default.red(" \u2717 Cannot reach server"));
|
|
30138
|
-
allGood = false;
|
|
30174
|
+
this.destroy(err);
|
|
30139
30175
|
}
|
|
30140
|
-
console.log(allGood ? source_default.green(`
|
|
30141
|
-
Everything looks good!
|
|
30142
|
-
`) : source_default.yellow(`
|
|
30143
|
-
Some issues found. See above.
|
|
30144
|
-
`));
|
|
30145
|
-
});
|
|
30146
|
-
}
|
|
30147
|
-
|
|
30148
|
-
// src/commands/self.ts
|
|
30149
|
-
import { execSync as execSync2 } from "child_process";
|
|
30150
|
-
function getLatestVersion() {
|
|
30151
|
-
try {
|
|
30152
|
-
const result = execSync2("npm view ccsini version", { encoding: "utf-8" }).trim();
|
|
30153
|
-
return result;
|
|
30154
|
-
} catch {
|
|
30155
|
-
return null;
|
|
30156
30176
|
}
|
|
30157
|
-
|
|
30158
|
-
|
|
30159
|
-
|
|
30160
|
-
console.log(`Current version: ${VERSION}`);
|
|
30161
|
-
const latest = getLatestVersion();
|
|
30162
|
-
if (!latest) {
|
|
30163
|
-
console.error("Failed to check latest version. Check your internet connection.");
|
|
30164
|
-
process.exit(1);
|
|
30165
|
-
}
|
|
30166
|
-
if (latest === VERSION) {
|
|
30167
|
-
console.log(`Already on the latest version (${VERSION})`);
|
|
30168
|
-
return;
|
|
30177
|
+
async _getEntryType(entry) {
|
|
30178
|
+
if (!entry && this._statsProp in entry) {
|
|
30179
|
+
return "";
|
|
30169
30180
|
}
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30173
|
-
|
|
30174
|
-
|
|
30175
|
-
|
|
30176
|
-
|
|
30177
|
-
} catch {
|
|
30178
|
-
console.log(`Bun failed (registry cache), trying npm...
|
|
30179
|
-
`);
|
|
30181
|
+
const stats = entry[this._statsProp];
|
|
30182
|
+
if (stats.isFile())
|
|
30183
|
+
return "file";
|
|
30184
|
+
if (stats.isDirectory())
|
|
30185
|
+
return "directory";
|
|
30186
|
+
if (stats && stats.isSymbolicLink()) {
|
|
30187
|
+
const full = entry.fullPath;
|
|
30180
30188
|
try {
|
|
30181
|
-
|
|
30182
|
-
|
|
30183
|
-
|
|
30184
|
-
|
|
30185
|
-
|
|
30186
|
-
|
|
30187
|
-
|
|
30188
|
-
|
|
30189
|
-
|
|
30190
|
-
|
|
30191
|
-
|
|
30192
|
-
|
|
30193
|
-
|
|
30194
|
-
|
|
30195
|
-
|
|
30196
|
-
|
|
30189
|
+
const entryRealPath = await realpath(full);
|
|
30190
|
+
const entryRealPathStats = await lstat(entryRealPath);
|
|
30191
|
+
if (entryRealPathStats.isFile()) {
|
|
30192
|
+
return "file";
|
|
30193
|
+
}
|
|
30194
|
+
if (entryRealPathStats.isDirectory()) {
|
|
30195
|
+
const len = entryRealPath.length;
|
|
30196
|
+
if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
|
|
30197
|
+
const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
|
|
30198
|
+
recursiveError.code = RECURSIVE_ERROR_CODE;
|
|
30199
|
+
return this._onError(recursiveError);
|
|
30200
|
+
}
|
|
30201
|
+
return "directory";
|
|
30202
|
+
}
|
|
30203
|
+
} catch (error) {
|
|
30204
|
+
this._onError(error);
|
|
30205
|
+
return "";
|
|
30197
30206
|
}
|
|
30198
|
-
} catch {}
|
|
30199
|
-
console.log("Restart your terminal to use the new version.");
|
|
30200
|
-
});
|
|
30201
|
-
program2.command("uninstall").description("Uninstall ccsini from this machine").action(async () => {
|
|
30202
|
-
console.log(`Uninstalling ccsini...
|
|
30203
|
-
`);
|
|
30204
|
-
try {
|
|
30205
|
-
await uninstallHeartbeatScheduler();
|
|
30206
|
-
} catch {}
|
|
30207
|
-
let removed = false;
|
|
30208
|
-
try {
|
|
30209
|
-
execSync2("bun remove -g ccsini", { stdio: "inherit" });
|
|
30210
|
-
removed = true;
|
|
30211
|
-
} catch {}
|
|
30212
|
-
try {
|
|
30213
|
-
execSync2("npm uninstall -g ccsini", { stdio: "inherit" });
|
|
30214
|
-
removed = true;
|
|
30215
|
-
} catch {}
|
|
30216
|
-
if (removed) {
|
|
30217
|
-
console.log(`
|
|
30218
|
-
ccsini has been uninstalled. Bye!`);
|
|
30219
|
-
} else {
|
|
30220
|
-
console.error(`
|
|
30221
|
-
Uninstall failed. Try manually:`);
|
|
30222
|
-
console.error(" npm uninstall -g ccsini");
|
|
30223
|
-
process.exit(1);
|
|
30224
30207
|
}
|
|
30225
|
-
}
|
|
30226
|
-
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
init_auth();
|
|
30230
|
-
init_src();
|
|
30231
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
30232
|
-
import { join as join11 } from "path";
|
|
30233
|
-
async function getMasterKey2(configDir) {
|
|
30234
|
-
const cachedKeyPath = join11(configDir, ".cached-key");
|
|
30235
|
-
try {
|
|
30236
|
-
const cached = await readFile8(cachedKeyPath);
|
|
30237
|
-
return new Uint8Array(cached);
|
|
30238
|
-
} catch {
|
|
30239
|
-
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
30240
|
-
const { password } = await inquirer2.default.prompt([
|
|
30241
|
-
{
|
|
30242
|
-
type: "password",
|
|
30243
|
-
name: "password",
|
|
30244
|
-
message: "Encryption password:",
|
|
30245
|
-
mask: "*"
|
|
30246
|
-
}
|
|
30247
|
-
]);
|
|
30248
|
-
const config = await loadKeys(configDir);
|
|
30249
|
-
const masterKey = await deriveKeyFromPassphrase(password, config.salt);
|
|
30250
|
-
const { writeFile: writeFile9 } = await import("fs/promises");
|
|
30251
|
-
await writeFile9(cachedKeyPath, masterKey, { mode: 384 });
|
|
30252
|
-
return masterKey;
|
|
30208
|
+
}
|
|
30209
|
+
_includeAsFile(entry) {
|
|
30210
|
+
const stats = entry && entry[this._statsProp];
|
|
30211
|
+
return stats && this._wantsEverything && !stats.isDirectory();
|
|
30253
30212
|
}
|
|
30254
30213
|
}
|
|
30255
|
-
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
|
|
30259
|
-
|
|
30260
|
-
|
|
30261
|
-
|
|
30262
|
-
|
|
30263
|
-
|
|
30264
|
-
|
|
30265
|
-
|
|
30266
|
-
|
|
30267
|
-
}
|
|
30268
|
-
|
|
30269
|
-
|
|
30270
|
-
syncCmd.command("push").description("Push local changes to cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
30271
|
-
const configDir = getConfigDir();
|
|
30272
|
-
if (!await configExists(configDir)) {
|
|
30273
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30274
|
-
process.exit(1);
|
|
30275
|
-
}
|
|
30276
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30277
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30278
|
-
try {
|
|
30279
|
-
const masterKey = await getMasterKey2(configDir);
|
|
30280
|
-
const client = await getAuthenticatedClient2(configDir);
|
|
30281
|
-
const config = await loadKeys(configDir);
|
|
30282
|
-
const storedSession = await loadSessionConfig(configDir);
|
|
30283
|
-
const sessionOptions = {
|
|
30284
|
-
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
30285
|
-
maxPerProject: storedSession.maxPerProject,
|
|
30286
|
-
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30287
|
-
};
|
|
30288
|
-
const spinner = ora2("Scanning files...").start();
|
|
30289
|
-
const result = await pushSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
30290
|
-
spinner.text = msg;
|
|
30291
|
-
}, sessionOptions);
|
|
30292
|
-
if (result.errors.length > 0) {
|
|
30293
|
-
spinner.warn("Push completed with errors");
|
|
30294
|
-
for (const err of result.errors) {
|
|
30295
|
-
console.error(chalk2.red(` ${err}`));
|
|
30296
|
-
}
|
|
30297
|
-
} else {
|
|
30298
|
-
spinner.succeed("Push complete");
|
|
30299
|
-
}
|
|
30300
|
-
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
30301
|
-
if (sessionOptions.enabled) {
|
|
30302
|
-
console.log(chalk2.dim(" Sessions: enabled"));
|
|
30303
|
-
}
|
|
30304
|
-
if (result.conflicts.length > 0) {
|
|
30305
|
-
console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
|
|
30306
|
-
}
|
|
30307
|
-
} catch (e) {
|
|
30308
|
-
console.error(`Push failed: ${e.message}`);
|
|
30309
|
-
process.exit(1);
|
|
30310
|
-
}
|
|
30311
|
-
});
|
|
30312
|
-
syncCmd.command("pull").description("Pull changes from cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
30313
|
-
const configDir = getConfigDir();
|
|
30314
|
-
if (!await configExists(configDir)) {
|
|
30315
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30316
|
-
process.exit(1);
|
|
30317
|
-
}
|
|
30318
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30319
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30320
|
-
try {
|
|
30321
|
-
const masterKey = await getMasterKey2(configDir);
|
|
30322
|
-
const client = await getAuthenticatedClient2(configDir);
|
|
30323
|
-
const config = await loadKeys(configDir);
|
|
30324
|
-
const storedSession = await loadSessionConfig(configDir);
|
|
30325
|
-
const sessionOptions = {
|
|
30326
|
-
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
30327
|
-
maxPerProject: storedSession.maxPerProject,
|
|
30328
|
-
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30329
|
-
};
|
|
30330
|
-
const spinner = ora2("Pulling from cloud...").start();
|
|
30331
|
-
const result = await pullSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
30332
|
-
spinner.text = msg;
|
|
30333
|
-
}, sessionOptions);
|
|
30334
|
-
if (result.errors.length > 0) {
|
|
30335
|
-
spinner.warn("Pull completed with errors");
|
|
30336
|
-
for (const err of result.errors) {
|
|
30337
|
-
console.error(chalk2.red(` ${err}`));
|
|
30338
|
-
}
|
|
30339
|
-
} else {
|
|
30340
|
-
spinner.succeed("Pull complete");
|
|
30341
|
-
}
|
|
30342
|
-
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
30343
|
-
if (sessionOptions.enabled) {
|
|
30344
|
-
console.log(chalk2.dim(" Sessions: enabled"));
|
|
30345
|
-
}
|
|
30346
|
-
if (result.conflicts.length > 0) {
|
|
30347
|
-
console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
|
|
30348
|
-
}
|
|
30349
|
-
} catch (e) {
|
|
30350
|
-
console.error(`Pull failed: ${e.message}`);
|
|
30351
|
-
process.exit(1);
|
|
30352
|
-
}
|
|
30353
|
-
});
|
|
30354
|
-
syncCmd.command("cleanup").description("Remove orphaned blobs from cloud storage").option("--dry-run", "Show what would be deleted without deleting").action(async (opts) => {
|
|
30355
|
-
const configDir = getConfigDir();
|
|
30356
|
-
if (!await configExists(configDir)) {
|
|
30357
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30358
|
-
process.exit(1);
|
|
30359
|
-
}
|
|
30360
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30361
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30362
|
-
try {
|
|
30363
|
-
const masterKey = await getMasterKey2(configDir);
|
|
30364
|
-
const client = await getAuthenticatedClient2(configDir);
|
|
30365
|
-
const spinner = ora2("Fetching remote manifest...").start();
|
|
30366
|
-
const remoteManifestEnc = await client.getManifest();
|
|
30367
|
-
if (!remoteManifestEnc) {
|
|
30368
|
-
spinner.fail("No remote manifest found. Nothing to clean up.");
|
|
30369
|
-
return;
|
|
30370
|
-
}
|
|
30371
|
-
let remoteManifest;
|
|
30372
|
-
try {
|
|
30373
|
-
const decrypted = decryptFile(masterKey, "__manifest__", remoteManifestEnc);
|
|
30374
|
-
remoteManifest = JSON.parse(new TextDecoder().decode(decrypted));
|
|
30375
|
-
} catch {
|
|
30376
|
-
spinner.fail("Failed to decrypt remote manifest");
|
|
30377
|
-
process.exit(1);
|
|
30378
|
-
}
|
|
30379
|
-
const referencedHashes = new Set;
|
|
30380
|
-
for (const entry of Object.values(remoteManifest.files)) {
|
|
30381
|
-
referencedHashes.add(entry.hash);
|
|
30382
|
-
}
|
|
30383
|
-
spinner.text = "Listing remote blobs...";
|
|
30384
|
-
const { hashes: serverHashes, totalSize } = await client.listBlobs();
|
|
30385
|
-
const orphans = serverHashes.filter((h) => !referencedHashes.has(h));
|
|
30386
|
-
if (orphans.length === 0) {
|
|
30387
|
-
spinner.succeed("No orphaned blobs found");
|
|
30388
|
-
console.log(chalk2.dim(` ${serverHashes.length} blobs on server, all referenced by manifest`));
|
|
30389
|
-
return;
|
|
30390
|
-
}
|
|
30391
|
-
spinner.stop();
|
|
30392
|
-
console.log(`Found ${chalk2.yellow(orphans.length)} orphaned blobs out of ${serverHashes.length} total`);
|
|
30393
|
-
if (opts.dryRun) {
|
|
30394
|
-
console.log(chalk2.dim(" Dry run \u2014 no blobs deleted"));
|
|
30395
|
-
for (const hash of orphans.slice(0, 10)) {
|
|
30396
|
-
console.log(chalk2.dim(` ${hash}`));
|
|
30397
|
-
}
|
|
30398
|
-
if (orphans.length > 10) {
|
|
30399
|
-
console.log(chalk2.dim(` ... and ${orphans.length - 10} more`));
|
|
30400
|
-
}
|
|
30401
|
-
return;
|
|
30402
|
-
}
|
|
30403
|
-
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
30404
|
-
const { confirm } = await inquirer2.default.prompt([
|
|
30405
|
-
{
|
|
30406
|
-
type: "confirm",
|
|
30407
|
-
name: "confirm",
|
|
30408
|
-
message: `Delete ${orphans.length} orphaned blobs?`,
|
|
30409
|
-
default: false
|
|
30410
|
-
}
|
|
30411
|
-
]);
|
|
30412
|
-
if (!confirm) {
|
|
30413
|
-
console.log("Cancelled.");
|
|
30414
|
-
return;
|
|
30415
|
-
}
|
|
30416
|
-
const deleteSpinner = ora2("Deleting orphaned blobs...").start();
|
|
30417
|
-
const batchSize = 500;
|
|
30418
|
-
let deleted = 0;
|
|
30419
|
-
for (let i = 0;i < orphans.length; i += batchSize) {
|
|
30420
|
-
const batch = orphans.slice(i, i + batchSize);
|
|
30421
|
-
const result = await client.deleteBlobs(batch);
|
|
30422
|
-
deleted += result.deleted;
|
|
30423
|
-
deleteSpinner.text = `Deleting orphaned blobs... ${deleted}/${orphans.length}`;
|
|
30424
|
-
}
|
|
30425
|
-
deleteSpinner.succeed(`Deleted ${deleted} orphaned blobs`);
|
|
30426
|
-
} catch (e) {
|
|
30427
|
-
console.error(`Cleanup failed: ${e.message}`);
|
|
30428
|
-
process.exit(1);
|
|
30429
|
-
}
|
|
30430
|
-
});
|
|
30431
|
-
syncCmd.command("status").description("Show sync status").action(async () => {
|
|
30432
|
-
const configDir = getConfigDir();
|
|
30433
|
-
if (!await configExists(configDir)) {
|
|
30434
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30435
|
-
process.exit(1);
|
|
30436
|
-
}
|
|
30437
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30438
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30439
|
-
try {
|
|
30440
|
-
const masterKey = await getMasterKey2(configDir);
|
|
30441
|
-
const client = await getAuthenticatedClient2(configDir);
|
|
30442
|
-
const config = await loadKeys(configDir);
|
|
30443
|
-
const spinner = ora2("Checking status...").start();
|
|
30444
|
-
const localManifest = await loadManifest(configDir);
|
|
30445
|
-
const claudeDir = getClaudeDir();
|
|
30446
|
-
const { manifest: currentManifest } = await generateManifest(claudeDir, config.deviceName, (msg) => {
|
|
30447
|
-
spinner.text = msg;
|
|
30448
|
-
});
|
|
30449
|
-
let remoteManifest = null;
|
|
30450
|
-
const remoteManifestEnc = await client.getManifest();
|
|
30451
|
-
if (remoteManifestEnc) {
|
|
30452
|
-
try {
|
|
30453
|
-
const decrypted = decryptFile(masterKey, "__manifest__", remoteManifestEnc);
|
|
30454
|
-
remoteManifest = JSON.parse(new TextDecoder().decode(decrypted));
|
|
30455
|
-
} catch {
|
|
30456
|
-
spinner.fail("Failed to decrypt remote manifest");
|
|
30457
|
-
console.error("Wrong encryption password?");
|
|
30458
|
-
process.exit(1);
|
|
30459
|
-
}
|
|
30460
|
-
}
|
|
30461
|
-
spinner.stop();
|
|
30462
|
-
const diffs = diffManifests(currentManifest, remoteManifest);
|
|
30463
|
-
const pendingPush = diffs.filter((d) => d.action === "push" || d.action === "merge").length;
|
|
30464
|
-
const pendingPull = diffs.filter((d) => d.action === "pull" || d.action === "merge").length;
|
|
30465
|
-
console.log(chalk2.bold("Sync Status"));
|
|
30466
|
-
console.log(` Device: ${config.deviceName}`);
|
|
30467
|
-
console.log(` Pending push: ${pendingPush === 0 ? chalk2.green("0") : chalk2.yellow(pendingPush)} files`);
|
|
30468
|
-
console.log(` Pending pull: ${pendingPull === 0 ? chalk2.green("0") : chalk2.yellow(pendingPull)} files`);
|
|
30469
|
-
if (localManifest) {
|
|
30470
|
-
const lastSync = new Date(localManifest.timestamp);
|
|
30471
|
-
console.log(` Last sync: ${lastSync.toLocaleString()}`);
|
|
30472
|
-
console.log(` Last device: ${localManifest.device}`);
|
|
30473
|
-
} else {
|
|
30474
|
-
console.log(` Last sync: ${chalk2.dim("never")}`);
|
|
30475
|
-
}
|
|
30476
|
-
if (!remoteManifestEnc) {
|
|
30477
|
-
console.log(chalk2.dim(`
|
|
30478
|
-
No remote data yet. Run 'ccsini sync push' first.`));
|
|
30479
|
-
}
|
|
30480
|
-
} catch (e) {
|
|
30481
|
-
console.error(`Status check failed: ${e.message}`);
|
|
30482
|
-
process.exit(1);
|
|
30483
|
-
}
|
|
30484
|
-
});
|
|
30485
|
-
}
|
|
30486
|
-
|
|
30487
|
-
// src/commands/hooks.ts
|
|
30488
|
-
init_source();
|
|
30489
|
-
function registerHooksCommands(program2) {
|
|
30490
|
-
const hooksCmd = program2.command("hooks").description("Manage Claude Code hooks");
|
|
30491
|
-
hooksCmd.command("fix").description("Repair broken hook entries in Claude Code settings").action(async () => {
|
|
30492
|
-
const claudeDir = getClaudeDir();
|
|
30493
|
-
console.log(source_default.bold(`
|
|
30494
|
-
Checking hooks...
|
|
30495
|
-
`));
|
|
30496
|
-
const { fixed, details } = await fixHooks(claudeDir);
|
|
30497
|
-
if (fixed === 0) {
|
|
30498
|
-
console.log(source_default.green(` Hooks are already healthy. Nothing to fix.
|
|
30499
|
-
`));
|
|
30500
|
-
return;
|
|
30501
|
-
}
|
|
30502
|
-
for (const detail of details) {
|
|
30503
|
-
console.log(source_default.yellow(` Fixed: ${detail}`));
|
|
30504
|
-
}
|
|
30505
|
-
console.log(source_default.green(`
|
|
30506
|
-
Repaired ${fixed} hook issue${fixed > 1 ? "s" : ""}.
|
|
30507
|
-
`));
|
|
30508
|
-
});
|
|
30214
|
+
function readdirp(root, options = {}) {
|
|
30215
|
+
let type = options.entryType || options.type;
|
|
30216
|
+
if (type === "both")
|
|
30217
|
+
type = EntryTypes.FILE_DIR_TYPE;
|
|
30218
|
+
if (type)
|
|
30219
|
+
options.type = type;
|
|
30220
|
+
if (!root) {
|
|
30221
|
+
throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
|
|
30222
|
+
} else if (typeof root !== "string") {
|
|
30223
|
+
throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
|
|
30224
|
+
} else if (type && !ALL_TYPES.includes(type)) {
|
|
30225
|
+
throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
|
|
30226
|
+
}
|
|
30227
|
+
options.root = root;
|
|
30228
|
+
return new ReaddirpStream(options);
|
|
30509
30229
|
}
|
|
30510
30230
|
|
|
30511
|
-
//
|
|
30512
|
-
|
|
30513
|
-
import {
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30529
|
-
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
|
|
30533
|
-
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
]);
|
|
30537
|
-
if (!confirm) {
|
|
30538
|
-
console.log("Cancelled.");
|
|
30539
|
-
return;
|
|
30540
|
-
}
|
|
30541
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30542
|
-
try {
|
|
30543
|
-
const config = await loadKeys(configDir);
|
|
30544
|
-
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
30545
|
-
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
30546
|
-
const client = new CcsiniClient(config.apiUrl, jwt);
|
|
30547
|
-
const spinner = ora2("Wiping server data...").start();
|
|
30548
|
-
try {
|
|
30549
|
-
const { blobsDeleted } = await client.resetAll();
|
|
30550
|
-
spinner.succeed(`Server data wiped (${blobsDeleted} blob${blobsDeleted !== 1 ? "s" : ""} deleted)`);
|
|
30551
|
-
} catch {
|
|
30552
|
-
spinner.warn("Could not wipe server data (device may have been removed from dashboard)");
|
|
30553
|
-
}
|
|
30554
|
-
const localSpinner = ora2("Removing local config...").start();
|
|
30555
|
-
await rm(configDir, { recursive: true, force: true });
|
|
30556
|
-
localSpinner.succeed("Local config removed");
|
|
30557
|
-
console.log(chalk2.green(`
|
|
30558
|
-
Account fully reset. Run 'ccsini init --token <token>' to start fresh.`));
|
|
30559
|
-
} catch (e) {
|
|
30560
|
-
console.error(`Reset failed: ${e.message}`);
|
|
30561
|
-
process.exit(1);
|
|
30562
|
-
}
|
|
30563
|
-
});
|
|
30564
|
-
}
|
|
30565
|
-
|
|
30566
|
-
// src/commands/sessions.ts
|
|
30567
|
-
init_src();
|
|
30568
|
-
function formatBytes2(bytes) {
|
|
30569
|
-
if (bytes < 1024)
|
|
30570
|
-
return `${bytes} B`;
|
|
30571
|
-
if (bytes < 1024 * 1024)
|
|
30572
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
30573
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
30574
|
-
}
|
|
30575
|
-
function registerSessionsCommand(program2) {
|
|
30576
|
-
const sessionsCmd = program2.command("sessions").description("Configure session sync (continue conversations across devices)");
|
|
30577
|
-
sessionsCmd.command("enable").description("Enable session sync").option("-n, --per-project <count>", "Max sessions per project", String(SESSION_SYNC_DEFAULTS.maxPerProject)).action(async (opts) => {
|
|
30578
|
-
const configDir = getConfigDir();
|
|
30579
|
-
if (!await configExists(configDir)) {
|
|
30580
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30581
|
-
process.exit(1);
|
|
30582
|
-
}
|
|
30583
|
-
const maxPerProject = parseInt(opts.perProject, 10);
|
|
30584
|
-
if (isNaN(maxPerProject) || maxPerProject < 1) {
|
|
30585
|
-
console.error("--per-project must be a positive integer");
|
|
30586
|
-
process.exit(1);
|
|
30587
|
-
}
|
|
30588
|
-
await saveSessionConfig(configDir, {
|
|
30589
|
-
enabled: true,
|
|
30590
|
-
maxPerProject
|
|
30591
|
-
});
|
|
30592
|
-
console.log(`Session sync enabled (${maxPerProject} sessions per project)`);
|
|
30593
|
-
});
|
|
30594
|
-
sessionsCmd.command("disable").description("Disable session sync").action(async () => {
|
|
30595
|
-
const configDir = getConfigDir();
|
|
30596
|
-
if (!await configExists(configDir)) {
|
|
30597
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30598
|
-
process.exit(1);
|
|
30599
|
-
}
|
|
30600
|
-
const current = await loadSessionConfig(configDir);
|
|
30601
|
-
await saveSessionConfig(configDir, {
|
|
30602
|
-
enabled: false,
|
|
30603
|
-
maxPerProject: current.maxPerProject
|
|
30604
|
-
});
|
|
30605
|
-
console.log("Session sync disabled");
|
|
30606
|
-
});
|
|
30607
|
-
sessionsCmd.command("status").description("Show session sync status and preview").action(async () => {
|
|
30608
|
-
const configDir = getConfigDir();
|
|
30609
|
-
if (!await configExists(configDir)) {
|
|
30610
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30611
|
-
process.exit(1);
|
|
30612
|
-
}
|
|
30613
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30614
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30615
|
-
const sessionConfig = await loadSessionConfig(configDir);
|
|
30616
|
-
console.log(chalk2.bold("Session Sync"));
|
|
30617
|
-
console.log(` Status: ${sessionConfig.enabled ? chalk2.green("enabled") : chalk2.yellow("disabled")}`);
|
|
30618
|
-
console.log(` Per project: ${sessionConfig.maxPerProject}`);
|
|
30619
|
-
console.log(` Max file size: ${formatBytes2(SESSION_SYNC_DEFAULTS.maxFileSize)}`);
|
|
30620
|
-
const spinner = ora2("Scanning session files...").start();
|
|
30621
|
-
try {
|
|
30622
|
-
const claudeDir = getClaudeDir();
|
|
30623
|
-
const enabledOpts = {
|
|
30624
|
-
enabled: true,
|
|
30625
|
-
maxPerProject: sessionConfig.maxPerProject,
|
|
30626
|
-
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30627
|
-
};
|
|
30628
|
-
const withSessionsResult = await scanClaudeDir(claudeDir, undefined, enabledOpts);
|
|
30629
|
-
const sessionFiles = withSessionsResult.files.filter((f) => isSessionFile(f.relativePath));
|
|
30630
|
-
const disabledOpts = {
|
|
30631
|
-
enabled: false,
|
|
30632
|
-
maxPerProject: sessionConfig.maxPerProject,
|
|
30633
|
-
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30634
|
-
};
|
|
30635
|
-
const withoutSessionsResult = await scanClaudeDir(claudeDir, undefined, disabledOpts);
|
|
30636
|
-
const withoutSessions = withoutSessionsResult.files;
|
|
30637
|
-
spinner.stop();
|
|
30638
|
-
const totalSessionSize = sessionFiles.reduce((sum, f) => sum + f.size, 0);
|
|
30639
|
-
const projects = new Set(sessionFiles.map((f) => f.relativePath.split("/").slice(0, 2).join("/")));
|
|
30640
|
-
console.log(chalk2.bold(`
|
|
30641
|
-
Preview`));
|
|
30642
|
-
console.log(` Projects: ${projects.size}`);
|
|
30643
|
-
console.log(` Sessions: ${sessionFiles.length} files`);
|
|
30644
|
-
console.log(` Session size: ${formatBytes2(totalSessionSize)}`);
|
|
30645
|
-
console.log(` Other files: ${withoutSessions.length}`);
|
|
30646
|
-
if (!sessionConfig.enabled) {
|
|
30647
|
-
console.log(chalk2.dim(`
|
|
30648
|
-
Run 'ccsini sessions enable' to start syncing sessions`));
|
|
30649
|
-
}
|
|
30650
|
-
} catch (e) {
|
|
30651
|
-
spinner.fail(`Scan failed: ${e.message}`);
|
|
30652
|
-
}
|
|
30653
|
-
});
|
|
30654
|
-
}
|
|
30655
|
-
|
|
30656
|
-
// src/commands/conflicts.ts
|
|
30657
|
-
import { readdir as readdir2, readFile as readFile9, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
|
|
30658
|
-
import { join as join12, relative as relative2 } from "path";
|
|
30659
|
-
async function findConflictFiles(dir, base) {
|
|
30660
|
-
base = base ?? dir;
|
|
30661
|
-
const results = [];
|
|
30662
|
-
const entries = await readdir2(dir, { withFileTypes: true });
|
|
30663
|
-
for (const entry of entries) {
|
|
30664
|
-
const full = join12(dir, entry.name);
|
|
30665
|
-
if (entry.isDirectory()) {
|
|
30666
|
-
results.push(...await findConflictFiles(full, base));
|
|
30667
|
-
} else if (entry.name.endsWith(".conflict")) {
|
|
30668
|
-
results.push(relative2(base, full).split("\\").join("/"));
|
|
30669
|
-
}
|
|
30670
|
-
}
|
|
30671
|
-
return results;
|
|
30672
|
-
}
|
|
30673
|
-
function registerConflictsCommand(program2) {
|
|
30674
|
-
program2.command("conflicts").description("List and resolve sync conflicts").option("--keep <side>", "Resolve all: 'local' keeps .conflict, 'remote' discards .conflict").action(async (opts) => {
|
|
30675
|
-
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30676
|
-
const claudeDir = getClaudeDir();
|
|
30677
|
-
const conflictFiles = await findConflictFiles(claudeDir);
|
|
30678
|
-
if (conflictFiles.length === 0) {
|
|
30679
|
-
console.log(chalk2.green("No conflicts found."));
|
|
30680
|
-
return;
|
|
30681
|
-
}
|
|
30682
|
-
console.log(chalk2.bold(`
|
|
30683
|
-
${conflictFiles.length} conflict(s) found:
|
|
30684
|
-
`));
|
|
30685
|
-
for (const file of conflictFiles) {
|
|
30686
|
-
const original = file.replace(/\.conflict$/, "");
|
|
30687
|
-
console.log(` ${chalk2.yellow("\u26A0")} ${original}`);
|
|
30688
|
-
console.log(chalk2.dim(` conflict: ~/.claude/${file}`));
|
|
30689
|
-
}
|
|
30690
|
-
console.log();
|
|
30691
|
-
if (opts.keep === "remote") {
|
|
30692
|
-
for (const file of conflictFiles) {
|
|
30693
|
-
await unlink2(join12(claudeDir, file));
|
|
30694
|
-
}
|
|
30695
|
-
console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept remote versions.`));
|
|
30696
|
-
return;
|
|
30697
|
-
}
|
|
30698
|
-
if (opts.keep === "local") {
|
|
30699
|
-
for (const file of conflictFiles) {
|
|
30700
|
-
const conflictPath = join12(claudeDir, file);
|
|
30701
|
-
const originalPath = conflictPath.replace(/\.conflict$/, "");
|
|
30702
|
-
const conflictContent = await readFile9(conflictPath);
|
|
30703
|
-
await writeFile9(originalPath, conflictContent);
|
|
30704
|
-
await unlink2(conflictPath);
|
|
30705
|
-
}
|
|
30706
|
-
console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept local versions.`));
|
|
30707
|
-
return;
|
|
30708
|
-
}
|
|
30709
|
-
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
30710
|
-
for (const file of conflictFiles) {
|
|
30711
|
-
const original = file.replace(/\.conflict$/, "");
|
|
30712
|
-
const conflictPath = join12(claudeDir, file);
|
|
30713
|
-
const originalPath = join12(claudeDir, original);
|
|
30714
|
-
const { action } = await inquirer2.default.prompt([
|
|
30715
|
-
{
|
|
30716
|
-
type: "list",
|
|
30717
|
-
name: "action",
|
|
30718
|
-
message: `${original}:`,
|
|
30719
|
-
choices: [
|
|
30720
|
-
{ name: "Keep remote (current file)", value: "remote" },
|
|
30721
|
-
{ name: "Keep local (restore from .conflict)", value: "local" },
|
|
30722
|
-
{ name: "Skip", value: "skip" }
|
|
30723
|
-
]
|
|
30724
|
-
}
|
|
30725
|
-
]);
|
|
30726
|
-
if (action === "remote") {
|
|
30727
|
-
await unlink2(conflictPath);
|
|
30728
|
-
console.log(chalk2.dim(` Kept remote \u2014 deleted ${file}`));
|
|
30729
|
-
} else if (action === "local") {
|
|
30730
|
-
const conflictContent = await readFile9(conflictPath);
|
|
30731
|
-
await writeFile9(originalPath, conflictContent);
|
|
30732
|
-
await unlink2(conflictPath);
|
|
30733
|
-
console.log(chalk2.dim(` Restored local version`));
|
|
30734
|
-
}
|
|
30735
|
-
}
|
|
30736
|
-
const remaining = await findConflictFiles(claudeDir);
|
|
30737
|
-
if (remaining.length === 0) {
|
|
30738
|
-
console.log(chalk2.green(`
|
|
30739
|
-
All conflicts resolved.`));
|
|
30740
|
-
} else {
|
|
30741
|
-
console.log(chalk2.yellow(`
|
|
30742
|
-
${remaining.length} conflict(s) remaining.`));
|
|
30743
|
-
}
|
|
30744
|
-
});
|
|
30745
|
-
}
|
|
30746
|
-
|
|
30747
|
-
// src/commands/daemon.ts
|
|
30748
|
-
import { spawn as spawn2 } from "child_process";
|
|
30749
|
-
import { readFile as readFile11, rm as rm2 } from "fs/promises";
|
|
30750
|
-
import { platform as platform4 } from "os";
|
|
30751
|
-
|
|
30752
|
-
// src/daemon/runner.ts
|
|
30753
|
-
import { readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
|
|
30754
|
-
import { join as join15 } from "path";
|
|
30755
|
-
import { platform as platform3 } from "os";
|
|
30756
|
-
import { appendFileSync } from "fs";
|
|
30757
|
-
init_auth();
|
|
30758
|
-
|
|
30759
|
-
// ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
30760
|
-
import { EventEmitter } from "events";
|
|
30761
|
-
import { stat as statcb, Stats } from "fs";
|
|
30762
|
-
import { readdir as readdir4, stat as stat4 } from "fs/promises";
|
|
30763
|
-
import * as sp2 from "path";
|
|
30764
|
-
|
|
30765
|
-
// ../../node_modules/.bun/readdirp@5.0.0/node_modules/readdirp/index.js
|
|
30766
|
-
import { lstat, readdir as readdir3, realpath, stat as stat2 } from "fs/promises";
|
|
30767
|
-
import { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from "path";
|
|
30768
|
-
import { Readable } from "stream";
|
|
30769
|
-
var EntryTypes = {
|
|
30770
|
-
FILE_TYPE: "files",
|
|
30771
|
-
DIR_TYPE: "directories",
|
|
30772
|
-
FILE_DIR_TYPE: "files_directories",
|
|
30773
|
-
EVERYTHING_TYPE: "all"
|
|
30774
|
-
};
|
|
30775
|
-
var defaultOptions = {
|
|
30776
|
-
root: ".",
|
|
30777
|
-
fileFilter: (_entryInfo) => true,
|
|
30778
|
-
directoryFilter: (_entryInfo) => true,
|
|
30779
|
-
type: EntryTypes.FILE_TYPE,
|
|
30780
|
-
lstat: false,
|
|
30781
|
-
depth: 2147483648,
|
|
30782
|
-
alwaysStat: false,
|
|
30783
|
-
highWaterMark: 4096
|
|
30784
|
-
};
|
|
30785
|
-
Object.freeze(defaultOptions);
|
|
30786
|
-
var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
|
|
30787
|
-
var NORMAL_FLOW_ERRORS = new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
|
|
30788
|
-
var ALL_TYPES = [
|
|
30789
|
-
EntryTypes.DIR_TYPE,
|
|
30790
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
30791
|
-
EntryTypes.FILE_DIR_TYPE,
|
|
30792
|
-
EntryTypes.FILE_TYPE
|
|
30793
|
-
];
|
|
30794
|
-
var DIR_TYPES = new Set([
|
|
30795
|
-
EntryTypes.DIR_TYPE,
|
|
30796
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
30797
|
-
EntryTypes.FILE_DIR_TYPE
|
|
30798
|
-
]);
|
|
30799
|
-
var FILE_TYPES = new Set([
|
|
30800
|
-
EntryTypes.EVERYTHING_TYPE,
|
|
30801
|
-
EntryTypes.FILE_DIR_TYPE,
|
|
30802
|
-
EntryTypes.FILE_TYPE
|
|
30803
|
-
]);
|
|
30804
|
-
var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
|
|
30805
|
-
var wantBigintFsStats = process.platform === "win32";
|
|
30806
|
-
var emptyFn = (_entryInfo) => true;
|
|
30807
|
-
var normalizeFilter = (filter2) => {
|
|
30808
|
-
if (filter2 === undefined)
|
|
30809
|
-
return emptyFn;
|
|
30810
|
-
if (typeof filter2 === "function")
|
|
30811
|
-
return filter2;
|
|
30812
|
-
if (typeof filter2 === "string") {
|
|
30813
|
-
const fl = filter2.trim();
|
|
30814
|
-
return (entry) => entry.basename === fl;
|
|
30815
|
-
}
|
|
30816
|
-
if (Array.isArray(filter2)) {
|
|
30817
|
-
const trItems = filter2.map((item) => item.trim());
|
|
30818
|
-
return (entry) => trItems.some((f) => entry.basename === f);
|
|
30819
|
-
}
|
|
30820
|
-
return emptyFn;
|
|
30821
|
-
};
|
|
30822
|
-
|
|
30823
|
-
class ReaddirpStream extends Readable {
|
|
30824
|
-
parents;
|
|
30825
|
-
reading;
|
|
30826
|
-
parent;
|
|
30827
|
-
_stat;
|
|
30828
|
-
_maxDepth;
|
|
30829
|
-
_wantsDir;
|
|
30830
|
-
_wantsFile;
|
|
30831
|
-
_wantsEverything;
|
|
30832
|
-
_root;
|
|
30833
|
-
_isDirent;
|
|
30834
|
-
_statsProp;
|
|
30835
|
-
_rdOptions;
|
|
30836
|
-
_fileFilter;
|
|
30837
|
-
_directoryFilter;
|
|
30838
|
-
constructor(options = {}) {
|
|
30839
|
-
super({
|
|
30840
|
-
objectMode: true,
|
|
30841
|
-
autoDestroy: true,
|
|
30842
|
-
highWaterMark: options.highWaterMark
|
|
30843
|
-
});
|
|
30844
|
-
const opts = { ...defaultOptions, ...options };
|
|
30845
|
-
const { root, type } = opts;
|
|
30846
|
-
this._fileFilter = normalizeFilter(opts.fileFilter);
|
|
30847
|
-
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
30848
|
-
const statMethod = opts.lstat ? lstat : stat2;
|
|
30849
|
-
if (wantBigintFsStats) {
|
|
30850
|
-
this._stat = (path2) => statMethod(path2, { bigint: true });
|
|
30851
|
-
} else {
|
|
30852
|
-
this._stat = statMethod;
|
|
30853
|
-
}
|
|
30854
|
-
this._maxDepth = opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
|
|
30855
|
-
this._wantsDir = type ? DIR_TYPES.has(type) : false;
|
|
30856
|
-
this._wantsFile = type ? FILE_TYPES.has(type) : false;
|
|
30857
|
-
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
|
|
30858
|
-
this._root = presolve(root);
|
|
30859
|
-
this._isDirent = !opts.alwaysStat;
|
|
30860
|
-
this._statsProp = this._isDirent ? "dirent" : "stats";
|
|
30861
|
-
this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
|
|
30862
|
-
this.parents = [this._exploreDir(root, 1)];
|
|
30863
|
-
this.reading = false;
|
|
30864
|
-
this.parent = undefined;
|
|
30865
|
-
}
|
|
30866
|
-
async _read(batch) {
|
|
30867
|
-
if (this.reading)
|
|
30868
|
-
return;
|
|
30869
|
-
this.reading = true;
|
|
30870
|
-
try {
|
|
30871
|
-
while (!this.destroyed && batch > 0) {
|
|
30872
|
-
const par = this.parent;
|
|
30873
|
-
const fil = par && par.files;
|
|
30874
|
-
if (fil && fil.length > 0) {
|
|
30875
|
-
const { path: path2, depth } = par;
|
|
30876
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path2));
|
|
30877
|
-
const awaited = await Promise.all(slice);
|
|
30878
|
-
for (const entry of awaited) {
|
|
30879
|
-
if (!entry)
|
|
30880
|
-
continue;
|
|
30881
|
-
if (this.destroyed)
|
|
30882
|
-
return;
|
|
30883
|
-
const entryType = await this._getEntryType(entry);
|
|
30884
|
-
if (entryType === "directory" && this._directoryFilter(entry)) {
|
|
30885
|
-
if (depth <= this._maxDepth) {
|
|
30886
|
-
this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
|
|
30887
|
-
}
|
|
30888
|
-
if (this._wantsDir) {
|
|
30889
|
-
this.push(entry);
|
|
30890
|
-
batch--;
|
|
30891
|
-
}
|
|
30892
|
-
} else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
|
|
30893
|
-
if (this._wantsFile) {
|
|
30894
|
-
this.push(entry);
|
|
30895
|
-
batch--;
|
|
30896
|
-
}
|
|
30897
|
-
}
|
|
30898
|
-
}
|
|
30899
|
-
} else {
|
|
30900
|
-
const parent = this.parents.pop();
|
|
30901
|
-
if (!parent) {
|
|
30902
|
-
this.push(null);
|
|
30903
|
-
break;
|
|
30904
|
-
}
|
|
30905
|
-
this.parent = await parent;
|
|
30906
|
-
if (this.destroyed)
|
|
30907
|
-
return;
|
|
30908
|
-
}
|
|
30909
|
-
}
|
|
30910
|
-
} catch (error) {
|
|
30911
|
-
this.destroy(error);
|
|
30912
|
-
} finally {
|
|
30913
|
-
this.reading = false;
|
|
30914
|
-
}
|
|
30915
|
-
}
|
|
30916
|
-
async _exploreDir(path2, depth) {
|
|
30917
|
-
let files;
|
|
30918
|
-
try {
|
|
30919
|
-
files = await readdir3(path2, this._rdOptions);
|
|
30920
|
-
} catch (error) {
|
|
30921
|
-
this._onError(error);
|
|
30922
|
-
}
|
|
30923
|
-
return { files, depth, path: path2 };
|
|
30924
|
-
}
|
|
30925
|
-
async _formatEntry(dirent, path2) {
|
|
30926
|
-
let entry;
|
|
30927
|
-
const basename = this._isDirent ? dirent.name : dirent;
|
|
30928
|
-
try {
|
|
30929
|
-
const fullPath = presolve(pjoin(path2, basename));
|
|
30930
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename };
|
|
30931
|
-
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
30932
|
-
} catch (err) {
|
|
30933
|
-
this._onError(err);
|
|
30934
|
-
return;
|
|
30935
|
-
}
|
|
30936
|
-
return entry;
|
|
30937
|
-
}
|
|
30938
|
-
_onError(err) {
|
|
30939
|
-
if (isNormalFlowError(err) && !this.destroyed) {
|
|
30940
|
-
this.emit("warn", err);
|
|
30941
|
-
} else {
|
|
30942
|
-
this.destroy(err);
|
|
30943
|
-
}
|
|
30944
|
-
}
|
|
30945
|
-
async _getEntryType(entry) {
|
|
30946
|
-
if (!entry && this._statsProp in entry) {
|
|
30947
|
-
return "";
|
|
30948
|
-
}
|
|
30949
|
-
const stats = entry[this._statsProp];
|
|
30950
|
-
if (stats.isFile())
|
|
30951
|
-
return "file";
|
|
30952
|
-
if (stats.isDirectory())
|
|
30953
|
-
return "directory";
|
|
30954
|
-
if (stats && stats.isSymbolicLink()) {
|
|
30955
|
-
const full = entry.fullPath;
|
|
30956
|
-
try {
|
|
30957
|
-
const entryRealPath = await realpath(full);
|
|
30958
|
-
const entryRealPathStats = await lstat(entryRealPath);
|
|
30959
|
-
if (entryRealPathStats.isFile()) {
|
|
30960
|
-
return "file";
|
|
30961
|
-
}
|
|
30962
|
-
if (entryRealPathStats.isDirectory()) {
|
|
30963
|
-
const len = entryRealPath.length;
|
|
30964
|
-
if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
|
|
30965
|
-
const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
|
|
30966
|
-
recursiveError.code = RECURSIVE_ERROR_CODE;
|
|
30967
|
-
return this._onError(recursiveError);
|
|
30968
|
-
}
|
|
30969
|
-
return "directory";
|
|
30970
|
-
}
|
|
30971
|
-
} catch (error) {
|
|
30972
|
-
this._onError(error);
|
|
30973
|
-
return "";
|
|
30974
|
-
}
|
|
30975
|
-
}
|
|
30976
|
-
}
|
|
30977
|
-
_includeAsFile(entry) {
|
|
30978
|
-
const stats = entry && entry[this._statsProp];
|
|
30979
|
-
return stats && this._wantsEverything && !stats.isDirectory();
|
|
30980
|
-
}
|
|
30981
|
-
}
|
|
30982
|
-
function readdirp(root, options = {}) {
|
|
30983
|
-
let type = options.entryType || options.type;
|
|
30984
|
-
if (type === "both")
|
|
30985
|
-
type = EntryTypes.FILE_DIR_TYPE;
|
|
30986
|
-
if (type)
|
|
30987
|
-
options.type = type;
|
|
30988
|
-
if (!root) {
|
|
30989
|
-
throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
|
|
30990
|
-
} else if (typeof root !== "string") {
|
|
30991
|
-
throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
|
|
30992
|
-
} else if (type && !ALL_TYPES.includes(type)) {
|
|
30993
|
-
throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
|
|
30994
|
-
}
|
|
30995
|
-
options.root = root;
|
|
30996
|
-
return new ReaddirpStream(options);
|
|
30997
|
-
}
|
|
30998
|
-
|
|
30999
|
-
// ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/handler.js
|
|
31000
|
-
import { watch as fs_watch, unwatchFile, watchFile } from "fs";
|
|
31001
|
-
import { realpath as fsrealpath, lstat as lstat2, open, stat as stat3 } from "fs/promises";
|
|
31002
|
-
import { type as osType } from "os";
|
|
31003
|
-
import * as sp from "path";
|
|
31004
|
-
var STR_DATA = "data";
|
|
31005
|
-
var STR_END = "end";
|
|
31006
|
-
var STR_CLOSE = "close";
|
|
31007
|
-
var EMPTY_FN = () => {};
|
|
31008
|
-
var pl = process.platform;
|
|
31009
|
-
var isWindows = pl === "win32";
|
|
31010
|
-
var isMacos = pl === "darwin";
|
|
31011
|
-
var isLinux = pl === "linux";
|
|
31012
|
-
var isFreeBSD = pl === "freebsd";
|
|
31013
|
-
var isIBMi = osType() === "OS400";
|
|
31014
|
-
var EVENTS = {
|
|
31015
|
-
ALL: "all",
|
|
31016
|
-
READY: "ready",
|
|
31017
|
-
ADD: "add",
|
|
31018
|
-
CHANGE: "change",
|
|
31019
|
-
ADD_DIR: "addDir",
|
|
31020
|
-
UNLINK: "unlink",
|
|
31021
|
-
UNLINK_DIR: "unlinkDir",
|
|
31022
|
-
RAW: "raw",
|
|
31023
|
-
ERROR: "error"
|
|
30231
|
+
// ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/handler.js
|
|
30232
|
+
import { watch as fs_watch, unwatchFile, watchFile } from "fs";
|
|
30233
|
+
import { realpath as fsrealpath, lstat as lstat2, open, stat as stat3 } from "fs/promises";
|
|
30234
|
+
import { type as osType } from "os";
|
|
30235
|
+
import * as sp from "path";
|
|
30236
|
+
var STR_DATA = "data";
|
|
30237
|
+
var STR_END = "end";
|
|
30238
|
+
var STR_CLOSE = "close";
|
|
30239
|
+
var EMPTY_FN = () => {};
|
|
30240
|
+
var pl = process.platform;
|
|
30241
|
+
var isWindows = pl === "win32";
|
|
30242
|
+
var isMacos = pl === "darwin";
|
|
30243
|
+
var isLinux = pl === "linux";
|
|
30244
|
+
var isFreeBSD = pl === "freebsd";
|
|
30245
|
+
var isIBMi = osType() === "OS400";
|
|
30246
|
+
var EVENTS = {
|
|
30247
|
+
ALL: "all",
|
|
30248
|
+
READY: "ready",
|
|
30249
|
+
ADD: "add",
|
|
30250
|
+
CHANGE: "change",
|
|
30251
|
+
ADD_DIR: "addDir",
|
|
30252
|
+
UNLINK: "unlink",
|
|
30253
|
+
UNLINK_DIR: "unlinkDir",
|
|
30254
|
+
RAW: "raw",
|
|
30255
|
+
ERROR: "error"
|
|
31024
30256
|
};
|
|
31025
30257
|
var EV = EVENTS;
|
|
31026
30258
|
var THROTTLE_MODE_WATCH = "watch";
|
|
@@ -31300,1260 +30532,2066 @@ var foreach = (val, fn) => {
|
|
|
31300
30532
|
fn(val);
|
|
31301
30533
|
}
|
|
31302
30534
|
};
|
|
31303
|
-
var addAndConvert = (main2, prop, item) => {
|
|
31304
|
-
let container = main2[prop];
|
|
31305
|
-
if (!(container instanceof Set)) {
|
|
31306
|
-
main2[prop] = container = new Set([container]);
|
|
30535
|
+
var addAndConvert = (main2, prop, item) => {
|
|
30536
|
+
let container = main2[prop];
|
|
30537
|
+
if (!(container instanceof Set)) {
|
|
30538
|
+
main2[prop] = container = new Set([container]);
|
|
30539
|
+
}
|
|
30540
|
+
container.add(item);
|
|
30541
|
+
};
|
|
30542
|
+
var clearItem = (cont) => (key) => {
|
|
30543
|
+
const set = cont[key];
|
|
30544
|
+
if (set instanceof Set) {
|
|
30545
|
+
set.clear();
|
|
30546
|
+
} else {
|
|
30547
|
+
delete cont[key];
|
|
30548
|
+
}
|
|
30549
|
+
};
|
|
30550
|
+
var delFromSet = (main2, prop, item) => {
|
|
30551
|
+
const container = main2[prop];
|
|
30552
|
+
if (container instanceof Set) {
|
|
30553
|
+
container.delete(item);
|
|
30554
|
+
} else if (container === item) {
|
|
30555
|
+
delete main2[prop];
|
|
30556
|
+
}
|
|
30557
|
+
};
|
|
30558
|
+
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
30559
|
+
var FsWatchInstances = new Map;
|
|
30560
|
+
function createFsWatchInstance(path2, options, listener, errHandler, emitRaw) {
|
|
30561
|
+
const handleEvent = (rawEvent, evPath) => {
|
|
30562
|
+
listener(path2);
|
|
30563
|
+
emitRaw(rawEvent, evPath, { watchedPath: path2 });
|
|
30564
|
+
if (evPath && path2 !== evPath) {
|
|
30565
|
+
fsWatchBroadcast(sp.resolve(path2, evPath), KEY_LISTENERS, sp.join(path2, evPath));
|
|
30566
|
+
}
|
|
30567
|
+
};
|
|
30568
|
+
try {
|
|
30569
|
+
return fs_watch(path2, {
|
|
30570
|
+
persistent: options.persistent
|
|
30571
|
+
}, handleEvent);
|
|
30572
|
+
} catch (error) {
|
|
30573
|
+
errHandler(error);
|
|
30574
|
+
return;
|
|
30575
|
+
}
|
|
30576
|
+
}
|
|
30577
|
+
var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
30578
|
+
const cont = FsWatchInstances.get(fullPath);
|
|
30579
|
+
if (!cont)
|
|
30580
|
+
return;
|
|
30581
|
+
foreach(cont[listenerType], (listener) => {
|
|
30582
|
+
listener(val1, val2, val3);
|
|
30583
|
+
});
|
|
30584
|
+
};
|
|
30585
|
+
var setFsWatchListener = (path2, fullPath, options, handlers) => {
|
|
30586
|
+
const { listener, errHandler, rawEmitter } = handlers;
|
|
30587
|
+
let cont = FsWatchInstances.get(fullPath);
|
|
30588
|
+
let watcher;
|
|
30589
|
+
if (!options.persistent) {
|
|
30590
|
+
watcher = createFsWatchInstance(path2, options, listener, errHandler, rawEmitter);
|
|
30591
|
+
if (!watcher)
|
|
30592
|
+
return;
|
|
30593
|
+
return watcher.close.bind(watcher);
|
|
30594
|
+
}
|
|
30595
|
+
if (cont) {
|
|
30596
|
+
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
30597
|
+
addAndConvert(cont, KEY_ERR, errHandler);
|
|
30598
|
+
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
30599
|
+
} else {
|
|
30600
|
+
watcher = createFsWatchInstance(path2, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
|
|
30601
|
+
if (!watcher)
|
|
30602
|
+
return;
|
|
30603
|
+
watcher.on(EV.ERROR, async (error) => {
|
|
30604
|
+
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
|
|
30605
|
+
if (cont)
|
|
30606
|
+
cont.watcherUnusable = true;
|
|
30607
|
+
if (isWindows && error.code === "EPERM") {
|
|
30608
|
+
try {
|
|
30609
|
+
const fd = await open(path2, "r");
|
|
30610
|
+
await fd.close();
|
|
30611
|
+
broadcastErr(error);
|
|
30612
|
+
} catch (err) {}
|
|
30613
|
+
} else {
|
|
30614
|
+
broadcastErr(error);
|
|
30615
|
+
}
|
|
30616
|
+
});
|
|
30617
|
+
cont = {
|
|
30618
|
+
listeners: listener,
|
|
30619
|
+
errHandlers: errHandler,
|
|
30620
|
+
rawEmitters: rawEmitter,
|
|
30621
|
+
watcher
|
|
30622
|
+
};
|
|
30623
|
+
FsWatchInstances.set(fullPath, cont);
|
|
30624
|
+
}
|
|
30625
|
+
return () => {
|
|
30626
|
+
delFromSet(cont, KEY_LISTENERS, listener);
|
|
30627
|
+
delFromSet(cont, KEY_ERR, errHandler);
|
|
30628
|
+
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
30629
|
+
if (isEmptySet(cont.listeners)) {
|
|
30630
|
+
cont.watcher.close();
|
|
30631
|
+
FsWatchInstances.delete(fullPath);
|
|
30632
|
+
HANDLER_KEYS.forEach(clearItem(cont));
|
|
30633
|
+
cont.watcher = undefined;
|
|
30634
|
+
Object.freeze(cont);
|
|
30635
|
+
}
|
|
30636
|
+
};
|
|
30637
|
+
};
|
|
30638
|
+
var FsWatchFileInstances = new Map;
|
|
30639
|
+
var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
|
|
30640
|
+
const { listener, rawEmitter } = handlers;
|
|
30641
|
+
let cont = FsWatchFileInstances.get(fullPath);
|
|
30642
|
+
const copts = cont && cont.options;
|
|
30643
|
+
if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
|
|
30644
|
+
unwatchFile(fullPath);
|
|
30645
|
+
cont = undefined;
|
|
30646
|
+
}
|
|
30647
|
+
if (cont) {
|
|
30648
|
+
addAndConvert(cont, KEY_LISTENERS, listener);
|
|
30649
|
+
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
30650
|
+
} else {
|
|
30651
|
+
cont = {
|
|
30652
|
+
listeners: listener,
|
|
30653
|
+
rawEmitters: rawEmitter,
|
|
30654
|
+
options,
|
|
30655
|
+
watcher: watchFile(fullPath, options, (curr, prev) => {
|
|
30656
|
+
foreach(cont.rawEmitters, (rawEmitter2) => {
|
|
30657
|
+
rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
|
|
30658
|
+
});
|
|
30659
|
+
const currmtime = curr.mtimeMs;
|
|
30660
|
+
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
30661
|
+
foreach(cont.listeners, (listener2) => listener2(path2, curr));
|
|
30662
|
+
}
|
|
30663
|
+
})
|
|
30664
|
+
};
|
|
30665
|
+
FsWatchFileInstances.set(fullPath, cont);
|
|
30666
|
+
}
|
|
30667
|
+
return () => {
|
|
30668
|
+
delFromSet(cont, KEY_LISTENERS, listener);
|
|
30669
|
+
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
30670
|
+
if (isEmptySet(cont.listeners)) {
|
|
30671
|
+
FsWatchFileInstances.delete(fullPath);
|
|
30672
|
+
unwatchFile(fullPath);
|
|
30673
|
+
cont.options = cont.watcher = undefined;
|
|
30674
|
+
Object.freeze(cont);
|
|
30675
|
+
}
|
|
30676
|
+
};
|
|
30677
|
+
};
|
|
30678
|
+
|
|
30679
|
+
class NodeFsHandler {
|
|
30680
|
+
fsw;
|
|
30681
|
+
_boundHandleError;
|
|
30682
|
+
constructor(fsW) {
|
|
30683
|
+
this.fsw = fsW;
|
|
30684
|
+
this._boundHandleError = (error) => fsW._handleError(error);
|
|
30685
|
+
}
|
|
30686
|
+
_watchWithNodeFs(path2, listener) {
|
|
30687
|
+
const opts = this.fsw.options;
|
|
30688
|
+
const directory = sp.dirname(path2);
|
|
30689
|
+
const basename2 = sp.basename(path2);
|
|
30690
|
+
const parent = this.fsw._getWatchedDir(directory);
|
|
30691
|
+
parent.add(basename2);
|
|
30692
|
+
const absolutePath = sp.resolve(path2);
|
|
30693
|
+
const options = {
|
|
30694
|
+
persistent: opts.persistent
|
|
30695
|
+
};
|
|
30696
|
+
if (!listener)
|
|
30697
|
+
listener = EMPTY_FN;
|
|
30698
|
+
let closer;
|
|
30699
|
+
if (opts.usePolling) {
|
|
30700
|
+
const enableBin = opts.interval !== opts.binaryInterval;
|
|
30701
|
+
options.interval = enableBin && isBinaryPath(basename2) ? opts.binaryInterval : opts.interval;
|
|
30702
|
+
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
30703
|
+
listener,
|
|
30704
|
+
rawEmitter: this.fsw._emitRaw
|
|
30705
|
+
});
|
|
30706
|
+
} else {
|
|
30707
|
+
closer = setFsWatchListener(path2, absolutePath, options, {
|
|
30708
|
+
listener,
|
|
30709
|
+
errHandler: this._boundHandleError,
|
|
30710
|
+
rawEmitter: this.fsw._emitRaw
|
|
30711
|
+
});
|
|
30712
|
+
}
|
|
30713
|
+
return closer;
|
|
30714
|
+
}
|
|
30715
|
+
_handleFile(file, stats, initialAdd) {
|
|
30716
|
+
if (this.fsw.closed) {
|
|
30717
|
+
return;
|
|
30718
|
+
}
|
|
30719
|
+
const dirname3 = sp.dirname(file);
|
|
30720
|
+
const basename2 = sp.basename(file);
|
|
30721
|
+
const parent = this.fsw._getWatchedDir(dirname3);
|
|
30722
|
+
let prevStats = stats;
|
|
30723
|
+
if (parent.has(basename2))
|
|
30724
|
+
return;
|
|
30725
|
+
const listener = async (path2, newStats) => {
|
|
30726
|
+
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
30727
|
+
return;
|
|
30728
|
+
if (!newStats || newStats.mtimeMs === 0) {
|
|
30729
|
+
try {
|
|
30730
|
+
const newStats2 = await stat3(file);
|
|
30731
|
+
if (this.fsw.closed)
|
|
30732
|
+
return;
|
|
30733
|
+
const at = newStats2.atimeMs;
|
|
30734
|
+
const mt = newStats2.mtimeMs;
|
|
30735
|
+
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
30736
|
+
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
30737
|
+
}
|
|
30738
|
+
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
30739
|
+
this.fsw._closeFile(path2);
|
|
30740
|
+
prevStats = newStats2;
|
|
30741
|
+
const closer2 = this._watchWithNodeFs(file, listener);
|
|
30742
|
+
if (closer2)
|
|
30743
|
+
this.fsw._addPathCloser(path2, closer2);
|
|
30744
|
+
} else {
|
|
30745
|
+
prevStats = newStats2;
|
|
30746
|
+
}
|
|
30747
|
+
} catch (error) {
|
|
30748
|
+
this.fsw._remove(dirname3, basename2);
|
|
30749
|
+
}
|
|
30750
|
+
} else if (parent.has(basename2)) {
|
|
30751
|
+
const at = newStats.atimeMs;
|
|
30752
|
+
const mt = newStats.mtimeMs;
|
|
30753
|
+
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
30754
|
+
this.fsw._emit(EV.CHANGE, file, newStats);
|
|
30755
|
+
}
|
|
30756
|
+
prevStats = newStats;
|
|
30757
|
+
}
|
|
30758
|
+
};
|
|
30759
|
+
const closer = this._watchWithNodeFs(file, listener);
|
|
30760
|
+
if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
|
|
30761
|
+
if (!this.fsw._throttle(EV.ADD, file, 0))
|
|
30762
|
+
return;
|
|
30763
|
+
this.fsw._emit(EV.ADD, file, stats);
|
|
30764
|
+
}
|
|
30765
|
+
return closer;
|
|
30766
|
+
}
|
|
30767
|
+
async _handleSymlink(entry, directory, path2, item) {
|
|
30768
|
+
if (this.fsw.closed) {
|
|
30769
|
+
return;
|
|
30770
|
+
}
|
|
30771
|
+
const full = entry.fullPath;
|
|
30772
|
+
const dir = this.fsw._getWatchedDir(directory);
|
|
30773
|
+
if (!this.fsw.options.followSymlinks) {
|
|
30774
|
+
this.fsw._incrReadyCount();
|
|
30775
|
+
let linkPath;
|
|
30776
|
+
try {
|
|
30777
|
+
linkPath = await fsrealpath(path2);
|
|
30778
|
+
} catch (e) {
|
|
30779
|
+
this.fsw._emitReady();
|
|
30780
|
+
return true;
|
|
30781
|
+
}
|
|
30782
|
+
if (this.fsw.closed)
|
|
30783
|
+
return;
|
|
30784
|
+
if (dir.has(item)) {
|
|
30785
|
+
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
30786
|
+
this.fsw._symlinkPaths.set(full, linkPath);
|
|
30787
|
+
this.fsw._emit(EV.CHANGE, path2, entry.stats);
|
|
30788
|
+
}
|
|
30789
|
+
} else {
|
|
30790
|
+
dir.add(item);
|
|
30791
|
+
this.fsw._symlinkPaths.set(full, linkPath);
|
|
30792
|
+
this.fsw._emit(EV.ADD, path2, entry.stats);
|
|
30793
|
+
}
|
|
30794
|
+
this.fsw._emitReady();
|
|
30795
|
+
return true;
|
|
30796
|
+
}
|
|
30797
|
+
if (this.fsw._symlinkPaths.has(full)) {
|
|
30798
|
+
return true;
|
|
30799
|
+
}
|
|
30800
|
+
this.fsw._symlinkPaths.set(full, true);
|
|
30801
|
+
}
|
|
30802
|
+
_handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
|
|
30803
|
+
directory = sp.join(directory, "");
|
|
30804
|
+
const throttleKey = target ? `${directory}:${target}` : directory;
|
|
30805
|
+
throttler = this.fsw._throttle("readdir", throttleKey, 1000);
|
|
30806
|
+
if (!throttler)
|
|
30807
|
+
return;
|
|
30808
|
+
const previous = this.fsw._getWatchedDir(wh.path);
|
|
30809
|
+
const current = new Set;
|
|
30810
|
+
let stream = this.fsw._readdirp(directory, {
|
|
30811
|
+
fileFilter: (entry) => wh.filterPath(entry),
|
|
30812
|
+
directoryFilter: (entry) => wh.filterDir(entry)
|
|
30813
|
+
});
|
|
30814
|
+
if (!stream)
|
|
30815
|
+
return;
|
|
30816
|
+
stream.on(STR_DATA, async (entry) => {
|
|
30817
|
+
if (this.fsw.closed) {
|
|
30818
|
+
stream = undefined;
|
|
30819
|
+
return;
|
|
30820
|
+
}
|
|
30821
|
+
const item = entry.path;
|
|
30822
|
+
let path2 = sp.join(directory, item);
|
|
30823
|
+
current.add(item);
|
|
30824
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path2, item)) {
|
|
30825
|
+
return;
|
|
30826
|
+
}
|
|
30827
|
+
if (this.fsw.closed) {
|
|
30828
|
+
stream = undefined;
|
|
30829
|
+
return;
|
|
30830
|
+
}
|
|
30831
|
+
if (item === target || !target && !previous.has(item)) {
|
|
30832
|
+
this.fsw._incrReadyCount();
|
|
30833
|
+
path2 = sp.join(dir, sp.relative(dir, path2));
|
|
30834
|
+
this._addToNodeFs(path2, initialAdd, wh, depth + 1);
|
|
30835
|
+
}
|
|
30836
|
+
}).on(EV.ERROR, this._boundHandleError);
|
|
30837
|
+
return new Promise((resolve2, reject) => {
|
|
30838
|
+
if (!stream)
|
|
30839
|
+
return reject();
|
|
30840
|
+
stream.once(STR_END, () => {
|
|
30841
|
+
if (this.fsw.closed) {
|
|
30842
|
+
stream = undefined;
|
|
30843
|
+
return;
|
|
30844
|
+
}
|
|
30845
|
+
const wasThrottled = throttler ? throttler.clear() : false;
|
|
30846
|
+
resolve2(undefined);
|
|
30847
|
+
previous.getChildren().filter((item) => {
|
|
30848
|
+
return item !== directory && !current.has(item);
|
|
30849
|
+
}).forEach((item) => {
|
|
30850
|
+
this.fsw._remove(directory, item);
|
|
30851
|
+
});
|
|
30852
|
+
stream = undefined;
|
|
30853
|
+
if (wasThrottled)
|
|
30854
|
+
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
30855
|
+
});
|
|
30856
|
+
});
|
|
30857
|
+
}
|
|
30858
|
+
async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
|
|
30859
|
+
const parentDir = this.fsw._getWatchedDir(sp.dirname(dir));
|
|
30860
|
+
const tracked = parentDir.has(sp.basename(dir));
|
|
30861
|
+
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
|
|
30862
|
+
this.fsw._emit(EV.ADD_DIR, dir, stats);
|
|
30863
|
+
}
|
|
30864
|
+
parentDir.add(sp.basename(dir));
|
|
30865
|
+
this.fsw._getWatchedDir(dir);
|
|
30866
|
+
let throttler;
|
|
30867
|
+
let closer;
|
|
30868
|
+
const oDepth = this.fsw.options.depth;
|
|
30869
|
+
if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
|
|
30870
|
+
if (!target) {
|
|
30871
|
+
await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
|
|
30872
|
+
if (this.fsw.closed)
|
|
30873
|
+
return;
|
|
30874
|
+
}
|
|
30875
|
+
closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
|
|
30876
|
+
if (stats2 && stats2.mtimeMs === 0)
|
|
30877
|
+
return;
|
|
30878
|
+
this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
|
|
30879
|
+
});
|
|
30880
|
+
}
|
|
30881
|
+
return closer;
|
|
30882
|
+
}
|
|
30883
|
+
async _addToNodeFs(path2, initialAdd, priorWh, depth, target) {
|
|
30884
|
+
const ready = this.fsw._emitReady;
|
|
30885
|
+
if (this.fsw._isIgnored(path2) || this.fsw.closed) {
|
|
30886
|
+
ready();
|
|
30887
|
+
return false;
|
|
30888
|
+
}
|
|
30889
|
+
const wh = this.fsw._getWatchHelpers(path2);
|
|
30890
|
+
if (priorWh) {
|
|
30891
|
+
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
30892
|
+
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
30893
|
+
}
|
|
30894
|
+
try {
|
|
30895
|
+
const stats = await statMethods[wh.statMethod](wh.watchPath);
|
|
30896
|
+
if (this.fsw.closed)
|
|
30897
|
+
return;
|
|
30898
|
+
if (this.fsw._isIgnored(wh.watchPath, stats)) {
|
|
30899
|
+
ready();
|
|
30900
|
+
return false;
|
|
30901
|
+
}
|
|
30902
|
+
const follow = this.fsw.options.followSymlinks;
|
|
30903
|
+
let closer;
|
|
30904
|
+
if (stats.isDirectory()) {
|
|
30905
|
+
const absPath = sp.resolve(path2);
|
|
30906
|
+
const targetPath = follow ? await fsrealpath(path2) : path2;
|
|
30907
|
+
if (this.fsw.closed)
|
|
30908
|
+
return;
|
|
30909
|
+
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
30910
|
+
if (this.fsw.closed)
|
|
30911
|
+
return;
|
|
30912
|
+
if (absPath !== targetPath && targetPath !== undefined) {
|
|
30913
|
+
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
30914
|
+
}
|
|
30915
|
+
} else if (stats.isSymbolicLink()) {
|
|
30916
|
+
const targetPath = follow ? await fsrealpath(path2) : path2;
|
|
30917
|
+
if (this.fsw.closed)
|
|
30918
|
+
return;
|
|
30919
|
+
const parent = sp.dirname(wh.watchPath);
|
|
30920
|
+
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
30921
|
+
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
30922
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path2, wh, targetPath);
|
|
30923
|
+
if (this.fsw.closed)
|
|
30924
|
+
return;
|
|
30925
|
+
if (targetPath !== undefined) {
|
|
30926
|
+
this.fsw._symlinkPaths.set(sp.resolve(path2), targetPath);
|
|
30927
|
+
}
|
|
30928
|
+
} else {
|
|
30929
|
+
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
30930
|
+
}
|
|
30931
|
+
ready();
|
|
30932
|
+
if (closer)
|
|
30933
|
+
this.fsw._addPathCloser(path2, closer);
|
|
30934
|
+
return false;
|
|
30935
|
+
} catch (error) {
|
|
30936
|
+
if (this.fsw._handleError(error)) {
|
|
30937
|
+
ready();
|
|
30938
|
+
return path2;
|
|
30939
|
+
}
|
|
30940
|
+
}
|
|
30941
|
+
}
|
|
30942
|
+
}
|
|
30943
|
+
|
|
30944
|
+
// ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
30945
|
+
/*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
|
|
30946
|
+
var SLASH = "/";
|
|
30947
|
+
var SLASH_SLASH = "//";
|
|
30948
|
+
var ONE_DOT = ".";
|
|
30949
|
+
var TWO_DOTS = "..";
|
|
30950
|
+
var STRING_TYPE = "string";
|
|
30951
|
+
var BACK_SLASH_RE = /\\/g;
|
|
30952
|
+
var DOUBLE_SLASH_RE = /\/\//g;
|
|
30953
|
+
var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
|
|
30954
|
+
var REPLACER_RE = /^\.[/\\]/;
|
|
30955
|
+
function arrify(item) {
|
|
30956
|
+
return Array.isArray(item) ? item : [item];
|
|
30957
|
+
}
|
|
30958
|
+
var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
|
|
30959
|
+
function createPattern(matcher) {
|
|
30960
|
+
if (typeof matcher === "function")
|
|
30961
|
+
return matcher;
|
|
30962
|
+
if (typeof matcher === "string")
|
|
30963
|
+
return (string) => matcher === string;
|
|
30964
|
+
if (matcher instanceof RegExp)
|
|
30965
|
+
return (string) => matcher.test(string);
|
|
30966
|
+
if (typeof matcher === "object" && matcher !== null) {
|
|
30967
|
+
return (string) => {
|
|
30968
|
+
if (matcher.path === string)
|
|
30969
|
+
return true;
|
|
30970
|
+
if (matcher.recursive) {
|
|
30971
|
+
const relative4 = sp2.relative(matcher.path, string);
|
|
30972
|
+
if (!relative4) {
|
|
30973
|
+
return false;
|
|
30974
|
+
}
|
|
30975
|
+
return !relative4.startsWith("..") && !sp2.isAbsolute(relative4);
|
|
30976
|
+
}
|
|
30977
|
+
return false;
|
|
30978
|
+
};
|
|
30979
|
+
}
|
|
30980
|
+
return () => false;
|
|
30981
|
+
}
|
|
30982
|
+
function normalizePath(path2) {
|
|
30983
|
+
if (typeof path2 !== "string")
|
|
30984
|
+
throw new Error("string expected");
|
|
30985
|
+
path2 = sp2.normalize(path2);
|
|
30986
|
+
path2 = path2.replace(/\\/g, "/");
|
|
30987
|
+
let prepend = false;
|
|
30988
|
+
if (path2.startsWith("//"))
|
|
30989
|
+
prepend = true;
|
|
30990
|
+
path2 = path2.replace(DOUBLE_SLASH_RE, "/");
|
|
30991
|
+
if (prepend)
|
|
30992
|
+
path2 = "/" + path2;
|
|
30993
|
+
return path2;
|
|
30994
|
+
}
|
|
30995
|
+
function matchPatterns(patterns, testString, stats) {
|
|
30996
|
+
const path2 = normalizePath(testString);
|
|
30997
|
+
for (let index = 0;index < patterns.length; index++) {
|
|
30998
|
+
const pattern = patterns[index];
|
|
30999
|
+
if (pattern(path2, stats)) {
|
|
31000
|
+
return true;
|
|
31001
|
+
}
|
|
31002
|
+
}
|
|
31003
|
+
return false;
|
|
31004
|
+
}
|
|
31005
|
+
function anymatch(matchers, testString) {
|
|
31006
|
+
if (matchers == null) {
|
|
31007
|
+
throw new TypeError("anymatch: specify first argument");
|
|
31008
|
+
}
|
|
31009
|
+
const matchersArray = arrify(matchers);
|
|
31010
|
+
const patterns = matchersArray.map((matcher) => createPattern(matcher));
|
|
31011
|
+
if (testString == null) {
|
|
31012
|
+
return (testString2, stats) => {
|
|
31013
|
+
return matchPatterns(patterns, testString2, stats);
|
|
31014
|
+
};
|
|
31015
|
+
}
|
|
31016
|
+
return matchPatterns(patterns, testString);
|
|
31017
|
+
}
|
|
31018
|
+
var unifyPaths = (paths_) => {
|
|
31019
|
+
const paths = arrify(paths_).flat();
|
|
31020
|
+
if (!paths.every((p) => typeof p === STRING_TYPE)) {
|
|
31021
|
+
throw new TypeError(`Non-string provided as watch path: ${paths}`);
|
|
31022
|
+
}
|
|
31023
|
+
return paths.map(normalizePathToUnix);
|
|
31024
|
+
};
|
|
31025
|
+
var toUnix = (string) => {
|
|
31026
|
+
let str = string.replace(BACK_SLASH_RE, SLASH);
|
|
31027
|
+
let prepend = false;
|
|
31028
|
+
if (str.startsWith(SLASH_SLASH)) {
|
|
31029
|
+
prepend = true;
|
|
31307
31030
|
}
|
|
31308
|
-
|
|
31031
|
+
str = str.replace(DOUBLE_SLASH_RE, SLASH);
|
|
31032
|
+
if (prepend) {
|
|
31033
|
+
str = SLASH + str;
|
|
31034
|
+
}
|
|
31035
|
+
return str;
|
|
31309
31036
|
};
|
|
31310
|
-
var
|
|
31311
|
-
|
|
31312
|
-
if (
|
|
31313
|
-
|
|
31037
|
+
var normalizePathToUnix = (path2) => toUnix(sp2.normalize(toUnix(path2)));
|
|
31038
|
+
var normalizeIgnored = (cwd = "") => (path2) => {
|
|
31039
|
+
if (typeof path2 === "string") {
|
|
31040
|
+
return normalizePathToUnix(sp2.isAbsolute(path2) ? path2 : sp2.join(cwd, path2));
|
|
31314
31041
|
} else {
|
|
31315
|
-
|
|
31042
|
+
return path2;
|
|
31316
31043
|
}
|
|
31317
31044
|
};
|
|
31318
|
-
var
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
container.delete(item);
|
|
31322
|
-
} else if (container === item) {
|
|
31323
|
-
delete main2[prop];
|
|
31045
|
+
var getAbsolutePath = (path2, cwd) => {
|
|
31046
|
+
if (sp2.isAbsolute(path2)) {
|
|
31047
|
+
return path2;
|
|
31324
31048
|
}
|
|
31049
|
+
return sp2.join(cwd, path2);
|
|
31325
31050
|
};
|
|
31326
|
-
var
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
try {
|
|
31337
|
-
return fs_watch(path2, {
|
|
31338
|
-
persistent: options.persistent
|
|
31339
|
-
}, handleEvent);
|
|
31340
|
-
} catch (error) {
|
|
31341
|
-
errHandler(error);
|
|
31342
|
-
return;
|
|
31051
|
+
var EMPTY_SET = Object.freeze(new Set);
|
|
31052
|
+
|
|
31053
|
+
class DirEntry {
|
|
31054
|
+
path;
|
|
31055
|
+
_removeWatcher;
|
|
31056
|
+
items;
|
|
31057
|
+
constructor(dir, removeWatcher) {
|
|
31058
|
+
this.path = dir;
|
|
31059
|
+
this._removeWatcher = removeWatcher;
|
|
31060
|
+
this.items = new Set;
|
|
31343
31061
|
}
|
|
31344
|
-
|
|
31345
|
-
|
|
31346
|
-
|
|
31347
|
-
if (!cont)
|
|
31348
|
-
return;
|
|
31349
|
-
foreach(cont[listenerType], (listener) => {
|
|
31350
|
-
listener(val1, val2, val3);
|
|
31351
|
-
});
|
|
31352
|
-
};
|
|
31353
|
-
var setFsWatchListener = (path2, fullPath, options, handlers) => {
|
|
31354
|
-
const { listener, errHandler, rawEmitter } = handlers;
|
|
31355
|
-
let cont = FsWatchInstances.get(fullPath);
|
|
31356
|
-
let watcher;
|
|
31357
|
-
if (!options.persistent) {
|
|
31358
|
-
watcher = createFsWatchInstance(path2, options, listener, errHandler, rawEmitter);
|
|
31359
|
-
if (!watcher)
|
|
31062
|
+
add(item) {
|
|
31063
|
+
const { items } = this;
|
|
31064
|
+
if (!items)
|
|
31360
31065
|
return;
|
|
31361
|
-
|
|
31066
|
+
if (item !== ONE_DOT && item !== TWO_DOTS)
|
|
31067
|
+
items.add(item);
|
|
31362
31068
|
}
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
31367
|
-
} else {
|
|
31368
|
-
watcher = createFsWatchInstance(path2, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
|
|
31369
|
-
if (!watcher)
|
|
31069
|
+
async remove(item) {
|
|
31070
|
+
const { items } = this;
|
|
31071
|
+
if (!items)
|
|
31370
31072
|
return;
|
|
31371
|
-
|
|
31372
|
-
|
|
31373
|
-
|
|
31374
|
-
|
|
31375
|
-
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31073
|
+
items.delete(item);
|
|
31074
|
+
if (items.size > 0)
|
|
31075
|
+
return;
|
|
31076
|
+
const dir = this.path;
|
|
31077
|
+
try {
|
|
31078
|
+
await readdir3(dir);
|
|
31079
|
+
} catch (err) {
|
|
31080
|
+
if (this._removeWatcher) {
|
|
31081
|
+
this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
|
|
31082
|
+
}
|
|
31083
|
+
}
|
|
31084
|
+
}
|
|
31085
|
+
has(item) {
|
|
31086
|
+
const { items } = this;
|
|
31087
|
+
if (!items)
|
|
31088
|
+
return;
|
|
31089
|
+
return items.has(item);
|
|
31090
|
+
}
|
|
31091
|
+
getChildren() {
|
|
31092
|
+
const { items } = this;
|
|
31093
|
+
if (!items)
|
|
31094
|
+
return [];
|
|
31095
|
+
return [...items.values()];
|
|
31096
|
+
}
|
|
31097
|
+
dispose() {
|
|
31098
|
+
this.items.clear();
|
|
31099
|
+
this.path = "";
|
|
31100
|
+
this._removeWatcher = EMPTY_FN;
|
|
31101
|
+
this.items = EMPTY_SET;
|
|
31102
|
+
Object.freeze(this);
|
|
31103
|
+
}
|
|
31104
|
+
}
|
|
31105
|
+
var STAT_METHOD_F = "stat";
|
|
31106
|
+
var STAT_METHOD_L = "lstat";
|
|
31107
|
+
|
|
31108
|
+
class WatchHelper {
|
|
31109
|
+
fsw;
|
|
31110
|
+
path;
|
|
31111
|
+
watchPath;
|
|
31112
|
+
fullWatchPath;
|
|
31113
|
+
dirParts;
|
|
31114
|
+
followSymlinks;
|
|
31115
|
+
statMethod;
|
|
31116
|
+
constructor(path2, follow, fsw) {
|
|
31117
|
+
this.fsw = fsw;
|
|
31118
|
+
const watchPath = path2;
|
|
31119
|
+
this.path = path2 = path2.replace(REPLACER_RE, "");
|
|
31120
|
+
this.watchPath = watchPath;
|
|
31121
|
+
this.fullWatchPath = sp2.resolve(watchPath);
|
|
31122
|
+
this.dirParts = [];
|
|
31123
|
+
this.dirParts.forEach((parts) => {
|
|
31124
|
+
if (parts.length > 1)
|
|
31125
|
+
parts.pop();
|
|
31126
|
+
});
|
|
31127
|
+
this.followSymlinks = follow;
|
|
31128
|
+
this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
|
|
31129
|
+
}
|
|
31130
|
+
entryPath(entry) {
|
|
31131
|
+
return sp2.join(this.watchPath, sp2.relative(this.watchPath, entry.fullPath));
|
|
31132
|
+
}
|
|
31133
|
+
filterPath(entry) {
|
|
31134
|
+
const { stats } = entry;
|
|
31135
|
+
if (stats && stats.isSymbolicLink())
|
|
31136
|
+
return this.filterDir(entry);
|
|
31137
|
+
const resolvedPath = this.entryPath(entry);
|
|
31138
|
+
return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
|
|
31139
|
+
}
|
|
31140
|
+
filterDir(entry) {
|
|
31141
|
+
return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
|
|
31142
|
+
}
|
|
31143
|
+
}
|
|
31144
|
+
|
|
31145
|
+
class FSWatcher extends EventEmitter {
|
|
31146
|
+
closed;
|
|
31147
|
+
options;
|
|
31148
|
+
_closers;
|
|
31149
|
+
_ignoredPaths;
|
|
31150
|
+
_throttled;
|
|
31151
|
+
_streams;
|
|
31152
|
+
_symlinkPaths;
|
|
31153
|
+
_watched;
|
|
31154
|
+
_pendingWrites;
|
|
31155
|
+
_pendingUnlinks;
|
|
31156
|
+
_readyCount;
|
|
31157
|
+
_emitReady;
|
|
31158
|
+
_closePromise;
|
|
31159
|
+
_userIgnored;
|
|
31160
|
+
_readyEmitted;
|
|
31161
|
+
_emitRaw;
|
|
31162
|
+
_boundRemove;
|
|
31163
|
+
_nodeFsHandler;
|
|
31164
|
+
constructor(_opts = {}) {
|
|
31165
|
+
super();
|
|
31166
|
+
this.closed = false;
|
|
31167
|
+
this._closers = new Map;
|
|
31168
|
+
this._ignoredPaths = new Set;
|
|
31169
|
+
this._throttled = new Map;
|
|
31170
|
+
this._streams = new Set;
|
|
31171
|
+
this._symlinkPaths = new Map;
|
|
31172
|
+
this._watched = new Map;
|
|
31173
|
+
this._pendingWrites = new Map;
|
|
31174
|
+
this._pendingUnlinks = new Map;
|
|
31175
|
+
this._readyCount = 0;
|
|
31176
|
+
this._readyEmitted = false;
|
|
31177
|
+
const awf = _opts.awaitWriteFinish;
|
|
31178
|
+
const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 };
|
|
31179
|
+
const opts = {
|
|
31180
|
+
persistent: true,
|
|
31181
|
+
ignoreInitial: false,
|
|
31182
|
+
ignorePermissionErrors: false,
|
|
31183
|
+
interval: 100,
|
|
31184
|
+
binaryInterval: 300,
|
|
31185
|
+
followSymlinks: true,
|
|
31186
|
+
usePolling: false,
|
|
31187
|
+
atomic: true,
|
|
31188
|
+
..._opts,
|
|
31189
|
+
ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
|
|
31190
|
+
awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
|
|
31191
|
+
};
|
|
31192
|
+
if (isIBMi)
|
|
31193
|
+
opts.usePolling = true;
|
|
31194
|
+
if (opts.atomic === undefined)
|
|
31195
|
+
opts.atomic = !opts.usePolling;
|
|
31196
|
+
const envPoll = process.env.CHOKIDAR_USEPOLLING;
|
|
31197
|
+
if (envPoll !== undefined) {
|
|
31198
|
+
const envLower = envPoll.toLowerCase();
|
|
31199
|
+
if (envLower === "false" || envLower === "0")
|
|
31200
|
+
opts.usePolling = false;
|
|
31201
|
+
else if (envLower === "true" || envLower === "1")
|
|
31202
|
+
opts.usePolling = true;
|
|
31203
|
+
else
|
|
31204
|
+
opts.usePolling = !!envLower;
|
|
31205
|
+
}
|
|
31206
|
+
const envInterval = process.env.CHOKIDAR_INTERVAL;
|
|
31207
|
+
if (envInterval)
|
|
31208
|
+
opts.interval = Number.parseInt(envInterval, 10);
|
|
31209
|
+
let readyCalls = 0;
|
|
31210
|
+
this._emitReady = () => {
|
|
31211
|
+
readyCalls++;
|
|
31212
|
+
if (readyCalls >= this._readyCount) {
|
|
31213
|
+
this._emitReady = EMPTY_FN;
|
|
31214
|
+
this._readyEmitted = true;
|
|
31215
|
+
process.nextTick(() => this.emit(EVENTS.READY));
|
|
31383
31216
|
}
|
|
31384
|
-
});
|
|
31385
|
-
cont = {
|
|
31386
|
-
listeners: listener,
|
|
31387
|
-
errHandlers: errHandler,
|
|
31388
|
-
rawEmitters: rawEmitter,
|
|
31389
|
-
watcher
|
|
31390
31217
|
};
|
|
31391
|
-
|
|
31218
|
+
this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
|
|
31219
|
+
this._boundRemove = this._remove.bind(this);
|
|
31220
|
+
this.options = opts;
|
|
31221
|
+
this._nodeFsHandler = new NodeFsHandler(this);
|
|
31222
|
+
Object.freeze(opts);
|
|
31392
31223
|
}
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
HANDLER_KEYS.forEach(clearItem(cont));
|
|
31401
|
-
cont.watcher = undefined;
|
|
31402
|
-
Object.freeze(cont);
|
|
31224
|
+
_addIgnoredPath(matcher) {
|
|
31225
|
+
if (isMatcherObject(matcher)) {
|
|
31226
|
+
for (const ignored of this._ignoredPaths) {
|
|
31227
|
+
if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
|
|
31228
|
+
return;
|
|
31229
|
+
}
|
|
31230
|
+
}
|
|
31403
31231
|
}
|
|
31404
|
-
|
|
31405
|
-
};
|
|
31406
|
-
var FsWatchFileInstances = new Map;
|
|
31407
|
-
var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
|
|
31408
|
-
const { listener, rawEmitter } = handlers;
|
|
31409
|
-
let cont = FsWatchFileInstances.get(fullPath);
|
|
31410
|
-
const copts = cont && cont.options;
|
|
31411
|
-
if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
|
|
31412
|
-
unwatchFile(fullPath);
|
|
31413
|
-
cont = undefined;
|
|
31232
|
+
this._ignoredPaths.add(matcher);
|
|
31414
31233
|
}
|
|
31415
|
-
|
|
31416
|
-
|
|
31417
|
-
|
|
31418
|
-
|
|
31419
|
-
|
|
31420
|
-
|
|
31421
|
-
rawEmitters: rawEmitter,
|
|
31422
|
-
options,
|
|
31423
|
-
watcher: watchFile(fullPath, options, (curr, prev) => {
|
|
31424
|
-
foreach(cont.rawEmitters, (rawEmitter2) => {
|
|
31425
|
-
rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
|
|
31426
|
-
});
|
|
31427
|
-
const currmtime = curr.mtimeMs;
|
|
31428
|
-
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
31429
|
-
foreach(cont.listeners, (listener2) => listener2(path2, curr));
|
|
31234
|
+
_removeIgnoredPath(matcher) {
|
|
31235
|
+
this._ignoredPaths.delete(matcher);
|
|
31236
|
+
if (typeof matcher === "string") {
|
|
31237
|
+
for (const ignored of this._ignoredPaths) {
|
|
31238
|
+
if (isMatcherObject(ignored) && ignored.path === matcher) {
|
|
31239
|
+
this._ignoredPaths.delete(ignored);
|
|
31430
31240
|
}
|
|
31431
|
-
}
|
|
31432
|
-
};
|
|
31433
|
-
FsWatchFileInstances.set(fullPath, cont);
|
|
31434
|
-
}
|
|
31435
|
-
return () => {
|
|
31436
|
-
delFromSet(cont, KEY_LISTENERS, listener);
|
|
31437
|
-
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
31438
|
-
if (isEmptySet(cont.listeners)) {
|
|
31439
|
-
FsWatchFileInstances.delete(fullPath);
|
|
31440
|
-
unwatchFile(fullPath);
|
|
31441
|
-
cont.options = cont.watcher = undefined;
|
|
31442
|
-
Object.freeze(cont);
|
|
31241
|
+
}
|
|
31443
31242
|
}
|
|
31444
|
-
};
|
|
31445
|
-
};
|
|
31446
|
-
|
|
31447
|
-
class NodeFsHandler {
|
|
31448
|
-
fsw;
|
|
31449
|
-
_boundHandleError;
|
|
31450
|
-
constructor(fsW) {
|
|
31451
|
-
this.fsw = fsW;
|
|
31452
|
-
this._boundHandleError = (error) => fsW._handleError(error);
|
|
31453
31243
|
}
|
|
31454
|
-
|
|
31455
|
-
const
|
|
31456
|
-
|
|
31457
|
-
|
|
31458
|
-
|
|
31459
|
-
|
|
31460
|
-
|
|
31461
|
-
|
|
31462
|
-
|
|
31463
|
-
};
|
|
31464
|
-
if (!listener)
|
|
31465
|
-
listener = EMPTY_FN;
|
|
31466
|
-
let closer;
|
|
31467
|
-
if (opts.usePolling) {
|
|
31468
|
-
const enableBin = opts.interval !== opts.binaryInterval;
|
|
31469
|
-
options.interval = enableBin && isBinaryPath(basename2) ? opts.binaryInterval : opts.interval;
|
|
31470
|
-
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
31471
|
-
listener,
|
|
31472
|
-
rawEmitter: this.fsw._emitRaw
|
|
31244
|
+
add(paths_, _origAdd, _internal) {
|
|
31245
|
+
const { cwd } = this.options;
|
|
31246
|
+
this.closed = false;
|
|
31247
|
+
this._closePromise = undefined;
|
|
31248
|
+
let paths = unifyPaths(paths_);
|
|
31249
|
+
if (cwd) {
|
|
31250
|
+
paths = paths.map((path2) => {
|
|
31251
|
+
const absPath = getAbsolutePath(path2, cwd);
|
|
31252
|
+
return absPath;
|
|
31473
31253
|
});
|
|
31474
|
-
}
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
|
|
31478
|
-
|
|
31254
|
+
}
|
|
31255
|
+
paths.forEach((path2) => {
|
|
31256
|
+
this._removeIgnoredPath(path2);
|
|
31257
|
+
});
|
|
31258
|
+
this._userIgnored = undefined;
|
|
31259
|
+
if (!this._readyCount)
|
|
31260
|
+
this._readyCount = 0;
|
|
31261
|
+
this._readyCount += paths.length;
|
|
31262
|
+
Promise.all(paths.map(async (path2) => {
|
|
31263
|
+
const res = await this._nodeFsHandler._addToNodeFs(path2, !_internal, undefined, 0, _origAdd);
|
|
31264
|
+
if (res)
|
|
31265
|
+
this._emitReady();
|
|
31266
|
+
return res;
|
|
31267
|
+
})).then((results) => {
|
|
31268
|
+
if (this.closed)
|
|
31269
|
+
return;
|
|
31270
|
+
results.forEach((item) => {
|
|
31271
|
+
if (item)
|
|
31272
|
+
this.add(sp2.dirname(item), sp2.basename(_origAdd || item));
|
|
31479
31273
|
});
|
|
31274
|
+
});
|
|
31275
|
+
return this;
|
|
31276
|
+
}
|
|
31277
|
+
unwatch(paths_) {
|
|
31278
|
+
if (this.closed)
|
|
31279
|
+
return this;
|
|
31280
|
+
const paths = unifyPaths(paths_);
|
|
31281
|
+
const { cwd } = this.options;
|
|
31282
|
+
paths.forEach((path2) => {
|
|
31283
|
+
if (!sp2.isAbsolute(path2) && !this._closers.has(path2)) {
|
|
31284
|
+
if (cwd)
|
|
31285
|
+
path2 = sp2.join(cwd, path2);
|
|
31286
|
+
path2 = sp2.resolve(path2);
|
|
31287
|
+
}
|
|
31288
|
+
this._closePath(path2);
|
|
31289
|
+
this._addIgnoredPath(path2);
|
|
31290
|
+
if (this._watched.has(path2)) {
|
|
31291
|
+
this._addIgnoredPath({
|
|
31292
|
+
path: path2,
|
|
31293
|
+
recursive: true
|
|
31294
|
+
});
|
|
31295
|
+
}
|
|
31296
|
+
this._userIgnored = undefined;
|
|
31297
|
+
});
|
|
31298
|
+
return this;
|
|
31299
|
+
}
|
|
31300
|
+
close() {
|
|
31301
|
+
if (this._closePromise) {
|
|
31302
|
+
return this._closePromise;
|
|
31480
31303
|
}
|
|
31481
|
-
|
|
31304
|
+
this.closed = true;
|
|
31305
|
+
this.removeAllListeners();
|
|
31306
|
+
const closers = [];
|
|
31307
|
+
this._closers.forEach((closerList) => closerList.forEach((closer) => {
|
|
31308
|
+
const promise = closer();
|
|
31309
|
+
if (promise instanceof Promise)
|
|
31310
|
+
closers.push(promise);
|
|
31311
|
+
}));
|
|
31312
|
+
this._streams.forEach((stream) => stream.destroy());
|
|
31313
|
+
this._userIgnored = undefined;
|
|
31314
|
+
this._readyCount = 0;
|
|
31315
|
+
this._readyEmitted = false;
|
|
31316
|
+
this._watched.forEach((dirent) => dirent.dispose());
|
|
31317
|
+
this._closers.clear();
|
|
31318
|
+
this._watched.clear();
|
|
31319
|
+
this._streams.clear();
|
|
31320
|
+
this._symlinkPaths.clear();
|
|
31321
|
+
this._throttled.clear();
|
|
31322
|
+
this._closePromise = closers.length ? Promise.all(closers).then(() => {
|
|
31323
|
+
return;
|
|
31324
|
+
}) : Promise.resolve();
|
|
31325
|
+
return this._closePromise;
|
|
31482
31326
|
}
|
|
31483
|
-
|
|
31484
|
-
|
|
31327
|
+
getWatched() {
|
|
31328
|
+
const watchList = {};
|
|
31329
|
+
this._watched.forEach((entry, dir) => {
|
|
31330
|
+
const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
|
|
31331
|
+
const index = key || ONE_DOT;
|
|
31332
|
+
watchList[index] = entry.getChildren().sort();
|
|
31333
|
+
});
|
|
31334
|
+
return watchList;
|
|
31335
|
+
}
|
|
31336
|
+
emitWithAll(event, args) {
|
|
31337
|
+
this.emit(event, ...args);
|
|
31338
|
+
if (event !== EVENTS.ERROR)
|
|
31339
|
+
this.emit(EVENTS.ALL, event, ...args);
|
|
31340
|
+
}
|
|
31341
|
+
async _emit(event, path2, stats) {
|
|
31342
|
+
if (this.closed)
|
|
31485
31343
|
return;
|
|
31344
|
+
const opts = this.options;
|
|
31345
|
+
if (isWindows)
|
|
31346
|
+
path2 = sp2.normalize(path2);
|
|
31347
|
+
if (opts.cwd)
|
|
31348
|
+
path2 = sp2.relative(opts.cwd, path2);
|
|
31349
|
+
const args = [path2];
|
|
31350
|
+
if (stats != null)
|
|
31351
|
+
args.push(stats);
|
|
31352
|
+
const awf = opts.awaitWriteFinish;
|
|
31353
|
+
let pw;
|
|
31354
|
+
if (awf && (pw = this._pendingWrites.get(path2))) {
|
|
31355
|
+
pw.lastChange = new Date;
|
|
31356
|
+
return this;
|
|
31486
31357
|
}
|
|
31487
|
-
|
|
31488
|
-
|
|
31489
|
-
|
|
31490
|
-
|
|
31491
|
-
|
|
31492
|
-
|
|
31493
|
-
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
|
|
31497
|
-
|
|
31498
|
-
|
|
31499
|
-
|
|
31500
|
-
|
|
31501
|
-
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
31510
|
-
|
|
31511
|
-
|
|
31358
|
+
if (opts.atomic) {
|
|
31359
|
+
if (event === EVENTS.UNLINK) {
|
|
31360
|
+
this._pendingUnlinks.set(path2, [event, ...args]);
|
|
31361
|
+
setTimeout(() => {
|
|
31362
|
+
this._pendingUnlinks.forEach((entry, path3) => {
|
|
31363
|
+
this.emit(...entry);
|
|
31364
|
+
this.emit(EVENTS.ALL, ...entry);
|
|
31365
|
+
this._pendingUnlinks.delete(path3);
|
|
31366
|
+
});
|
|
31367
|
+
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
31368
|
+
return this;
|
|
31369
|
+
}
|
|
31370
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path2)) {
|
|
31371
|
+
event = EVENTS.CHANGE;
|
|
31372
|
+
this._pendingUnlinks.delete(path2);
|
|
31373
|
+
}
|
|
31374
|
+
}
|
|
31375
|
+
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
31376
|
+
const awfEmit = (err, stats2) => {
|
|
31377
|
+
if (err) {
|
|
31378
|
+
event = EVENTS.ERROR;
|
|
31379
|
+
args[0] = err;
|
|
31380
|
+
this.emitWithAll(event, args);
|
|
31381
|
+
} else if (stats2) {
|
|
31382
|
+
if (args.length > 1) {
|
|
31383
|
+
args[1] = stats2;
|
|
31512
31384
|
} else {
|
|
31513
|
-
|
|
31385
|
+
args.push(stats2);
|
|
31514
31386
|
}
|
|
31515
|
-
|
|
31516
|
-
this.fsw._remove(dirname3, basename2);
|
|
31517
|
-
}
|
|
31518
|
-
} else if (parent.has(basename2)) {
|
|
31519
|
-
const at = newStats.atimeMs;
|
|
31520
|
-
const mt = newStats.mtimeMs;
|
|
31521
|
-
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
31522
|
-
this.fsw._emit(EV.CHANGE, file, newStats);
|
|
31387
|
+
this.emitWithAll(event, args);
|
|
31523
31388
|
}
|
|
31524
|
-
|
|
31525
|
-
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
if (
|
|
31529
|
-
|
|
31389
|
+
};
|
|
31390
|
+
this._awaitWriteFinish(path2, awf.stabilityThreshold, event, awfEmit);
|
|
31391
|
+
return this;
|
|
31392
|
+
}
|
|
31393
|
+
if (event === EVENTS.CHANGE) {
|
|
31394
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path2, 50);
|
|
31395
|
+
if (isThrottled)
|
|
31396
|
+
return this;
|
|
31397
|
+
}
|
|
31398
|
+
if (opts.alwaysStat && stats === undefined && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
31399
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path2) : path2;
|
|
31400
|
+
let stats2;
|
|
31401
|
+
try {
|
|
31402
|
+
stats2 = await stat4(fullPath);
|
|
31403
|
+
} catch (err) {}
|
|
31404
|
+
if (!stats2 || this.closed)
|
|
31530
31405
|
return;
|
|
31531
|
-
|
|
31406
|
+
args.push(stats2);
|
|
31532
31407
|
}
|
|
31533
|
-
|
|
31408
|
+
this.emitWithAll(event, args);
|
|
31409
|
+
return this;
|
|
31534
31410
|
}
|
|
31535
|
-
|
|
31536
|
-
|
|
31411
|
+
_handleError(error) {
|
|
31412
|
+
const code = error && error.code;
|
|
31413
|
+
if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
|
|
31414
|
+
this.emit(EVENTS.ERROR, error);
|
|
31415
|
+
}
|
|
31416
|
+
return error || this.closed;
|
|
31417
|
+
}
|
|
31418
|
+
_throttle(actionType, path2, timeout) {
|
|
31419
|
+
if (!this._throttled.has(actionType)) {
|
|
31420
|
+
this._throttled.set(actionType, new Map);
|
|
31421
|
+
}
|
|
31422
|
+
const action = this._throttled.get(actionType);
|
|
31423
|
+
if (!action)
|
|
31424
|
+
throw new Error("invalid throttle");
|
|
31425
|
+
const actionPath = action.get(path2);
|
|
31426
|
+
if (actionPath) {
|
|
31427
|
+
actionPath.count++;
|
|
31428
|
+
return false;
|
|
31429
|
+
}
|
|
31430
|
+
let timeoutObject;
|
|
31431
|
+
const clear = () => {
|
|
31432
|
+
const item = action.get(path2);
|
|
31433
|
+
const count = item ? item.count : 0;
|
|
31434
|
+
action.delete(path2);
|
|
31435
|
+
clearTimeout(timeoutObject);
|
|
31436
|
+
if (item)
|
|
31437
|
+
clearTimeout(item.timeoutObject);
|
|
31438
|
+
return count;
|
|
31439
|
+
};
|
|
31440
|
+
timeoutObject = setTimeout(clear, timeout);
|
|
31441
|
+
const thr = { timeoutObject, clear, count: 0 };
|
|
31442
|
+
action.set(path2, thr);
|
|
31443
|
+
return thr;
|
|
31444
|
+
}
|
|
31445
|
+
_incrReadyCount() {
|
|
31446
|
+
return this._readyCount++;
|
|
31447
|
+
}
|
|
31448
|
+
_awaitWriteFinish(path2, threshold, event, awfEmit) {
|
|
31449
|
+
const awf = this.options.awaitWriteFinish;
|
|
31450
|
+
if (typeof awf !== "object")
|
|
31537
31451
|
return;
|
|
31452
|
+
const pollInterval = awf.pollInterval;
|
|
31453
|
+
let timeoutHandler;
|
|
31454
|
+
let fullPath = path2;
|
|
31455
|
+
if (this.options.cwd && !sp2.isAbsolute(path2)) {
|
|
31456
|
+
fullPath = sp2.join(this.options.cwd, path2);
|
|
31538
31457
|
}
|
|
31539
|
-
const
|
|
31540
|
-
const
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
this.fsw._emitReady();
|
|
31548
|
-
return true;
|
|
31549
|
-
}
|
|
31550
|
-
if (this.fsw.closed)
|
|
31551
|
-
return;
|
|
31552
|
-
if (dir.has(item)) {
|
|
31553
|
-
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
31554
|
-
this.fsw._symlinkPaths.set(full, linkPath);
|
|
31555
|
-
this.fsw._emit(EV.CHANGE, path2, entry.stats);
|
|
31458
|
+
const now = new Date;
|
|
31459
|
+
const writes = this._pendingWrites;
|
|
31460
|
+
function awaitWriteFinishFn(prevStat) {
|
|
31461
|
+
statcb(fullPath, (err, curStat) => {
|
|
31462
|
+
if (err || !writes.has(path2)) {
|
|
31463
|
+
if (err && err.code !== "ENOENT")
|
|
31464
|
+
awfEmit(err);
|
|
31465
|
+
return;
|
|
31556
31466
|
}
|
|
31557
|
-
|
|
31558
|
-
|
|
31559
|
-
|
|
31560
|
-
|
|
31561
|
-
|
|
31562
|
-
|
|
31467
|
+
const now2 = Number(new Date);
|
|
31468
|
+
if (prevStat && curStat.size !== prevStat.size) {
|
|
31469
|
+
writes.get(path2).lastChange = now2;
|
|
31470
|
+
}
|
|
31471
|
+
const pw = writes.get(path2);
|
|
31472
|
+
const df = now2 - pw.lastChange;
|
|
31473
|
+
if (df >= threshold) {
|
|
31474
|
+
writes.delete(path2);
|
|
31475
|
+
awfEmit(undefined, curStat);
|
|
31476
|
+
} else {
|
|
31477
|
+
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
31478
|
+
}
|
|
31479
|
+
});
|
|
31480
|
+
}
|
|
31481
|
+
if (!writes.has(path2)) {
|
|
31482
|
+
writes.set(path2, {
|
|
31483
|
+
lastChange: now,
|
|
31484
|
+
cancelWait: () => {
|
|
31485
|
+
writes.delete(path2);
|
|
31486
|
+
clearTimeout(timeoutHandler);
|
|
31487
|
+
return event;
|
|
31488
|
+
}
|
|
31489
|
+
});
|
|
31490
|
+
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
|
|
31491
|
+
}
|
|
31492
|
+
}
|
|
31493
|
+
_isIgnored(path2, stats) {
|
|
31494
|
+
if (this.options.atomic && DOT_RE.test(path2))
|
|
31563
31495
|
return true;
|
|
31496
|
+
if (!this._userIgnored) {
|
|
31497
|
+
const { cwd } = this.options;
|
|
31498
|
+
const ign = this.options.ignored;
|
|
31499
|
+
const ignored = (ign || []).map(normalizeIgnored(cwd));
|
|
31500
|
+
const ignoredPaths = [...this._ignoredPaths];
|
|
31501
|
+
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
31502
|
+
this._userIgnored = anymatch(list, undefined);
|
|
31564
31503
|
}
|
|
31565
|
-
|
|
31504
|
+
return this._userIgnored(path2, stats);
|
|
31505
|
+
}
|
|
31506
|
+
_isntIgnored(path2, stat5) {
|
|
31507
|
+
return !this._isIgnored(path2, stat5);
|
|
31508
|
+
}
|
|
31509
|
+
_getWatchHelpers(path2) {
|
|
31510
|
+
return new WatchHelper(path2, this.options.followSymlinks, this);
|
|
31511
|
+
}
|
|
31512
|
+
_getWatchedDir(directory) {
|
|
31513
|
+
const dir = sp2.resolve(directory);
|
|
31514
|
+
if (!this._watched.has(dir))
|
|
31515
|
+
this._watched.set(dir, new DirEntry(dir, this._boundRemove));
|
|
31516
|
+
return this._watched.get(dir);
|
|
31517
|
+
}
|
|
31518
|
+
_hasReadPermissions(stats) {
|
|
31519
|
+
if (this.options.ignorePermissionErrors)
|
|
31566
31520
|
return true;
|
|
31521
|
+
return Boolean(Number(stats.mode) & 256);
|
|
31522
|
+
}
|
|
31523
|
+
_remove(directory, item, isDirectory) {
|
|
31524
|
+
const path2 = sp2.join(directory, item);
|
|
31525
|
+
const fullPath = sp2.resolve(path2);
|
|
31526
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path2) || this._watched.has(fullPath);
|
|
31527
|
+
if (!this._throttle("remove", path2, 100))
|
|
31528
|
+
return;
|
|
31529
|
+
if (!isDirectory && this._watched.size === 1) {
|
|
31530
|
+
this.add(directory, item, true);
|
|
31567
31531
|
}
|
|
31568
|
-
this.
|
|
31532
|
+
const wp = this._getWatchedDir(path2);
|
|
31533
|
+
const nestedDirectoryChildren = wp.getChildren();
|
|
31534
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path2, nested));
|
|
31535
|
+
const parent = this._getWatchedDir(directory);
|
|
31536
|
+
const wasTracked = parent.has(item);
|
|
31537
|
+
parent.remove(item);
|
|
31538
|
+
if (this._symlinkPaths.has(fullPath)) {
|
|
31539
|
+
this._symlinkPaths.delete(fullPath);
|
|
31540
|
+
}
|
|
31541
|
+
let relPath = path2;
|
|
31542
|
+
if (this.options.cwd)
|
|
31543
|
+
relPath = sp2.relative(this.options.cwd, path2);
|
|
31544
|
+
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
31545
|
+
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
31546
|
+
if (event === EVENTS.ADD)
|
|
31547
|
+
return;
|
|
31548
|
+
}
|
|
31549
|
+
this._watched.delete(path2);
|
|
31550
|
+
this._watched.delete(fullPath);
|
|
31551
|
+
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
31552
|
+
if (wasTracked && !this._isIgnored(path2))
|
|
31553
|
+
this._emit(eventName, path2);
|
|
31554
|
+
this._closePath(path2);
|
|
31569
31555
|
}
|
|
31570
|
-
|
|
31571
|
-
|
|
31572
|
-
const
|
|
31573
|
-
|
|
31574
|
-
|
|
31556
|
+
_closePath(path2) {
|
|
31557
|
+
this._closeFile(path2);
|
|
31558
|
+
const dir = sp2.dirname(path2);
|
|
31559
|
+
this._getWatchedDir(dir).remove(sp2.basename(path2));
|
|
31560
|
+
}
|
|
31561
|
+
_closeFile(path2) {
|
|
31562
|
+
const closers = this._closers.get(path2);
|
|
31563
|
+
if (!closers)
|
|
31575
31564
|
return;
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31582
|
-
|
|
31565
|
+
closers.forEach((closer) => closer());
|
|
31566
|
+
this._closers.delete(path2);
|
|
31567
|
+
}
|
|
31568
|
+
_addPathCloser(path2, closer) {
|
|
31569
|
+
if (!closer)
|
|
31570
|
+
return;
|
|
31571
|
+
let list = this._closers.get(path2);
|
|
31572
|
+
if (!list) {
|
|
31573
|
+
list = [];
|
|
31574
|
+
this._closers.set(path2, list);
|
|
31575
|
+
}
|
|
31576
|
+
list.push(closer);
|
|
31577
|
+
}
|
|
31578
|
+
_readdirp(root, opts) {
|
|
31579
|
+
if (this.closed)
|
|
31583
31580
|
return;
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
31592
|
-
|
|
31593
|
-
return;
|
|
31594
|
-
}
|
|
31595
|
-
if (this.fsw.closed) {
|
|
31581
|
+
const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
31582
|
+
let stream = readdirp(root, options);
|
|
31583
|
+
this._streams.add(stream);
|
|
31584
|
+
stream.once(STR_CLOSE, () => {
|
|
31585
|
+
stream = undefined;
|
|
31586
|
+
});
|
|
31587
|
+
stream.once(STR_END, () => {
|
|
31588
|
+
if (stream) {
|
|
31589
|
+
this._streams.delete(stream);
|
|
31596
31590
|
stream = undefined;
|
|
31597
|
-
return;
|
|
31598
|
-
}
|
|
31599
|
-
if (item === target || !target && !previous.has(item)) {
|
|
31600
|
-
this.fsw._incrReadyCount();
|
|
31601
|
-
path2 = sp.join(dir, sp.relative(dir, path2));
|
|
31602
|
-
this._addToNodeFs(path2, initialAdd, wh, depth + 1);
|
|
31603
31591
|
}
|
|
31604
|
-
}).on(EV.ERROR, this._boundHandleError);
|
|
31605
|
-
return new Promise((resolve2, reject) => {
|
|
31606
|
-
if (!stream)
|
|
31607
|
-
return reject();
|
|
31608
|
-
stream.once(STR_END, () => {
|
|
31609
|
-
if (this.fsw.closed) {
|
|
31610
|
-
stream = undefined;
|
|
31611
|
-
return;
|
|
31612
|
-
}
|
|
31613
|
-
const wasThrottled = throttler ? throttler.clear() : false;
|
|
31614
|
-
resolve2(undefined);
|
|
31615
|
-
previous.getChildren().filter((item) => {
|
|
31616
|
-
return item !== directory && !current.has(item);
|
|
31617
|
-
}).forEach((item) => {
|
|
31618
|
-
this.fsw._remove(directory, item);
|
|
31619
|
-
});
|
|
31620
|
-
stream = undefined;
|
|
31621
|
-
if (wasThrottled)
|
|
31622
|
-
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
31623
|
-
});
|
|
31624
31592
|
});
|
|
31593
|
+
return stream;
|
|
31625
31594
|
}
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
|
|
31645
|
-
|
|
31646
|
-
this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
|
|
31647
|
-
});
|
|
31648
|
-
}
|
|
31649
|
-
return closer;
|
|
31650
|
-
}
|
|
31651
|
-
async _addToNodeFs(path2, initialAdd, priorWh, depth, target) {
|
|
31652
|
-
const ready = this.fsw._emitReady;
|
|
31653
|
-
if (this.fsw._isIgnored(path2) || this.fsw.closed) {
|
|
31654
|
-
ready();
|
|
31655
|
-
return false;
|
|
31656
|
-
}
|
|
31657
|
-
const wh = this.fsw._getWatchHelpers(path2);
|
|
31658
|
-
if (priorWh) {
|
|
31659
|
-
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
31660
|
-
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
31595
|
+
}
|
|
31596
|
+
function watch(paths, options = {}) {
|
|
31597
|
+
const watcher = new FSWatcher(options);
|
|
31598
|
+
watcher.add(paths);
|
|
31599
|
+
return watcher;
|
|
31600
|
+
}
|
|
31601
|
+
|
|
31602
|
+
// src/daemon/watcher.ts
|
|
31603
|
+
init_src();
|
|
31604
|
+
function startWatcher(onSync) {
|
|
31605
|
+
const claudeDir = getClaudeDir();
|
|
31606
|
+
let debounceTimer = null;
|
|
31607
|
+
let syncing = false;
|
|
31608
|
+
const watcher = watch(claudeDir, {
|
|
31609
|
+
ignored: NEVER_SYNC_PATTERNS.map((p) => `**/${p}`),
|
|
31610
|
+
persistent: true,
|
|
31611
|
+
ignoreInitial: true,
|
|
31612
|
+
awaitWriteFinish: {
|
|
31613
|
+
stabilityThreshold: 2000,
|
|
31614
|
+
pollInterval: 500
|
|
31661
31615
|
}
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
31616
|
+
});
|
|
31617
|
+
const triggerSync = () => {
|
|
31618
|
+
if (debounceTimer)
|
|
31619
|
+
clearTimeout(debounceTimer);
|
|
31620
|
+
debounceTimer = setTimeout(async () => {
|
|
31621
|
+
if (syncing)
|
|
31665
31622
|
return;
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
}
|
|
31670
|
-
|
|
31671
|
-
let closer;
|
|
31672
|
-
if (stats.isDirectory()) {
|
|
31673
|
-
const absPath = sp.resolve(path2);
|
|
31674
|
-
const targetPath = follow ? await fsrealpath(path2) : path2;
|
|
31675
|
-
if (this.fsw.closed)
|
|
31676
|
-
return;
|
|
31677
|
-
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
31678
|
-
if (this.fsw.closed)
|
|
31679
|
-
return;
|
|
31680
|
-
if (absPath !== targetPath && targetPath !== undefined) {
|
|
31681
|
-
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
31682
|
-
}
|
|
31683
|
-
} else if (stats.isSymbolicLink()) {
|
|
31684
|
-
const targetPath = follow ? await fsrealpath(path2) : path2;
|
|
31685
|
-
if (this.fsw.closed)
|
|
31686
|
-
return;
|
|
31687
|
-
const parent = sp.dirname(wh.watchPath);
|
|
31688
|
-
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
31689
|
-
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
31690
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth, path2, wh, targetPath);
|
|
31691
|
-
if (this.fsw.closed)
|
|
31692
|
-
return;
|
|
31693
|
-
if (targetPath !== undefined) {
|
|
31694
|
-
this.fsw._symlinkPaths.set(sp.resolve(path2), targetPath);
|
|
31695
|
-
}
|
|
31696
|
-
} else {
|
|
31697
|
-
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
31698
|
-
}
|
|
31699
|
-
ready();
|
|
31700
|
-
if (closer)
|
|
31701
|
-
this.fsw._addPathCloser(path2, closer);
|
|
31702
|
-
return false;
|
|
31703
|
-
} catch (error) {
|
|
31704
|
-
if (this.fsw._handleError(error)) {
|
|
31705
|
-
ready();
|
|
31706
|
-
return path2;
|
|
31623
|
+
syncing = true;
|
|
31624
|
+
try {
|
|
31625
|
+
await onSync();
|
|
31626
|
+
} catch {} finally {
|
|
31627
|
+
syncing = false;
|
|
31707
31628
|
}
|
|
31629
|
+
}, DAEMON_DEBOUNCE_MS);
|
|
31630
|
+
};
|
|
31631
|
+
watcher.on("change", triggerSync);
|
|
31632
|
+
watcher.on("add", triggerSync);
|
|
31633
|
+
watcher.on("unlink", triggerSync);
|
|
31634
|
+
return {
|
|
31635
|
+
stop: () => {
|
|
31636
|
+
if (debounceTimer)
|
|
31637
|
+
clearTimeout(debounceTimer);
|
|
31638
|
+
watcher.close();
|
|
31708
31639
|
}
|
|
31709
|
-
}
|
|
31640
|
+
};
|
|
31710
31641
|
}
|
|
31711
31642
|
|
|
31712
|
-
//
|
|
31713
|
-
|
|
31714
|
-
|
|
31715
|
-
var
|
|
31716
|
-
|
|
31717
|
-
|
|
31718
|
-
var STRING_TYPE = "string";
|
|
31719
|
-
var BACK_SLASH_RE = /\\/g;
|
|
31720
|
-
var DOUBLE_SLASH_RE = /\/\//g;
|
|
31721
|
-
var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
|
|
31722
|
-
var REPLACER_RE = /^\.[/\\]/;
|
|
31723
|
-
function arrify(item) {
|
|
31724
|
-
return Array.isArray(item) ? item : [item];
|
|
31643
|
+
// src/daemon/runner.ts
|
|
31644
|
+
init_src();
|
|
31645
|
+
init_src();
|
|
31646
|
+
var HEARTBEAT_INTERVAL_MS = 3 * 60 * 1000;
|
|
31647
|
+
function getStatusPath(configDir) {
|
|
31648
|
+
return join11(configDir, "daemon.status.json");
|
|
31725
31649
|
}
|
|
31726
|
-
|
|
31727
|
-
|
|
31728
|
-
if (typeof matcher === "function")
|
|
31729
|
-
return matcher;
|
|
31730
|
-
if (typeof matcher === "string")
|
|
31731
|
-
return (string) => matcher === string;
|
|
31732
|
-
if (matcher instanceof RegExp)
|
|
31733
|
-
return (string) => matcher.test(string);
|
|
31734
|
-
if (typeof matcher === "object" && matcher !== null) {
|
|
31735
|
-
return (string) => {
|
|
31736
|
-
if (matcher.path === string)
|
|
31737
|
-
return true;
|
|
31738
|
-
if (matcher.recursive) {
|
|
31739
|
-
const relative5 = sp2.relative(matcher.path, string);
|
|
31740
|
-
if (!relative5) {
|
|
31741
|
-
return false;
|
|
31742
|
-
}
|
|
31743
|
-
return !relative5.startsWith("..") && !sp2.isAbsolute(relative5);
|
|
31744
|
-
}
|
|
31745
|
-
return false;
|
|
31746
|
-
};
|
|
31747
|
-
}
|
|
31748
|
-
return () => false;
|
|
31650
|
+
function getLogPath(configDir) {
|
|
31651
|
+
return join11(configDir, "daemon.log");
|
|
31749
31652
|
}
|
|
31750
|
-
function
|
|
31751
|
-
|
|
31752
|
-
throw new Error("string expected");
|
|
31753
|
-
path2 = sp2.normalize(path2);
|
|
31754
|
-
path2 = path2.replace(/\\/g, "/");
|
|
31755
|
-
let prepend = false;
|
|
31756
|
-
if (path2.startsWith("//"))
|
|
31757
|
-
prepend = true;
|
|
31758
|
-
path2 = path2.replace(DOUBLE_SLASH_RE, "/");
|
|
31759
|
-
if (prepend)
|
|
31760
|
-
path2 = "/" + path2;
|
|
31761
|
-
return path2;
|
|
31653
|
+
function getPidPath(configDir) {
|
|
31654
|
+
return join11(configDir, "daemon.pid");
|
|
31762
31655
|
}
|
|
31763
|
-
function
|
|
31764
|
-
const
|
|
31765
|
-
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
31769
|
-
|
|
31770
|
-
|
|
31771
|
-
|
|
31656
|
+
function log(configDir, message2) {
|
|
31657
|
+
const ts = new Date().toISOString();
|
|
31658
|
+
const line = `[${ts}] ${message2}
|
|
31659
|
+
`;
|
|
31660
|
+
try {
|
|
31661
|
+
appendFileSync(getLogPath(configDir), line);
|
|
31662
|
+
} catch {}
|
|
31663
|
+
}
|
|
31664
|
+
async function getMasterKey(configDir) {
|
|
31665
|
+
const cachedKeyPath = join11(configDir, ".cached-key");
|
|
31666
|
+
const cached = await readFile7(cachedKeyPath);
|
|
31667
|
+
return new Uint8Array(cached);
|
|
31668
|
+
}
|
|
31669
|
+
async function getAuthenticatedClient(configDir) {
|
|
31670
|
+
const config = await loadKeys(configDir);
|
|
31671
|
+
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
31672
|
+
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
31673
|
+
return new CcsiniClient(config.apiUrl, jwt);
|
|
31674
|
+
}
|
|
31675
|
+
async function getSessionOptions(configDir) {
|
|
31676
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
31677
|
+
return {
|
|
31678
|
+
enabled: storedSession.enabled,
|
|
31679
|
+
maxPerProject: storedSession.maxPerProject,
|
|
31680
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
31681
|
+
};
|
|
31772
31682
|
}
|
|
31773
|
-
function
|
|
31774
|
-
|
|
31775
|
-
throw new TypeError("anymatch: specify first argument");
|
|
31776
|
-
}
|
|
31777
|
-
const matchersArray = arrify(matchers);
|
|
31778
|
-
const patterns = matchersArray.map((matcher) => createPattern(matcher));
|
|
31779
|
-
if (testString == null) {
|
|
31780
|
-
return (testString2, stats) => {
|
|
31781
|
-
return matchPatterns(patterns, testString2, stats);
|
|
31782
|
-
};
|
|
31783
|
-
}
|
|
31784
|
-
return matchPatterns(patterns, testString);
|
|
31683
|
+
async function writeStatus(configDir, status) {
|
|
31684
|
+
await writeFile8(getStatusPath(configDir), JSON.stringify(status, null, 2));
|
|
31785
31685
|
}
|
|
31786
|
-
|
|
31787
|
-
const
|
|
31788
|
-
if (!
|
|
31789
|
-
|
|
31790
|
-
|
|
31791
|
-
return paths.map(normalizePathToUnix);
|
|
31792
|
-
};
|
|
31793
|
-
var toUnix = (string) => {
|
|
31794
|
-
let str = string.replace(BACK_SLASH_RE, SLASH);
|
|
31795
|
-
let prepend = false;
|
|
31796
|
-
if (str.startsWith(SLASH_SLASH)) {
|
|
31797
|
-
prepend = true;
|
|
31798
|
-
}
|
|
31799
|
-
str = str.replace(DOUBLE_SLASH_RE, SLASH);
|
|
31800
|
-
if (prepend) {
|
|
31801
|
-
str = SLASH + str;
|
|
31802
|
-
}
|
|
31803
|
-
return str;
|
|
31804
|
-
};
|
|
31805
|
-
var normalizePathToUnix = (path2) => toUnix(sp2.normalize(toUnix(path2)));
|
|
31806
|
-
var normalizeIgnored = (cwd = "") => (path2) => {
|
|
31807
|
-
if (typeof path2 === "string") {
|
|
31808
|
-
return normalizePathToUnix(sp2.isAbsolute(path2) ? path2 : sp2.join(cwd, path2));
|
|
31809
|
-
} else {
|
|
31810
|
-
return path2;
|
|
31811
|
-
}
|
|
31812
|
-
};
|
|
31813
|
-
var getAbsolutePath = (path2, cwd) => {
|
|
31814
|
-
if (sp2.isAbsolute(path2)) {
|
|
31815
|
-
return path2;
|
|
31816
|
-
}
|
|
31817
|
-
return sp2.join(cwd, path2);
|
|
31818
|
-
};
|
|
31819
|
-
var EMPTY_SET = Object.freeze(new Set);
|
|
31820
|
-
|
|
31821
|
-
class DirEntry {
|
|
31822
|
-
path;
|
|
31823
|
-
_removeWatcher;
|
|
31824
|
-
items;
|
|
31825
|
-
constructor(dir, removeWatcher) {
|
|
31826
|
-
this.path = dir;
|
|
31827
|
-
this._removeWatcher = removeWatcher;
|
|
31828
|
-
this.items = new Set;
|
|
31686
|
+
async function runDaemon() {
|
|
31687
|
+
const configDir = getConfigDir();
|
|
31688
|
+
if (!await configExists(configDir)) {
|
|
31689
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
31690
|
+
process.exit(1);
|
|
31829
31691
|
}
|
|
31830
|
-
|
|
31831
|
-
|
|
31832
|
-
|
|
31833
|
-
|
|
31834
|
-
|
|
31835
|
-
items.add(item);
|
|
31692
|
+
try {
|
|
31693
|
+
await getMasterKey(configDir);
|
|
31694
|
+
} catch {
|
|
31695
|
+
console.error("No cached key. Run 'ccsini unlock' first.");
|
|
31696
|
+
process.exit(1);
|
|
31836
31697
|
}
|
|
31837
|
-
|
|
31838
|
-
|
|
31839
|
-
|
|
31840
|
-
|
|
31841
|
-
|
|
31842
|
-
|
|
31843
|
-
|
|
31844
|
-
|
|
31698
|
+
const pid = process.pid;
|
|
31699
|
+
await writeFile8(getPidPath(configDir), String(pid));
|
|
31700
|
+
const status = {
|
|
31701
|
+
pid,
|
|
31702
|
+
startedAt: Date.now(),
|
|
31703
|
+
lastPushAt: null,
|
|
31704
|
+
lastPullAt: null,
|
|
31705
|
+
lastError: null,
|
|
31706
|
+
pushCount: 0,
|
|
31707
|
+
pullCount: 0
|
|
31708
|
+
};
|
|
31709
|
+
await writeStatus(configDir, status);
|
|
31710
|
+
log(configDir, `Daemon started (PID ${pid})`);
|
|
31711
|
+
async function doPush() {
|
|
31845
31712
|
try {
|
|
31846
|
-
await
|
|
31847
|
-
|
|
31848
|
-
|
|
31849
|
-
|
|
31713
|
+
const masterKey = await getMasterKey(configDir);
|
|
31714
|
+
const client = await getAuthenticatedClient(configDir);
|
|
31715
|
+
const config = await loadKeys(configDir);
|
|
31716
|
+
const sessionOptions = await getSessionOptions(configDir);
|
|
31717
|
+
const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
|
|
31718
|
+
status.lastPushAt = Date.now();
|
|
31719
|
+
status.pushCount++;
|
|
31720
|
+
status.lastError = null;
|
|
31721
|
+
await writeStatus(configDir, status);
|
|
31722
|
+
if (result.filesChanged > 0) {
|
|
31723
|
+
log(configDir, `Push: ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
31724
|
+
}
|
|
31725
|
+
if (result.conflicts.length > 0) {
|
|
31726
|
+
log(configDir, `Push: ${result.conflicts.length} conflict(s)`);
|
|
31850
31727
|
}
|
|
31728
|
+
} catch (e) {
|
|
31729
|
+
status.lastError = e.message;
|
|
31730
|
+
await writeStatus(configDir, status).catch(() => {});
|
|
31731
|
+
log(configDir, `Push error: ${e.message}`);
|
|
31851
31732
|
}
|
|
31852
31733
|
}
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
|
|
31859
|
-
|
|
31860
|
-
|
|
31861
|
-
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
this.items.clear();
|
|
31867
|
-
this.path = "";
|
|
31868
|
-
this._removeWatcher = EMPTY_FN;
|
|
31869
|
-
this.items = EMPTY_SET;
|
|
31870
|
-
Object.freeze(this);
|
|
31871
|
-
}
|
|
31872
|
-
}
|
|
31873
|
-
var STAT_METHOD_F = "stat";
|
|
31874
|
-
var STAT_METHOD_L = "lstat";
|
|
31875
|
-
|
|
31876
|
-
class WatchHelper {
|
|
31877
|
-
fsw;
|
|
31878
|
-
path;
|
|
31879
|
-
watchPath;
|
|
31880
|
-
fullWatchPath;
|
|
31881
|
-
dirParts;
|
|
31882
|
-
followSymlinks;
|
|
31883
|
-
statMethod;
|
|
31884
|
-
constructor(path2, follow, fsw) {
|
|
31885
|
-
this.fsw = fsw;
|
|
31886
|
-
const watchPath = path2;
|
|
31887
|
-
this.path = path2 = path2.replace(REPLACER_RE, "");
|
|
31888
|
-
this.watchPath = watchPath;
|
|
31889
|
-
this.fullWatchPath = sp2.resolve(watchPath);
|
|
31890
|
-
this.dirParts = [];
|
|
31891
|
-
this.dirParts.forEach((parts) => {
|
|
31892
|
-
if (parts.length > 1)
|
|
31893
|
-
parts.pop();
|
|
31894
|
-
});
|
|
31895
|
-
this.followSymlinks = follow;
|
|
31896
|
-
this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
|
|
31897
|
-
}
|
|
31898
|
-
entryPath(entry) {
|
|
31899
|
-
return sp2.join(this.watchPath, sp2.relative(this.watchPath, entry.fullPath));
|
|
31900
|
-
}
|
|
31901
|
-
filterPath(entry) {
|
|
31902
|
-
const { stats } = entry;
|
|
31903
|
-
if (stats && stats.isSymbolicLink())
|
|
31904
|
-
return this.filterDir(entry);
|
|
31905
|
-
const resolvedPath = this.entryPath(entry);
|
|
31906
|
-
return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
|
|
31907
|
-
}
|
|
31908
|
-
filterDir(entry) {
|
|
31909
|
-
return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
|
|
31910
|
-
}
|
|
31911
|
-
}
|
|
31912
|
-
|
|
31913
|
-
class FSWatcher extends EventEmitter {
|
|
31914
|
-
closed;
|
|
31915
|
-
options;
|
|
31916
|
-
_closers;
|
|
31917
|
-
_ignoredPaths;
|
|
31918
|
-
_throttled;
|
|
31919
|
-
_streams;
|
|
31920
|
-
_symlinkPaths;
|
|
31921
|
-
_watched;
|
|
31922
|
-
_pendingWrites;
|
|
31923
|
-
_pendingUnlinks;
|
|
31924
|
-
_readyCount;
|
|
31925
|
-
_emitReady;
|
|
31926
|
-
_closePromise;
|
|
31927
|
-
_userIgnored;
|
|
31928
|
-
_readyEmitted;
|
|
31929
|
-
_emitRaw;
|
|
31930
|
-
_boundRemove;
|
|
31931
|
-
_nodeFsHandler;
|
|
31932
|
-
constructor(_opts = {}) {
|
|
31933
|
-
super();
|
|
31934
|
-
this.closed = false;
|
|
31935
|
-
this._closers = new Map;
|
|
31936
|
-
this._ignoredPaths = new Set;
|
|
31937
|
-
this._throttled = new Map;
|
|
31938
|
-
this._streams = new Set;
|
|
31939
|
-
this._symlinkPaths = new Map;
|
|
31940
|
-
this._watched = new Map;
|
|
31941
|
-
this._pendingWrites = new Map;
|
|
31942
|
-
this._pendingUnlinks = new Map;
|
|
31943
|
-
this._readyCount = 0;
|
|
31944
|
-
this._readyEmitted = false;
|
|
31945
|
-
const awf = _opts.awaitWriteFinish;
|
|
31946
|
-
const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 };
|
|
31947
|
-
const opts = {
|
|
31948
|
-
persistent: true,
|
|
31949
|
-
ignoreInitial: false,
|
|
31950
|
-
ignorePermissionErrors: false,
|
|
31951
|
-
interval: 100,
|
|
31952
|
-
binaryInterval: 300,
|
|
31953
|
-
followSymlinks: true,
|
|
31954
|
-
usePolling: false,
|
|
31955
|
-
atomic: true,
|
|
31956
|
-
..._opts,
|
|
31957
|
-
ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
|
|
31958
|
-
awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
|
|
31959
|
-
};
|
|
31960
|
-
if (isIBMi)
|
|
31961
|
-
opts.usePolling = true;
|
|
31962
|
-
if (opts.atomic === undefined)
|
|
31963
|
-
opts.atomic = !opts.usePolling;
|
|
31964
|
-
const envPoll = process.env.CHOKIDAR_USEPOLLING;
|
|
31965
|
-
if (envPoll !== undefined) {
|
|
31966
|
-
const envLower = envPoll.toLowerCase();
|
|
31967
|
-
if (envLower === "false" || envLower === "0")
|
|
31968
|
-
opts.usePolling = false;
|
|
31969
|
-
else if (envLower === "true" || envLower === "1")
|
|
31970
|
-
opts.usePolling = true;
|
|
31971
|
-
else
|
|
31972
|
-
opts.usePolling = !!envLower;
|
|
31973
|
-
}
|
|
31974
|
-
const envInterval = process.env.CHOKIDAR_INTERVAL;
|
|
31975
|
-
if (envInterval)
|
|
31976
|
-
opts.interval = Number.parseInt(envInterval, 10);
|
|
31977
|
-
let readyCalls = 0;
|
|
31978
|
-
this._emitReady = () => {
|
|
31979
|
-
readyCalls++;
|
|
31980
|
-
if (readyCalls >= this._readyCount) {
|
|
31981
|
-
this._emitReady = EMPTY_FN;
|
|
31982
|
-
this._readyEmitted = true;
|
|
31983
|
-
process.nextTick(() => this.emit(EVENTS.READY));
|
|
31734
|
+
async function doPull() {
|
|
31735
|
+
try {
|
|
31736
|
+
const masterKey = await getMasterKey(configDir);
|
|
31737
|
+
const client = await getAuthenticatedClient(configDir);
|
|
31738
|
+
const config = await loadKeys(configDir);
|
|
31739
|
+
const sessionOptions = await getSessionOptions(configDir);
|
|
31740
|
+
const result = await pullSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
|
|
31741
|
+
status.lastPullAt = Date.now();
|
|
31742
|
+
status.pullCount++;
|
|
31743
|
+
status.lastError = null;
|
|
31744
|
+
await writeStatus(configDir, status);
|
|
31745
|
+
if (result.filesChanged > 0) {
|
|
31746
|
+
log(configDir, `Pull: ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
31984
31747
|
}
|
|
31985
|
-
|
|
31986
|
-
|
|
31987
|
-
this._boundRemove = this._remove.bind(this);
|
|
31988
|
-
this.options = opts;
|
|
31989
|
-
this._nodeFsHandler = new NodeFsHandler(this);
|
|
31990
|
-
Object.freeze(opts);
|
|
31991
|
-
}
|
|
31992
|
-
_addIgnoredPath(matcher) {
|
|
31993
|
-
if (isMatcherObject(matcher)) {
|
|
31994
|
-
for (const ignored of this._ignoredPaths) {
|
|
31995
|
-
if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
|
|
31996
|
-
return;
|
|
31997
|
-
}
|
|
31748
|
+
if (result.conflicts.length > 0) {
|
|
31749
|
+
log(configDir, `Pull: ${result.conflicts.length} conflict(s)`);
|
|
31998
31750
|
}
|
|
31751
|
+
} catch (e) {
|
|
31752
|
+
status.lastError = e.message;
|
|
31753
|
+
await writeStatus(configDir, status).catch(() => {});
|
|
31754
|
+
log(configDir, `Pull error: ${e.message}`);
|
|
31999
31755
|
}
|
|
32000
|
-
this._ignoredPaths.add(matcher);
|
|
32001
31756
|
}
|
|
32002
|
-
|
|
32003
|
-
|
|
32004
|
-
|
|
32005
|
-
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
}
|
|
32009
|
-
}
|
|
31757
|
+
async function doHeartbeat() {
|
|
31758
|
+
try {
|
|
31759
|
+
const client = await getAuthenticatedClient(configDir);
|
|
31760
|
+
await client.heartbeat("daemon");
|
|
31761
|
+
} catch (e) {
|
|
31762
|
+
log(configDir, `Heartbeat error: ${e.message}`);
|
|
32010
31763
|
}
|
|
32011
31764
|
}
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
|
|
32015
|
-
|
|
32016
|
-
|
|
32017
|
-
|
|
32018
|
-
|
|
32019
|
-
|
|
32020
|
-
|
|
32021
|
-
|
|
31765
|
+
const watcher = startWatcher(doPush);
|
|
31766
|
+
const pullInterval = setInterval(doPull, DAEMON_INTERVAL_MS);
|
|
31767
|
+
const heartbeatInterval = setInterval(doHeartbeat, HEARTBEAT_INTERVAL_MS);
|
|
31768
|
+
await doPull();
|
|
31769
|
+
await doHeartbeat();
|
|
31770
|
+
const shutdown = async () => {
|
|
31771
|
+
log(configDir, "Daemon shutting down...");
|
|
31772
|
+
watcher.stop();
|
|
31773
|
+
clearInterval(pullInterval);
|
|
31774
|
+
clearInterval(heartbeatInterval);
|
|
31775
|
+
const { rm } = await import("fs/promises");
|
|
31776
|
+
await rm(getPidPath(configDir)).catch(() => {});
|
|
31777
|
+
await rm(getStatusPath(configDir)).catch(() => {});
|
|
31778
|
+
log(configDir, "Daemon stopped");
|
|
31779
|
+
process.exit(0);
|
|
31780
|
+
};
|
|
31781
|
+
process.on("SIGTERM", shutdown);
|
|
31782
|
+
process.on("SIGINT", shutdown);
|
|
31783
|
+
if (platform3() === "win32") {
|
|
31784
|
+
process.on("SIGBREAK", shutdown);
|
|
31785
|
+
}
|
|
31786
|
+
}
|
|
31787
|
+
|
|
31788
|
+
// src/commands/auto.ts
|
|
31789
|
+
async function getMasterKey2(configDir) {
|
|
31790
|
+
const cachedKeyPath = join12(configDir, ".cached-key");
|
|
31791
|
+
try {
|
|
31792
|
+
const cached = await readFile8(cachedKeyPath);
|
|
31793
|
+
return new Uint8Array(cached);
|
|
31794
|
+
} catch {
|
|
31795
|
+
throw new Error("No cached key. Run 'ccsini unlock' to enter your encryption password.");
|
|
31796
|
+
}
|
|
31797
|
+
}
|
|
31798
|
+
async function getAuthenticatedClient2(configDir) {
|
|
31799
|
+
const config = await loadKeys(configDir);
|
|
31800
|
+
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
31801
|
+
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
31802
|
+
return new CcsiniClient(config.apiUrl, jwt);
|
|
31803
|
+
}
|
|
31804
|
+
async function ensureDaemon(configDir) {
|
|
31805
|
+
try {
|
|
31806
|
+
const raw = await readFile8(getPidPath(configDir), "utf-8");
|
|
31807
|
+
const pid = parseInt(raw.trim(), 10);
|
|
31808
|
+
if (!isNaN(pid)) {
|
|
31809
|
+
try {
|
|
31810
|
+
process.kill(pid, 0);
|
|
31811
|
+
return;
|
|
31812
|
+
} catch {}
|
|
32022
31813
|
}
|
|
32023
|
-
|
|
32024
|
-
|
|
31814
|
+
} catch {}
|
|
31815
|
+
try {
|
|
31816
|
+
const isWin = platform4() === "win32";
|
|
31817
|
+
const child = spawn3("ccsini", ["daemon", "_run"], {
|
|
31818
|
+
detached: true,
|
|
31819
|
+
stdio: "ignore",
|
|
31820
|
+
...isWin ? { shell: true } : {}
|
|
32025
31821
|
});
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
|
|
32031
|
-
|
|
32032
|
-
|
|
32033
|
-
|
|
32034
|
-
|
|
32035
|
-
|
|
32036
|
-
|
|
32037
|
-
|
|
32038
|
-
|
|
32039
|
-
|
|
32040
|
-
|
|
32041
|
-
|
|
31822
|
+
child.unref();
|
|
31823
|
+
} catch {}
|
|
31824
|
+
}
|
|
31825
|
+
function registerAutoCommands(program2) {
|
|
31826
|
+
program2.command("auto-pull").description("Auto-pull changes (called by Claude Code hooks)").action(async () => {
|
|
31827
|
+
const configDir = getConfigDir();
|
|
31828
|
+
if (!await configExists(configDir))
|
|
31829
|
+
return;
|
|
31830
|
+
await ensureDaemon(configDir);
|
|
31831
|
+
try {
|
|
31832
|
+
const masterKey = await getMasterKey2(configDir);
|
|
31833
|
+
const client = await getAuthenticatedClient2(configDir);
|
|
31834
|
+
const config = await loadKeys(configDir);
|
|
31835
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
31836
|
+
const sessionOptions = {
|
|
31837
|
+
enabled: storedSession.enabled,
|
|
31838
|
+
maxPerProject: storedSession.maxPerProject,
|
|
31839
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
31840
|
+
};
|
|
31841
|
+
const result = await pullSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
|
|
31842
|
+
if (result.filesChanged > 0) {
|
|
31843
|
+
console.log(`[ccsini] Pulled ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
31844
|
+
}
|
|
31845
|
+
if (result.conflicts.length > 0) {
|
|
31846
|
+
console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
|
|
31847
|
+
}
|
|
31848
|
+
} catch {}
|
|
31849
|
+
});
|
|
31850
|
+
program2.command("auto-push").description("Auto-push changes (called by Claude Code hooks)").action(async () => {
|
|
31851
|
+
const configDir = getConfigDir();
|
|
31852
|
+
if (!await configExists(configDir))
|
|
31853
|
+
return;
|
|
31854
|
+
try {
|
|
31855
|
+
const masterKey = await getMasterKey2(configDir);
|
|
31856
|
+
const client = await getAuthenticatedClient2(configDir);
|
|
31857
|
+
const config = await loadKeys(configDir);
|
|
31858
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
31859
|
+
const sessionOptions = {
|
|
31860
|
+
enabled: storedSession.enabled,
|
|
31861
|
+
maxPerProject: storedSession.maxPerProject,
|
|
31862
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
31863
|
+
};
|
|
31864
|
+
const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
|
|
31865
|
+
if (result.filesChanged > 0) {
|
|
31866
|
+
let msg = `[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`;
|
|
31867
|
+
if (result.cacheStats && result.cacheStats.hits > 0) {
|
|
31868
|
+
msg += ` [cache: ${result.cacheStats.hitRate.toFixed(0)}% hits]`;
|
|
31869
|
+
}
|
|
31870
|
+
console.log(msg);
|
|
31871
|
+
}
|
|
31872
|
+
if (result.conflicts.length > 0) {
|
|
31873
|
+
console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
|
|
31874
|
+
}
|
|
31875
|
+
} catch {}
|
|
31876
|
+
});
|
|
31877
|
+
program2.command("unlock").description("Enter encryption password to enable auto-sync").action(async () => {
|
|
31878
|
+
const configDir = getConfigDir();
|
|
31879
|
+
if (!await configExists(configDir)) {
|
|
31880
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
31881
|
+
process.exit(1);
|
|
31882
|
+
}
|
|
31883
|
+
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
31884
|
+
const { password } = await inquirer2.default.prompt([
|
|
31885
|
+
{
|
|
31886
|
+
type: "password",
|
|
31887
|
+
name: "password",
|
|
31888
|
+
message: "Encryption password:",
|
|
31889
|
+
mask: "*"
|
|
31890
|
+
}
|
|
31891
|
+
]);
|
|
31892
|
+
const config = await loadKeys(configDir);
|
|
31893
|
+
const masterKey = await deriveKeyFromPassphrase(password, config.salt);
|
|
31894
|
+
await writeFile9(join12(configDir, ".cached-key"), masterKey, {
|
|
31895
|
+
mode: 384
|
|
32042
31896
|
});
|
|
32043
|
-
|
|
32044
|
-
}
|
|
32045
|
-
|
|
32046
|
-
|
|
32047
|
-
|
|
32048
|
-
|
|
32049
|
-
|
|
32050
|
-
|
|
32051
|
-
|
|
32052
|
-
|
|
32053
|
-
|
|
32054
|
-
|
|
31897
|
+
console.log("Unlocked. Auto-sync is now active.");
|
|
31898
|
+
});
|
|
31899
|
+
program2.command("heartbeat").description("Send heartbeat to keep device online (runs via scheduled task)").action(async () => {
|
|
31900
|
+
const configDir = getConfigDir();
|
|
31901
|
+
if (!await configExists(configDir))
|
|
31902
|
+
return;
|
|
31903
|
+
try {
|
|
31904
|
+
const client = await getAuthenticatedClient2(configDir);
|
|
31905
|
+
await client.heartbeat();
|
|
31906
|
+
} catch {}
|
|
31907
|
+
});
|
|
31908
|
+
program2.command("lock").description("Remove cached encryption key").action(async () => {
|
|
31909
|
+
const configDir = getConfigDir();
|
|
31910
|
+
const { rm } = await import("fs/promises");
|
|
31911
|
+
try {
|
|
31912
|
+
await rm(join12(configDir, ".cached-key"));
|
|
31913
|
+
console.log("Locked. Auto-sync paused until next unlock.");
|
|
31914
|
+
} catch {
|
|
31915
|
+
console.log("Already locked.");
|
|
31916
|
+
}
|
|
31917
|
+
});
|
|
31918
|
+
}
|
|
31919
|
+
|
|
31920
|
+
// src/commands/doctor.ts
|
|
31921
|
+
init_source();
|
|
31922
|
+
import { access as access2 } from "fs/promises";
|
|
31923
|
+
import { join as join13 } from "path";
|
|
31924
|
+
function registerDoctorCommand(program2) {
|
|
31925
|
+
program2.command("doctor").description("Diagnose ccsini setup and connectivity").action(async () => {
|
|
31926
|
+
const configDir = getConfigDir();
|
|
31927
|
+
const claudeDir = getClaudeDir();
|
|
31928
|
+
let allGood = true;
|
|
31929
|
+
console.log(source_default.bold(`
|
|
31930
|
+
ccsini doctor
|
|
31931
|
+
`));
|
|
31932
|
+
try {
|
|
31933
|
+
await access2(claudeDir);
|
|
31934
|
+
console.log(source_default.green(" \u2713 Claude Code directory found"));
|
|
31935
|
+
} catch {
|
|
31936
|
+
console.log(source_default.red(" \u2717 Claude Code directory not found"));
|
|
31937
|
+
allGood = false;
|
|
31938
|
+
}
|
|
31939
|
+
if (await configExists(configDir)) {
|
|
31940
|
+
console.log(source_default.green(" \u2713 ccsini initialized"));
|
|
31941
|
+
} else {
|
|
31942
|
+
console.log(source_default.red(" \u2717 ccsini not initialized (run 'ccsini init')"));
|
|
31943
|
+
allGood = false;
|
|
31944
|
+
return;
|
|
31945
|
+
}
|
|
31946
|
+
const config = await loadKeys(configDir);
|
|
31947
|
+
console.log(source_default.green(` \u2713 Device: ${config.deviceName} (${config.deviceId.slice(0, 8)}...)`));
|
|
31948
|
+
if (await hooksInstalled(claudeDir)) {
|
|
31949
|
+
console.log(source_default.green(" \u2713 Hooks installed"));
|
|
31950
|
+
const { healthy, issues } = await checkHooksHealth(claudeDir);
|
|
31951
|
+
if (healthy) {
|
|
31952
|
+
console.log(source_default.green(" \u2713 Hooks format valid"));
|
|
31953
|
+
} else {
|
|
31954
|
+
for (const issue of issues) {
|
|
31955
|
+
console.log(source_default.red(` \u2717 ${issue}`));
|
|
31956
|
+
}
|
|
31957
|
+
console.log(source_default.yellow(" Run 'ccsini hooks fix' to repair"));
|
|
31958
|
+
allGood = false;
|
|
32055
31959
|
}
|
|
32056
|
-
|
|
32057
|
-
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
31960
|
+
} else {
|
|
31961
|
+
console.log(source_default.yellow(" \u26A0 Hooks not installed (run 'ccsini init')"));
|
|
31962
|
+
allGood = false;
|
|
31963
|
+
}
|
|
31964
|
+
try {
|
|
31965
|
+
await access2(join13(configDir, ".cached-key"));
|
|
31966
|
+
console.log(source_default.green(" \u2713 Auto-sync unlocked"));
|
|
31967
|
+
} catch {
|
|
31968
|
+
console.log(source_default.yellow(" \u26A0 Auto-sync locked (run 'ccsini unlock')"));
|
|
31969
|
+
}
|
|
31970
|
+
try {
|
|
31971
|
+
const start = Date.now();
|
|
31972
|
+
const res = await fetch(`${config.apiUrl}/health`);
|
|
31973
|
+
const latency = Date.now() - start;
|
|
31974
|
+
if (res.ok) {
|
|
31975
|
+
console.log(source_default.green(` \u2713 Server reachable (${latency}ms)`));
|
|
31976
|
+
} else {
|
|
31977
|
+
console.log(source_default.red(" \u2717 Server returned error"));
|
|
31978
|
+
allGood = false;
|
|
32063
31979
|
}
|
|
32064
|
-
|
|
32065
|
-
|
|
32066
|
-
|
|
32067
|
-
}
|
|
32068
|
-
close() {
|
|
32069
|
-
if (this._closePromise) {
|
|
32070
|
-
return this._closePromise;
|
|
31980
|
+
} catch {
|
|
31981
|
+
console.log(source_default.red(" \u2717 Cannot reach server"));
|
|
31982
|
+
allGood = false;
|
|
32071
31983
|
}
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
|
|
32076
|
-
|
|
32077
|
-
|
|
32078
|
-
|
|
32079
|
-
|
|
32080
|
-
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
|
|
32084
|
-
|
|
32085
|
-
|
|
32086
|
-
|
|
32087
|
-
|
|
32088
|
-
this._symlinkPaths.clear();
|
|
32089
|
-
this._throttled.clear();
|
|
32090
|
-
this._closePromise = closers.length ? Promise.all(closers).then(() => {
|
|
32091
|
-
return;
|
|
32092
|
-
}) : Promise.resolve();
|
|
32093
|
-
return this._closePromise;
|
|
32094
|
-
}
|
|
32095
|
-
getWatched() {
|
|
32096
|
-
const watchList = {};
|
|
32097
|
-
this._watched.forEach((entry, dir) => {
|
|
32098
|
-
const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
|
|
32099
|
-
const index = key || ONE_DOT;
|
|
32100
|
-
watchList[index] = entry.getChildren().sort();
|
|
32101
|
-
});
|
|
32102
|
-
return watchList;
|
|
32103
|
-
}
|
|
32104
|
-
emitWithAll(event, args) {
|
|
32105
|
-
this.emit(event, ...args);
|
|
32106
|
-
if (event !== EVENTS.ERROR)
|
|
32107
|
-
this.emit(EVENTS.ALL, event, ...args);
|
|
31984
|
+
console.log(allGood ? source_default.green(`
|
|
31985
|
+
Everything looks good!
|
|
31986
|
+
`) : source_default.yellow(`
|
|
31987
|
+
Some issues found. See above.
|
|
31988
|
+
`));
|
|
31989
|
+
});
|
|
31990
|
+
}
|
|
31991
|
+
|
|
31992
|
+
// src/commands/self.ts
|
|
31993
|
+
import { execSync as execSync2 } from "child_process";
|
|
31994
|
+
function getLatestVersion() {
|
|
31995
|
+
try {
|
|
31996
|
+
const result = execSync2("npm view ccsini version", { encoding: "utf-8" }).trim();
|
|
31997
|
+
return result;
|
|
31998
|
+
} catch {
|
|
31999
|
+
return null;
|
|
32108
32000
|
}
|
|
32109
|
-
|
|
32110
|
-
|
|
32001
|
+
}
|
|
32002
|
+
function registerSelfCommands(program2) {
|
|
32003
|
+
program2.command("update").description("Update ccsini to the latest version").action(async () => {
|
|
32004
|
+
console.log(`Current version: ${VERSION}`);
|
|
32005
|
+
const latest = getLatestVersion();
|
|
32006
|
+
if (!latest) {
|
|
32007
|
+
console.error("Failed to check latest version. Check your internet connection.");
|
|
32008
|
+
process.exit(1);
|
|
32009
|
+
}
|
|
32010
|
+
if (latest === VERSION) {
|
|
32011
|
+
console.log(`Already on the latest version (${VERSION})`);
|
|
32111
32012
|
return;
|
|
32112
|
-
const opts = this.options;
|
|
32113
|
-
if (isWindows)
|
|
32114
|
-
path2 = sp2.normalize(path2);
|
|
32115
|
-
if (opts.cwd)
|
|
32116
|
-
path2 = sp2.relative(opts.cwd, path2);
|
|
32117
|
-
const args = [path2];
|
|
32118
|
-
if (stats != null)
|
|
32119
|
-
args.push(stats);
|
|
32120
|
-
const awf = opts.awaitWriteFinish;
|
|
32121
|
-
let pw;
|
|
32122
|
-
if (awf && (pw = this._pendingWrites.get(path2))) {
|
|
32123
|
-
pw.lastChange = new Date;
|
|
32124
|
-
return this;
|
|
32125
32013
|
}
|
|
32126
|
-
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32132
|
-
|
|
32133
|
-
|
|
32134
|
-
|
|
32135
|
-
|
|
32136
|
-
|
|
32014
|
+
console.log(`New version available: ${latest}`);
|
|
32015
|
+
console.log(`Updating...
|
|
32016
|
+
`);
|
|
32017
|
+
let updated = false;
|
|
32018
|
+
try {
|
|
32019
|
+
execSync2(`bun add -g ccsini@${latest}`, { stdio: "inherit" });
|
|
32020
|
+
updated = true;
|
|
32021
|
+
} catch {
|
|
32022
|
+
console.log(`Bun failed (registry cache), trying npm...
|
|
32023
|
+
`);
|
|
32024
|
+
try {
|
|
32025
|
+
execSync2(`npm install -g ccsini@${latest}`, { stdio: "inherit" });
|
|
32026
|
+
updated = true;
|
|
32027
|
+
} catch {}
|
|
32028
|
+
}
|
|
32029
|
+
if (!updated) {
|
|
32030
|
+
console.error(`
|
|
32031
|
+
Update failed. Try manually:`);
|
|
32032
|
+
console.error(` npm install -g ccsini@${latest}`);
|
|
32033
|
+
process.exit(1);
|
|
32034
|
+
}
|
|
32035
|
+
console.log(`
|
|
32036
|
+
Updated to v${latest}`);
|
|
32037
|
+
try {
|
|
32038
|
+
const { fixed } = await fixHooks(getClaudeDir());
|
|
32039
|
+
if (fixed > 0) {
|
|
32040
|
+
console.log(`Repaired ${fixed} hook issue${fixed > 1 ? "s" : ""} in Claude Code settings.`);
|
|
32137
32041
|
}
|
|
32138
|
-
|
|
32139
|
-
|
|
32140
|
-
|
|
32042
|
+
} catch {}
|
|
32043
|
+
console.log("Restart your terminal to use the new version.");
|
|
32044
|
+
});
|
|
32045
|
+
program2.command("uninstall").description("Uninstall ccsini from this machine").action(async () => {
|
|
32046
|
+
console.log(`Uninstalling ccsini...
|
|
32047
|
+
`);
|
|
32048
|
+
try {
|
|
32049
|
+
await uninstallHeartbeatScheduler();
|
|
32050
|
+
} catch {}
|
|
32051
|
+
let removed = false;
|
|
32052
|
+
try {
|
|
32053
|
+
execSync2("bun remove -g ccsini", { stdio: "inherit" });
|
|
32054
|
+
removed = true;
|
|
32055
|
+
} catch {}
|
|
32056
|
+
try {
|
|
32057
|
+
execSync2("npm uninstall -g ccsini", { stdio: "inherit" });
|
|
32058
|
+
removed = true;
|
|
32059
|
+
} catch {}
|
|
32060
|
+
if (removed) {
|
|
32061
|
+
console.log(`
|
|
32062
|
+
ccsini has been uninstalled. Bye!`);
|
|
32063
|
+
} else {
|
|
32064
|
+
console.error(`
|
|
32065
|
+
Uninstall failed. Try manually:`);
|
|
32066
|
+
console.error(" npm uninstall -g ccsini");
|
|
32067
|
+
process.exit(1);
|
|
32068
|
+
}
|
|
32069
|
+
});
|
|
32070
|
+
}
|
|
32071
|
+
|
|
32072
|
+
// src/commands/sync.ts
|
|
32073
|
+
init_auth();
|
|
32074
|
+
init_src();
|
|
32075
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
32076
|
+
import { join as join14 } from "path";
|
|
32077
|
+
async function getMasterKey3(configDir) {
|
|
32078
|
+
const cachedKeyPath = join14(configDir, ".cached-key");
|
|
32079
|
+
try {
|
|
32080
|
+
const cached = await readFile9(cachedKeyPath);
|
|
32081
|
+
return new Uint8Array(cached);
|
|
32082
|
+
} catch {
|
|
32083
|
+
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
32084
|
+
const { password } = await inquirer2.default.prompt([
|
|
32085
|
+
{
|
|
32086
|
+
type: "password",
|
|
32087
|
+
name: "password",
|
|
32088
|
+
message: "Encryption password:",
|
|
32089
|
+
mask: "*"
|
|
32141
32090
|
}
|
|
32091
|
+
]);
|
|
32092
|
+
const config = await loadKeys(configDir);
|
|
32093
|
+
const masterKey = await deriveKeyFromPassphrase(password, config.salt);
|
|
32094
|
+
const { writeFile: writeFile10 } = await import("fs/promises");
|
|
32095
|
+
await writeFile10(cachedKeyPath, masterKey, { mode: 384 });
|
|
32096
|
+
return masterKey;
|
|
32097
|
+
}
|
|
32098
|
+
}
|
|
32099
|
+
async function getAuthenticatedClient3(configDir) {
|
|
32100
|
+
const config = await loadKeys(configDir);
|
|
32101
|
+
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
32102
|
+
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
32103
|
+
return new CcsiniClient(config.apiUrl, jwt);
|
|
32104
|
+
}
|
|
32105
|
+
function formatBytes(bytes) {
|
|
32106
|
+
if (bytes < 1024)
|
|
32107
|
+
return `${bytes} B`;
|
|
32108
|
+
if (bytes < 1048576)
|
|
32109
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
32110
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
32111
|
+
}
|
|
32112
|
+
function registerSyncCommands(program2) {
|
|
32113
|
+
const syncCmd = program2.command("sync").description("Sync management commands");
|
|
32114
|
+
syncCmd.command("push").description("Push local changes to cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
32115
|
+
const configDir = getConfigDir();
|
|
32116
|
+
if (!await configExists(configDir)) {
|
|
32117
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32118
|
+
process.exit(1);
|
|
32142
32119
|
}
|
|
32143
|
-
|
|
32144
|
-
|
|
32145
|
-
|
|
32146
|
-
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
|
|
32151
|
-
|
|
32152
|
-
|
|
32153
|
-
|
|
32154
|
-
|
|
32155
|
-
|
|
32120
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32121
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32122
|
+
try {
|
|
32123
|
+
const masterKey = await getMasterKey3(configDir);
|
|
32124
|
+
const client = await getAuthenticatedClient3(configDir);
|
|
32125
|
+
const config = await loadKeys(configDir);
|
|
32126
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
32127
|
+
const sessionOptions = {
|
|
32128
|
+
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
32129
|
+
maxPerProject: storedSession.maxPerProject,
|
|
32130
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
32131
|
+
};
|
|
32132
|
+
const spinner = ora2("Scanning files...").start();
|
|
32133
|
+
const result = await pushSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
32134
|
+
spinner.text = msg;
|
|
32135
|
+
}, sessionOptions);
|
|
32136
|
+
if (result.errors.length > 0) {
|
|
32137
|
+
spinner.warn("Push completed with errors");
|
|
32138
|
+
for (const err of result.errors) {
|
|
32139
|
+
console.error(chalk2.red(` ${err}`));
|
|
32156
32140
|
}
|
|
32141
|
+
} else {
|
|
32142
|
+
spinner.succeed("Push complete");
|
|
32143
|
+
}
|
|
32144
|
+
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
32145
|
+
if (sessionOptions.enabled) {
|
|
32146
|
+
console.log(chalk2.dim(" Sessions: enabled"));
|
|
32147
|
+
}
|
|
32148
|
+
if (result.conflicts.length > 0) {
|
|
32149
|
+
console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
|
|
32150
|
+
}
|
|
32151
|
+
} catch (e) {
|
|
32152
|
+
console.error(`Push failed: ${e.message}`);
|
|
32153
|
+
process.exit(1);
|
|
32154
|
+
}
|
|
32155
|
+
});
|
|
32156
|
+
syncCmd.command("pull").description("Pull changes from cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
32157
|
+
const configDir = getConfigDir();
|
|
32158
|
+
if (!await configExists(configDir)) {
|
|
32159
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32160
|
+
process.exit(1);
|
|
32161
|
+
}
|
|
32162
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32163
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32164
|
+
try {
|
|
32165
|
+
const masterKey = await getMasterKey3(configDir);
|
|
32166
|
+
const client = await getAuthenticatedClient3(configDir);
|
|
32167
|
+
const config = await loadKeys(configDir);
|
|
32168
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
32169
|
+
const sessionOptions = {
|
|
32170
|
+
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
32171
|
+
maxPerProject: storedSession.maxPerProject,
|
|
32172
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
32157
32173
|
};
|
|
32158
|
-
|
|
32159
|
-
|
|
32174
|
+
const spinner = ora2("Pulling from cloud...").start();
|
|
32175
|
+
const result = await pullSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
32176
|
+
spinner.text = msg;
|
|
32177
|
+
}, sessionOptions);
|
|
32178
|
+
if (result.errors.length > 0) {
|
|
32179
|
+
spinner.warn("Pull completed with errors");
|
|
32180
|
+
for (const err of result.errors) {
|
|
32181
|
+
console.error(chalk2.red(` ${err}`));
|
|
32182
|
+
}
|
|
32183
|
+
} else {
|
|
32184
|
+
spinner.succeed("Pull complete");
|
|
32185
|
+
}
|
|
32186
|
+
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
32187
|
+
if (sessionOptions.enabled) {
|
|
32188
|
+
console.log(chalk2.dim(" Sessions: enabled"));
|
|
32189
|
+
}
|
|
32190
|
+
if (result.conflicts.length > 0) {
|
|
32191
|
+
console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
|
|
32192
|
+
}
|
|
32193
|
+
} catch (e) {
|
|
32194
|
+
console.error(`Pull failed: ${e.message}`);
|
|
32195
|
+
process.exit(1);
|
|
32160
32196
|
}
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32197
|
+
});
|
|
32198
|
+
syncCmd.command("cleanup").description("Remove orphaned blobs from cloud storage").option("--dry-run", "Show what would be deleted without deleting").action(async (opts) => {
|
|
32199
|
+
const configDir = getConfigDir();
|
|
32200
|
+
if (!await configExists(configDir)) {
|
|
32201
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32202
|
+
process.exit(1);
|
|
32165
32203
|
}
|
|
32166
|
-
|
|
32167
|
-
|
|
32168
|
-
|
|
32204
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32205
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32206
|
+
try {
|
|
32207
|
+
const masterKey = await getMasterKey3(configDir);
|
|
32208
|
+
const client = await getAuthenticatedClient3(configDir);
|
|
32209
|
+
const spinner = ora2("Fetching remote manifest...").start();
|
|
32210
|
+
const remoteManifestEnc = await client.getManifest();
|
|
32211
|
+
if (!remoteManifestEnc) {
|
|
32212
|
+
spinner.fail("No remote manifest found. Nothing to clean up.");
|
|
32213
|
+
return;
|
|
32214
|
+
}
|
|
32215
|
+
let remoteManifest;
|
|
32169
32216
|
try {
|
|
32170
|
-
|
|
32171
|
-
|
|
32172
|
-
|
|
32217
|
+
const decrypted = decryptFile(masterKey, "__manifest__", remoteManifestEnc);
|
|
32218
|
+
remoteManifest = JSON.parse(new TextDecoder().decode(decrypted));
|
|
32219
|
+
} catch {
|
|
32220
|
+
spinner.fail("Failed to decrypt remote manifest");
|
|
32221
|
+
process.exit(1);
|
|
32222
|
+
}
|
|
32223
|
+
const referencedHashes = new Set;
|
|
32224
|
+
for (const entry of Object.values(remoteManifest.files)) {
|
|
32225
|
+
referencedHashes.add(entry.hash);
|
|
32226
|
+
}
|
|
32227
|
+
spinner.text = "Listing remote blobs...";
|
|
32228
|
+
const { hashes: serverHashes, totalSize } = await client.listBlobs();
|
|
32229
|
+
const orphans = serverHashes.filter((h) => !referencedHashes.has(h));
|
|
32230
|
+
if (orphans.length === 0) {
|
|
32231
|
+
spinner.succeed("No orphaned blobs found");
|
|
32232
|
+
console.log(chalk2.dim(` ${serverHashes.length} blobs on server, all referenced by manifest`));
|
|
32173
32233
|
return;
|
|
32174
|
-
|
|
32175
|
-
|
|
32176
|
-
|
|
32177
|
-
|
|
32178
|
-
|
|
32179
|
-
|
|
32180
|
-
|
|
32181
|
-
if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
|
|
32182
|
-
this.emit(EVENTS.ERROR, error);
|
|
32183
|
-
}
|
|
32184
|
-
return error || this.closed;
|
|
32185
|
-
}
|
|
32186
|
-
_throttle(actionType, path2, timeout) {
|
|
32187
|
-
if (!this._throttled.has(actionType)) {
|
|
32188
|
-
this._throttled.set(actionType, new Map);
|
|
32189
|
-
}
|
|
32190
|
-
const action = this._throttled.get(actionType);
|
|
32191
|
-
if (!action)
|
|
32192
|
-
throw new Error("invalid throttle");
|
|
32193
|
-
const actionPath = action.get(path2);
|
|
32194
|
-
if (actionPath) {
|
|
32195
|
-
actionPath.count++;
|
|
32196
|
-
return false;
|
|
32197
|
-
}
|
|
32198
|
-
let timeoutObject;
|
|
32199
|
-
const clear = () => {
|
|
32200
|
-
const item = action.get(path2);
|
|
32201
|
-
const count = item ? item.count : 0;
|
|
32202
|
-
action.delete(path2);
|
|
32203
|
-
clearTimeout(timeoutObject);
|
|
32204
|
-
if (item)
|
|
32205
|
-
clearTimeout(item.timeoutObject);
|
|
32206
|
-
return count;
|
|
32207
|
-
};
|
|
32208
|
-
timeoutObject = setTimeout(clear, timeout);
|
|
32209
|
-
const thr = { timeoutObject, clear, count: 0 };
|
|
32210
|
-
action.set(path2, thr);
|
|
32211
|
-
return thr;
|
|
32212
|
-
}
|
|
32213
|
-
_incrReadyCount() {
|
|
32214
|
-
return this._readyCount++;
|
|
32215
|
-
}
|
|
32216
|
-
_awaitWriteFinish(path2, threshold, event, awfEmit) {
|
|
32217
|
-
const awf = this.options.awaitWriteFinish;
|
|
32218
|
-
if (typeof awf !== "object")
|
|
32219
|
-
return;
|
|
32220
|
-
const pollInterval = awf.pollInterval;
|
|
32221
|
-
let timeoutHandler;
|
|
32222
|
-
let fullPath = path2;
|
|
32223
|
-
if (this.options.cwd && !sp2.isAbsolute(path2)) {
|
|
32224
|
-
fullPath = sp2.join(this.options.cwd, path2);
|
|
32225
|
-
}
|
|
32226
|
-
const now = new Date;
|
|
32227
|
-
const writes = this._pendingWrites;
|
|
32228
|
-
function awaitWriteFinishFn(prevStat) {
|
|
32229
|
-
statcb(fullPath, (err, curStat) => {
|
|
32230
|
-
if (err || !writes.has(path2)) {
|
|
32231
|
-
if (err && err.code !== "ENOENT")
|
|
32232
|
-
awfEmit(err);
|
|
32233
|
-
return;
|
|
32234
|
+
}
|
|
32235
|
+
spinner.stop();
|
|
32236
|
+
console.log(`Found ${chalk2.yellow(orphans.length)} orphaned blobs out of ${serverHashes.length} total`);
|
|
32237
|
+
if (opts.dryRun) {
|
|
32238
|
+
console.log(chalk2.dim(" Dry run \u2014 no blobs deleted"));
|
|
32239
|
+
for (const hash of orphans.slice(0, 10)) {
|
|
32240
|
+
console.log(chalk2.dim(` ${hash}`));
|
|
32234
32241
|
}
|
|
32235
|
-
|
|
32236
|
-
|
|
32237
|
-
writes.get(path2).lastChange = now2;
|
|
32242
|
+
if (orphans.length > 10) {
|
|
32243
|
+
console.log(chalk2.dim(` ... and ${orphans.length - 10} more`));
|
|
32238
32244
|
}
|
|
32239
|
-
|
|
32240
|
-
|
|
32241
|
-
|
|
32242
|
-
|
|
32243
|
-
|
|
32244
|
-
|
|
32245
|
-
|
|
32245
|
+
return;
|
|
32246
|
+
}
|
|
32247
|
+
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
32248
|
+
const { confirm } = await inquirer2.default.prompt([
|
|
32249
|
+
{
|
|
32250
|
+
type: "confirm",
|
|
32251
|
+
name: "confirm",
|
|
32252
|
+
message: `Delete ${orphans.length} orphaned blobs?`,
|
|
32253
|
+
default: false
|
|
32246
32254
|
}
|
|
32255
|
+
]);
|
|
32256
|
+
if (!confirm) {
|
|
32257
|
+
console.log("Cancelled.");
|
|
32258
|
+
return;
|
|
32259
|
+
}
|
|
32260
|
+
const deleteSpinner = ora2("Deleting orphaned blobs...").start();
|
|
32261
|
+
const batchSize = 500;
|
|
32262
|
+
let deleted = 0;
|
|
32263
|
+
for (let i = 0;i < orphans.length; i += batchSize) {
|
|
32264
|
+
const batch = orphans.slice(i, i + batchSize);
|
|
32265
|
+
const result = await client.deleteBlobs(batch);
|
|
32266
|
+
deleted += result.deleted;
|
|
32267
|
+
deleteSpinner.text = `Deleting orphaned blobs... ${deleted}/${orphans.length}`;
|
|
32268
|
+
}
|
|
32269
|
+
deleteSpinner.succeed(`Deleted ${deleted} orphaned blobs`);
|
|
32270
|
+
} catch (e) {
|
|
32271
|
+
console.error(`Cleanup failed: ${e.message}`);
|
|
32272
|
+
process.exit(1);
|
|
32273
|
+
}
|
|
32274
|
+
});
|
|
32275
|
+
syncCmd.command("status").description("Show sync status").action(async () => {
|
|
32276
|
+
const configDir = getConfigDir();
|
|
32277
|
+
if (!await configExists(configDir)) {
|
|
32278
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32279
|
+
process.exit(1);
|
|
32280
|
+
}
|
|
32281
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32282
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32283
|
+
try {
|
|
32284
|
+
const masterKey = await getMasterKey3(configDir);
|
|
32285
|
+
const client = await getAuthenticatedClient3(configDir);
|
|
32286
|
+
const config = await loadKeys(configDir);
|
|
32287
|
+
const spinner = ora2("Checking status...").start();
|
|
32288
|
+
const localManifest = await loadManifest(configDir);
|
|
32289
|
+
const claudeDir = getClaudeDir();
|
|
32290
|
+
const { manifest: currentManifest } = await generateManifest(claudeDir, config.deviceName, (msg) => {
|
|
32291
|
+
spinner.text = msg;
|
|
32247
32292
|
});
|
|
32293
|
+
let remoteManifest = null;
|
|
32294
|
+
const remoteManifestEnc = await client.getManifest();
|
|
32295
|
+
if (remoteManifestEnc) {
|
|
32296
|
+
try {
|
|
32297
|
+
const decrypted = decryptFile(masterKey, "__manifest__", remoteManifestEnc);
|
|
32298
|
+
remoteManifest = JSON.parse(new TextDecoder().decode(decrypted));
|
|
32299
|
+
} catch {
|
|
32300
|
+
spinner.fail("Failed to decrypt remote manifest");
|
|
32301
|
+
console.error("Wrong encryption password?");
|
|
32302
|
+
process.exit(1);
|
|
32303
|
+
}
|
|
32304
|
+
}
|
|
32305
|
+
spinner.stop();
|
|
32306
|
+
const diffs = diffManifests(currentManifest, remoteManifest);
|
|
32307
|
+
const pendingPush = diffs.filter((d) => d.action === "push" || d.action === "merge").length;
|
|
32308
|
+
const pendingPull = diffs.filter((d) => d.action === "pull" || d.action === "merge").length;
|
|
32309
|
+
console.log(chalk2.bold("Sync Status"));
|
|
32310
|
+
console.log(` Device: ${config.deviceName}`);
|
|
32311
|
+
console.log(` Pending push: ${pendingPush === 0 ? chalk2.green("0") : chalk2.yellow(pendingPush)} files`);
|
|
32312
|
+
console.log(` Pending pull: ${pendingPull === 0 ? chalk2.green("0") : chalk2.yellow(pendingPull)} files`);
|
|
32313
|
+
if (localManifest) {
|
|
32314
|
+
const lastSync = new Date(localManifest.timestamp);
|
|
32315
|
+
console.log(` Last sync: ${lastSync.toLocaleString()}`);
|
|
32316
|
+
console.log(` Last device: ${localManifest.device}`);
|
|
32317
|
+
} else {
|
|
32318
|
+
console.log(` Last sync: ${chalk2.dim("never")}`);
|
|
32319
|
+
}
|
|
32320
|
+
if (!remoteManifestEnc) {
|
|
32321
|
+
console.log(chalk2.dim(`
|
|
32322
|
+
No remote data yet. Run 'ccsini sync push' first.`));
|
|
32323
|
+
}
|
|
32324
|
+
} catch (e) {
|
|
32325
|
+
console.error(`Status check failed: ${e.message}`);
|
|
32326
|
+
process.exit(1);
|
|
32248
32327
|
}
|
|
32249
|
-
|
|
32250
|
-
|
|
32251
|
-
|
|
32252
|
-
|
|
32253
|
-
|
|
32254
|
-
|
|
32255
|
-
|
|
32256
|
-
|
|
32257
|
-
|
|
32258
|
-
|
|
32328
|
+
});
|
|
32329
|
+
}
|
|
32330
|
+
|
|
32331
|
+
// src/commands/hooks.ts
|
|
32332
|
+
init_source();
|
|
32333
|
+
function registerHooksCommands(program2) {
|
|
32334
|
+
const hooksCmd = program2.command("hooks").description("Manage Claude Code hooks");
|
|
32335
|
+
hooksCmd.command("fix").description("Repair broken hook entries in Claude Code settings").action(async () => {
|
|
32336
|
+
const claudeDir = getClaudeDir();
|
|
32337
|
+
console.log(source_default.bold(`
|
|
32338
|
+
Checking hooks...
|
|
32339
|
+
`));
|
|
32340
|
+
const { fixed, details } = await fixHooks(claudeDir);
|
|
32341
|
+
if (fixed === 0) {
|
|
32342
|
+
console.log(source_default.green(` Hooks are already healthy. Nothing to fix.
|
|
32343
|
+
`));
|
|
32344
|
+
return;
|
|
32259
32345
|
}
|
|
32260
|
-
|
|
32261
|
-
|
|
32262
|
-
if (this.options.atomic && DOT_RE.test(path2))
|
|
32263
|
-
return true;
|
|
32264
|
-
if (!this._userIgnored) {
|
|
32265
|
-
const { cwd } = this.options;
|
|
32266
|
-
const ign = this.options.ignored;
|
|
32267
|
-
const ignored = (ign || []).map(normalizeIgnored(cwd));
|
|
32268
|
-
const ignoredPaths = [...this._ignoredPaths];
|
|
32269
|
-
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
32270
|
-
this._userIgnored = anymatch(list, undefined);
|
|
32346
|
+
for (const detail of details) {
|
|
32347
|
+
console.log(source_default.yellow(` Fixed: ${detail}`));
|
|
32271
32348
|
}
|
|
32272
|
-
|
|
32273
|
-
}
|
|
32274
|
-
|
|
32275
|
-
|
|
32276
|
-
|
|
32277
|
-
|
|
32278
|
-
|
|
32279
|
-
|
|
32280
|
-
|
|
32281
|
-
|
|
32282
|
-
|
|
32283
|
-
|
|
32284
|
-
|
|
32285
|
-
|
|
32286
|
-
|
|
32287
|
-
|
|
32288
|
-
|
|
32289
|
-
|
|
32290
|
-
|
|
32291
|
-
|
|
32292
|
-
|
|
32293
|
-
|
|
32294
|
-
|
|
32295
|
-
|
|
32349
|
+
console.log(source_default.green(`
|
|
32350
|
+
Repaired ${fixed} hook issue${fixed > 1 ? "s" : ""}.
|
|
32351
|
+
`));
|
|
32352
|
+
});
|
|
32353
|
+
}
|
|
32354
|
+
|
|
32355
|
+
// src/commands/reset.ts
|
|
32356
|
+
init_auth();
|
|
32357
|
+
import { rm } from "fs/promises";
|
|
32358
|
+
function registerResetCommand(program2) {
|
|
32359
|
+
program2.command("reset").description("Wipe all server data and local config (full account reset)").action(async () => {
|
|
32360
|
+
const configDir = getConfigDir();
|
|
32361
|
+
if (!await configExists(configDir)) {
|
|
32362
|
+
console.error("Not initialized. Nothing to reset.");
|
|
32363
|
+
process.exit(1);
|
|
32364
|
+
}
|
|
32365
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32366
|
+
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
32367
|
+
console.log(chalk2.red.bold(`
|
|
32368
|
+
WARNING: This will permanently delete:`));
|
|
32369
|
+
console.log(chalk2.red(" - All encrypted files on the server"));
|
|
32370
|
+
console.log(chalk2.red(" - Your encryption salt"));
|
|
32371
|
+
console.log(chalk2.red(` - Your local config (~/.ccsini/)
|
|
32372
|
+
`));
|
|
32373
|
+
const { confirm } = await inquirer2.default.prompt([
|
|
32374
|
+
{
|
|
32375
|
+
type: "confirm",
|
|
32376
|
+
name: "confirm",
|
|
32377
|
+
message: "Are you sure you want to wipe everything?",
|
|
32378
|
+
default: false
|
|
32379
|
+
}
|
|
32380
|
+
]);
|
|
32381
|
+
if (!confirm) {
|
|
32382
|
+
console.log("Cancelled.");
|
|
32296
32383
|
return;
|
|
32297
|
-
if (!isDirectory && this._watched.size === 1) {
|
|
32298
|
-
this.add(directory, item, true);
|
|
32299
32384
|
}
|
|
32300
|
-
const
|
|
32301
|
-
|
|
32302
|
-
|
|
32303
|
-
|
|
32304
|
-
|
|
32305
|
-
|
|
32306
|
-
|
|
32307
|
-
|
|
32385
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32386
|
+
try {
|
|
32387
|
+
const config = await loadKeys(configDir);
|
|
32388
|
+
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
32389
|
+
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
32390
|
+
const client = new CcsiniClient(config.apiUrl, jwt);
|
|
32391
|
+
const spinner = ora2("Wiping server data...").start();
|
|
32392
|
+
try {
|
|
32393
|
+
const { blobsDeleted } = await client.resetAll();
|
|
32394
|
+
spinner.succeed(`Server data wiped (${blobsDeleted} blob${blobsDeleted !== 1 ? "s" : ""} deleted)`);
|
|
32395
|
+
} catch {
|
|
32396
|
+
spinner.warn("Could not wipe server data (device may have been removed from dashboard)");
|
|
32397
|
+
}
|
|
32398
|
+
const localSpinner = ora2("Removing local config...").start();
|
|
32399
|
+
await rm(configDir, { recursive: true, force: true });
|
|
32400
|
+
localSpinner.succeed("Local config removed");
|
|
32401
|
+
console.log(chalk2.green(`
|
|
32402
|
+
Account fully reset. Run 'ccsini init --token <token>' to start fresh.`));
|
|
32403
|
+
} catch (e) {
|
|
32404
|
+
console.error(`Reset failed: ${e.message}`);
|
|
32405
|
+
process.exit(1);
|
|
32308
32406
|
}
|
|
32309
|
-
|
|
32310
|
-
|
|
32311
|
-
|
|
32312
|
-
|
|
32313
|
-
|
|
32314
|
-
|
|
32315
|
-
|
|
32407
|
+
});
|
|
32408
|
+
}
|
|
32409
|
+
|
|
32410
|
+
// src/commands/sessions.ts
|
|
32411
|
+
init_src();
|
|
32412
|
+
function formatBytes2(bytes) {
|
|
32413
|
+
if (bytes < 1024)
|
|
32414
|
+
return `${bytes} B`;
|
|
32415
|
+
if (bytes < 1024 * 1024)
|
|
32416
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
32417
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
32418
|
+
}
|
|
32419
|
+
function registerSessionsCommand(program2) {
|
|
32420
|
+
const sessionsCmd = program2.command("sessions").description("Configure session sync (continue conversations across devices)");
|
|
32421
|
+
sessionsCmd.command("enable").description("Enable session sync").option("-n, --per-project <count>", "Max sessions per project", String(SESSION_SYNC_DEFAULTS.maxPerProject)).action(async (opts) => {
|
|
32422
|
+
const configDir = getConfigDir();
|
|
32423
|
+
if (!await configExists(configDir)) {
|
|
32424
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32425
|
+
process.exit(1);
|
|
32316
32426
|
}
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
this._emit(eventName, path2);
|
|
32322
|
-
this._closePath(path2);
|
|
32323
|
-
}
|
|
32324
|
-
_closePath(path2) {
|
|
32325
|
-
this._closeFile(path2);
|
|
32326
|
-
const dir = sp2.dirname(path2);
|
|
32327
|
-
this._getWatchedDir(dir).remove(sp2.basename(path2));
|
|
32328
|
-
}
|
|
32329
|
-
_closeFile(path2) {
|
|
32330
|
-
const closers = this._closers.get(path2);
|
|
32331
|
-
if (!closers)
|
|
32332
|
-
return;
|
|
32333
|
-
closers.forEach((closer) => closer());
|
|
32334
|
-
this._closers.delete(path2);
|
|
32335
|
-
}
|
|
32336
|
-
_addPathCloser(path2, closer) {
|
|
32337
|
-
if (!closer)
|
|
32338
|
-
return;
|
|
32339
|
-
let list = this._closers.get(path2);
|
|
32340
|
-
if (!list) {
|
|
32341
|
-
list = [];
|
|
32342
|
-
this._closers.set(path2, list);
|
|
32427
|
+
const maxPerProject = parseInt(opts.perProject, 10);
|
|
32428
|
+
if (isNaN(maxPerProject) || maxPerProject < 1) {
|
|
32429
|
+
console.error("--per-project must be a positive integer");
|
|
32430
|
+
process.exit(1);
|
|
32343
32431
|
}
|
|
32344
|
-
|
|
32345
|
-
|
|
32346
|
-
|
|
32347
|
-
if (this.closed)
|
|
32348
|
-
return;
|
|
32349
|
-
const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
32350
|
-
let stream = readdirp(root, options);
|
|
32351
|
-
this._streams.add(stream);
|
|
32352
|
-
stream.once(STR_CLOSE, () => {
|
|
32353
|
-
stream = undefined;
|
|
32432
|
+
await saveSessionConfig(configDir, {
|
|
32433
|
+
enabled: true,
|
|
32434
|
+
maxPerProject
|
|
32354
32435
|
});
|
|
32355
|
-
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32436
|
+
console.log(`Session sync enabled (${maxPerProject} sessions per project)`);
|
|
32437
|
+
});
|
|
32438
|
+
sessionsCmd.command("disable").description("Disable session sync").action(async () => {
|
|
32439
|
+
const configDir = getConfigDir();
|
|
32440
|
+
if (!await configExists(configDir)) {
|
|
32441
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32442
|
+
process.exit(1);
|
|
32443
|
+
}
|
|
32444
|
+
const current = await loadSessionConfig(configDir);
|
|
32445
|
+
await saveSessionConfig(configDir, {
|
|
32446
|
+
enabled: false,
|
|
32447
|
+
maxPerProject: current.maxPerProject
|
|
32448
|
+
});
|
|
32449
|
+
console.log("Session sync disabled");
|
|
32450
|
+
});
|
|
32451
|
+
sessionsCmd.command("status").description("Show session sync status and preview").action(async () => {
|
|
32452
|
+
const configDir = getConfigDir();
|
|
32453
|
+
if (!await configExists(configDir)) {
|
|
32454
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32455
|
+
process.exit(1);
|
|
32456
|
+
}
|
|
32457
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32458
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
32459
|
+
const sessionConfig = await loadSessionConfig(configDir);
|
|
32460
|
+
console.log(chalk2.bold("Session Sync"));
|
|
32461
|
+
console.log(` Status: ${sessionConfig.enabled ? chalk2.green("enabled") : chalk2.yellow("disabled")}`);
|
|
32462
|
+
console.log(` Per project: ${sessionConfig.maxPerProject}`);
|
|
32463
|
+
console.log(` Max file size: ${formatBytes2(SESSION_SYNC_DEFAULTS.maxFileSize)}`);
|
|
32464
|
+
const spinner = ora2("Scanning session files...").start();
|
|
32465
|
+
try {
|
|
32466
|
+
const claudeDir = getClaudeDir();
|
|
32467
|
+
const enabledOpts = {
|
|
32468
|
+
enabled: true,
|
|
32469
|
+
maxPerProject: sessionConfig.maxPerProject,
|
|
32470
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
32471
|
+
};
|
|
32472
|
+
const withSessionsResult = await scanClaudeDir(claudeDir, undefined, enabledOpts);
|
|
32473
|
+
const sessionFiles = withSessionsResult.files.filter((f) => isSessionFile(f.relativePath));
|
|
32474
|
+
const disabledOpts = {
|
|
32475
|
+
enabled: false,
|
|
32476
|
+
maxPerProject: sessionConfig.maxPerProject,
|
|
32477
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
32478
|
+
};
|
|
32479
|
+
const withoutSessionsResult = await scanClaudeDir(claudeDir, undefined, disabledOpts);
|
|
32480
|
+
const withoutSessions = withoutSessionsResult.files;
|
|
32481
|
+
spinner.stop();
|
|
32482
|
+
const totalSessionSize = sessionFiles.reduce((sum, f) => sum + f.size, 0);
|
|
32483
|
+
const projects = new Set(sessionFiles.map((f) => f.relativePath.split("/").slice(0, 2).join("/")));
|
|
32484
|
+
console.log(chalk2.bold(`
|
|
32485
|
+
Preview`));
|
|
32486
|
+
console.log(` Projects: ${projects.size}`);
|
|
32487
|
+
console.log(` Sessions: ${sessionFiles.length} files`);
|
|
32488
|
+
console.log(` Session size: ${formatBytes2(totalSessionSize)}`);
|
|
32489
|
+
console.log(` Other files: ${withoutSessions.length}`);
|
|
32490
|
+
if (!sessionConfig.enabled) {
|
|
32491
|
+
console.log(chalk2.dim(`
|
|
32492
|
+
Run 'ccsini sessions enable' to start syncing sessions`));
|
|
32359
32493
|
}
|
|
32360
|
-
})
|
|
32361
|
-
|
|
32362
|
-
}
|
|
32363
|
-
}
|
|
32364
|
-
function watch(paths, options = {}) {
|
|
32365
|
-
const watcher = new FSWatcher(options);
|
|
32366
|
-
watcher.add(paths);
|
|
32367
|
-
return watcher;
|
|
32368
|
-
}
|
|
32369
|
-
|
|
32370
|
-
// src/daemon/watcher.ts
|
|
32371
|
-
init_src();
|
|
32372
|
-
function startWatcher(onSync) {
|
|
32373
|
-
const claudeDir = getClaudeDir();
|
|
32374
|
-
let debounceTimer = null;
|
|
32375
|
-
let syncing = false;
|
|
32376
|
-
const watcher = watch(claudeDir, {
|
|
32377
|
-
ignored: NEVER_SYNC_PATTERNS.map((p) => `**/${p}`),
|
|
32378
|
-
persistent: true,
|
|
32379
|
-
ignoreInitial: true,
|
|
32380
|
-
awaitWriteFinish: {
|
|
32381
|
-
stabilityThreshold: 2000,
|
|
32382
|
-
pollInterval: 500
|
|
32494
|
+
} catch (e) {
|
|
32495
|
+
spinner.fail(`Scan failed: ${e.message}`);
|
|
32383
32496
|
}
|
|
32384
32497
|
});
|
|
32385
|
-
const triggerSync = () => {
|
|
32386
|
-
if (debounceTimer)
|
|
32387
|
-
clearTimeout(debounceTimer);
|
|
32388
|
-
debounceTimer = setTimeout(async () => {
|
|
32389
|
-
if (syncing)
|
|
32390
|
-
return;
|
|
32391
|
-
syncing = true;
|
|
32392
|
-
try {
|
|
32393
|
-
await onSync();
|
|
32394
|
-
} catch {} finally {
|
|
32395
|
-
syncing = false;
|
|
32396
|
-
}
|
|
32397
|
-
}, DAEMON_DEBOUNCE_MS);
|
|
32398
|
-
};
|
|
32399
|
-
watcher.on("change", triggerSync);
|
|
32400
|
-
watcher.on("add", triggerSync);
|
|
32401
|
-
watcher.on("unlink", triggerSync);
|
|
32402
|
-
return {
|
|
32403
|
-
stop: () => {
|
|
32404
|
-
if (debounceTimer)
|
|
32405
|
-
clearTimeout(debounceTimer);
|
|
32406
|
-
watcher.close();
|
|
32407
|
-
}
|
|
32408
|
-
};
|
|
32409
32498
|
}
|
|
32410
32499
|
|
|
32411
|
-
// src/
|
|
32412
|
-
|
|
32413
|
-
|
|
32414
|
-
|
|
32415
|
-
|
|
32416
|
-
|
|
32417
|
-
}
|
|
32418
|
-
|
|
32419
|
-
|
|
32420
|
-
|
|
32421
|
-
|
|
32422
|
-
|
|
32423
|
-
|
|
32424
|
-
|
|
32425
|
-
const ts = new Date().toISOString();
|
|
32426
|
-
const line = `[${ts}] ${message2}
|
|
32427
|
-
`;
|
|
32428
|
-
try {
|
|
32429
|
-
appendFileSync(getLogPath(configDir), line);
|
|
32430
|
-
} catch {}
|
|
32431
|
-
}
|
|
32432
|
-
async function getMasterKey3(configDir) {
|
|
32433
|
-
const cachedKeyPath = join15(configDir, ".cached-key");
|
|
32434
|
-
const cached = await readFile10(cachedKeyPath);
|
|
32435
|
-
return new Uint8Array(cached);
|
|
32436
|
-
}
|
|
32437
|
-
async function getAuthenticatedClient3(configDir) {
|
|
32438
|
-
const config = await loadKeys(configDir);
|
|
32439
|
-
const privateKey = await importPrivateKey(config.devicePrivateKey);
|
|
32440
|
-
const jwt = await createDeviceJWT(privateKey, config.deviceId);
|
|
32441
|
-
return new CcsiniClient(config.apiUrl, jwt);
|
|
32442
|
-
}
|
|
32443
|
-
async function getSessionOptions(configDir) {
|
|
32444
|
-
const storedSession = await loadSessionConfig(configDir);
|
|
32445
|
-
return {
|
|
32446
|
-
enabled: storedSession.enabled,
|
|
32447
|
-
maxPerProject: storedSession.maxPerProject,
|
|
32448
|
-
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
32449
|
-
};
|
|
32450
|
-
}
|
|
32451
|
-
async function writeStatus(configDir, status) {
|
|
32452
|
-
await writeFile10(getStatusPath(configDir), JSON.stringify(status, null, 2));
|
|
32453
|
-
}
|
|
32454
|
-
async function runDaemon() {
|
|
32455
|
-
const configDir = getConfigDir();
|
|
32456
|
-
if (!await configExists(configDir)) {
|
|
32457
|
-
console.error("Not initialized. Run 'ccsini init' first.");
|
|
32458
|
-
process.exit(1);
|
|
32459
|
-
}
|
|
32460
|
-
try {
|
|
32461
|
-
await getMasterKey3(configDir);
|
|
32462
|
-
} catch {
|
|
32463
|
-
console.error("No cached key. Run 'ccsini unlock' first.");
|
|
32464
|
-
process.exit(1);
|
|
32500
|
+
// src/commands/conflicts.ts
|
|
32501
|
+
import { readdir as readdir4, readFile as readFile10, unlink as unlink2, writeFile as writeFile10 } from "fs/promises";
|
|
32502
|
+
import { join as join15, relative as relative4 } from "path";
|
|
32503
|
+
async function findConflictFiles(dir, base) {
|
|
32504
|
+
base = base ?? dir;
|
|
32505
|
+
const results = [];
|
|
32506
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
32507
|
+
for (const entry of entries) {
|
|
32508
|
+
const full = join15(dir, entry.name);
|
|
32509
|
+
if (entry.isDirectory()) {
|
|
32510
|
+
results.push(...await findConflictFiles(full, base));
|
|
32511
|
+
} else if (entry.name.endsWith(".conflict")) {
|
|
32512
|
+
results.push(relative4(base, full).split("\\").join("/"));
|
|
32513
|
+
}
|
|
32465
32514
|
}
|
|
32466
|
-
|
|
32467
|
-
|
|
32468
|
-
|
|
32469
|
-
|
|
32470
|
-
|
|
32471
|
-
|
|
32472
|
-
|
|
32473
|
-
|
|
32474
|
-
|
|
32475
|
-
|
|
32476
|
-
|
|
32477
|
-
|
|
32478
|
-
|
|
32479
|
-
|
|
32480
|
-
|
|
32481
|
-
const
|
|
32482
|
-
|
|
32483
|
-
|
|
32484
|
-
|
|
32485
|
-
|
|
32486
|
-
|
|
32487
|
-
|
|
32488
|
-
|
|
32489
|
-
await writeStatus(configDir, status);
|
|
32490
|
-
if (result.filesChanged > 0) {
|
|
32491
|
-
log(configDir, `Push: ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
32492
|
-
}
|
|
32493
|
-
if (result.conflicts.length > 0) {
|
|
32494
|
-
log(configDir, `Push: ${result.conflicts.length} conflict(s)`);
|
|
32515
|
+
return results;
|
|
32516
|
+
}
|
|
32517
|
+
function registerConflictsCommand(program2) {
|
|
32518
|
+
program2.command("conflicts").description("List and resolve sync conflicts").option("--keep <side>", "Resolve all: 'local' keeps .conflict, 'remote' discards .conflict").action(async (opts) => {
|
|
32519
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
32520
|
+
const claudeDir = getClaudeDir();
|
|
32521
|
+
const conflictFiles = await findConflictFiles(claudeDir);
|
|
32522
|
+
if (conflictFiles.length === 0) {
|
|
32523
|
+
console.log(chalk2.green("No conflicts found."));
|
|
32524
|
+
return;
|
|
32525
|
+
}
|
|
32526
|
+
console.log(chalk2.bold(`
|
|
32527
|
+
${conflictFiles.length} conflict(s) found:
|
|
32528
|
+
`));
|
|
32529
|
+
for (const file of conflictFiles) {
|
|
32530
|
+
const original = file.replace(/\.conflict$/, "");
|
|
32531
|
+
console.log(` ${chalk2.yellow("\u26A0")} ${original}`);
|
|
32532
|
+
console.log(chalk2.dim(` conflict: ~/.claude/${file}`));
|
|
32533
|
+
}
|
|
32534
|
+
console.log();
|
|
32535
|
+
if (opts.keep === "remote") {
|
|
32536
|
+
for (const file of conflictFiles) {
|
|
32537
|
+
await unlink2(join15(claudeDir, file));
|
|
32495
32538
|
}
|
|
32496
|
-
|
|
32497
|
-
|
|
32498
|
-
await writeStatus(configDir, status).catch(() => {});
|
|
32499
|
-
log(configDir, `Push error: ${e.message}`);
|
|
32539
|
+
console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept remote versions.`));
|
|
32540
|
+
return;
|
|
32500
32541
|
}
|
|
32501
|
-
|
|
32502
|
-
|
|
32503
|
-
|
|
32504
|
-
|
|
32505
|
-
|
|
32506
|
-
|
|
32507
|
-
|
|
32508
|
-
const result = await pullSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
|
|
32509
|
-
status.lastPullAt = Date.now();
|
|
32510
|
-
status.pullCount++;
|
|
32511
|
-
status.lastError = null;
|
|
32512
|
-
await writeStatus(configDir, status);
|
|
32513
|
-
if (result.filesChanged > 0) {
|
|
32514
|
-
log(configDir, `Pull: ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
32542
|
+
if (opts.keep === "local") {
|
|
32543
|
+
for (const file of conflictFiles) {
|
|
32544
|
+
const conflictPath = join15(claudeDir, file);
|
|
32545
|
+
const originalPath = conflictPath.replace(/\.conflict$/, "");
|
|
32546
|
+
const conflictContent = await readFile10(conflictPath);
|
|
32547
|
+
await writeFile10(originalPath, conflictContent);
|
|
32548
|
+
await unlink2(conflictPath);
|
|
32515
32549
|
}
|
|
32516
|
-
|
|
32517
|
-
|
|
32550
|
+
console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept local versions.`));
|
|
32551
|
+
return;
|
|
32552
|
+
}
|
|
32553
|
+
const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
|
|
32554
|
+
for (const file of conflictFiles) {
|
|
32555
|
+
const original = file.replace(/\.conflict$/, "");
|
|
32556
|
+
const conflictPath = join15(claudeDir, file);
|
|
32557
|
+
const originalPath = join15(claudeDir, original);
|
|
32558
|
+
const { action } = await inquirer2.default.prompt([
|
|
32559
|
+
{
|
|
32560
|
+
type: "list",
|
|
32561
|
+
name: "action",
|
|
32562
|
+
message: `${original}:`,
|
|
32563
|
+
choices: [
|
|
32564
|
+
{ name: "Keep remote (current file)", value: "remote" },
|
|
32565
|
+
{ name: "Keep local (restore from .conflict)", value: "local" },
|
|
32566
|
+
{ name: "Skip", value: "skip" }
|
|
32567
|
+
]
|
|
32568
|
+
}
|
|
32569
|
+
]);
|
|
32570
|
+
if (action === "remote") {
|
|
32571
|
+
await unlink2(conflictPath);
|
|
32572
|
+
console.log(chalk2.dim(` Kept remote \u2014 deleted ${file}`));
|
|
32573
|
+
} else if (action === "local") {
|
|
32574
|
+
const conflictContent = await readFile10(conflictPath);
|
|
32575
|
+
await writeFile10(originalPath, conflictContent);
|
|
32576
|
+
await unlink2(conflictPath);
|
|
32577
|
+
console.log(chalk2.dim(` Restored local version`));
|
|
32518
32578
|
}
|
|
32519
|
-
} catch (e) {
|
|
32520
|
-
status.lastError = e.message;
|
|
32521
|
-
await writeStatus(configDir, status).catch(() => {});
|
|
32522
|
-
log(configDir, `Pull error: ${e.message}`);
|
|
32523
32579
|
}
|
|
32524
|
-
|
|
32525
|
-
|
|
32526
|
-
|
|
32527
|
-
|
|
32528
|
-
|
|
32529
|
-
|
|
32530
|
-
|
|
32580
|
+
const remaining = await findConflictFiles(claudeDir);
|
|
32581
|
+
if (remaining.length === 0) {
|
|
32582
|
+
console.log(chalk2.green(`
|
|
32583
|
+
All conflicts resolved.`));
|
|
32584
|
+
} else {
|
|
32585
|
+
console.log(chalk2.yellow(`
|
|
32586
|
+
${remaining.length} conflict(s) remaining.`));
|
|
32531
32587
|
}
|
|
32532
|
-
}
|
|
32533
|
-
const watcher = startWatcher(doPush);
|
|
32534
|
-
const pullInterval = setInterval(doPull, DAEMON_INTERVAL_MS);
|
|
32535
|
-
const heartbeatInterval = setInterval(doHeartbeat, HEARTBEAT_INTERVAL_MS);
|
|
32536
|
-
await doPull();
|
|
32537
|
-
await doHeartbeat();
|
|
32538
|
-
const shutdown = async () => {
|
|
32539
|
-
log(configDir, "Daemon shutting down...");
|
|
32540
|
-
watcher.stop();
|
|
32541
|
-
clearInterval(pullInterval);
|
|
32542
|
-
clearInterval(heartbeatInterval);
|
|
32543
|
-
const { rm: rm2 } = await import("fs/promises");
|
|
32544
|
-
await rm2(getPidPath(configDir)).catch(() => {});
|
|
32545
|
-
await rm2(getStatusPath(configDir)).catch(() => {});
|
|
32546
|
-
log(configDir, "Daemon stopped");
|
|
32547
|
-
process.exit(0);
|
|
32548
|
-
};
|
|
32549
|
-
process.on("SIGTERM", shutdown);
|
|
32550
|
-
process.on("SIGINT", shutdown);
|
|
32551
|
-
if (platform3() === "win32") {
|
|
32552
|
-
process.on("SIGBREAK", shutdown);
|
|
32553
|
-
}
|
|
32588
|
+
});
|
|
32554
32589
|
}
|
|
32555
32590
|
|
|
32556
32591
|
// src/commands/daemon.ts
|
|
32592
|
+
import { spawn as spawn4 } from "child_process";
|
|
32593
|
+
import { readFile as readFile11, rm as rm2 } from "fs/promises";
|
|
32594
|
+
import { platform as platform5 } from "os";
|
|
32557
32595
|
function isProcessRunning(pid) {
|
|
32558
32596
|
try {
|
|
32559
32597
|
process.kill(pid, 0);
|
|
@@ -32593,8 +32631,8 @@ function registerDaemonCommands(program2) {
|
|
|
32593
32631
|
await rm2(getPidPath(configDir)).catch(() => {});
|
|
32594
32632
|
await rm2(getStatusPath(configDir)).catch(() => {});
|
|
32595
32633
|
}
|
|
32596
|
-
const isWin =
|
|
32597
|
-
const child =
|
|
32634
|
+
const isWin = platform5() === "win32";
|
|
32635
|
+
const child = spawn4("ccsini", ["daemon", "_run"], {
|
|
32598
32636
|
detached: true,
|
|
32599
32637
|
stdio: "ignore",
|
|
32600
32638
|
...isWin ? { shell: true } : {}
|
|
@@ -32621,7 +32659,7 @@ function registerDaemonCommands(program2) {
|
|
|
32621
32659
|
console.log("Daemon is not running (cleaned up stale PID file).");
|
|
32622
32660
|
return;
|
|
32623
32661
|
}
|
|
32624
|
-
const isWin =
|
|
32662
|
+
const isWin = platform5() === "win32";
|
|
32625
32663
|
if (isWin) {
|
|
32626
32664
|
const { execSync: execSync3 } = await import("child_process");
|
|
32627
32665
|
try {
|