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