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