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.
Files changed (2) hide show
  1. package/dist/index.js +2204 -2180
  2. 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.53";
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 join9 } from "path";
29981
- async function getMasterKey(configDir) {
29982
- const cachedKeyPath = join9(configDir, ".cached-key");
29983
- try {
29984
- const cached = await readFile7(cachedKeyPath);
29985
- return new Uint8Array(cached);
29986
- } catch {
29987
- throw new Error("No cached key. Run 'ccsini unlock' to enter your encryption password.");
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
- async function getAuthenticatedClient(configDir) {
29991
- const config = await loadKeys(configDir);
29992
- const privateKey = await importPrivateKey(config.devicePrivateKey);
29993
- const jwt = await createDeviceJWT(privateKey, config.deviceId);
29994
- return new CcsiniClient(config.apiUrl, jwt);
29995
- }
29996
- function registerAutoCommands(program2) {
29997
- program2.command("auto-pull").description("Auto-pull changes (called by Claude Code hooks)").action(async () => {
29998
- const configDir = getConfigDir();
29999
- if (!await configExists(configDir))
30000
- return;
30001
- try {
30002
- const masterKey = await getMasterKey(configDir);
30003
- const client = await getAuthenticatedClient(configDir);
30004
- const config = await loadKeys(configDir);
30005
- const storedSession = await loadSessionConfig(configDir);
30006
- const sessionOptions = {
30007
- enabled: storedSession.enabled,
30008
- maxPerProject: storedSession.maxPerProject,
30009
- maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
30010
- };
30011
- const result = await pullSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
30012
- if (result.filesChanged > 0) {
30013
- console.log(`[ccsini] Pulled ${result.filesChanged} files (${result.durationMs}ms)`);
30014
- }
30015
- if (result.conflicts.length > 0) {
30016
- console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
30017
- }
30018
- } catch {}
30019
- });
30020
- program2.command("auto-push").description("Auto-push changes (called by Claude Code hooks)").action(async () => {
30021
- const configDir = getConfigDir();
30022
- if (!await configExists(configDir))
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
- const masterKey = await getMasterKey(configDir);
30026
- const client = await getAuthenticatedClient(configDir);
30027
- const config = await loadKeys(configDir);
30028
- const storedSession = await loadSessionConfig(configDir);
30029
- const sessionOptions = {
30030
- enabled: storedSession.enabled,
30031
- maxPerProject: storedSession.maxPerProject,
30032
- maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
30033
- };
30034
- const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
30035
- if (result.filesChanged > 0) {
30036
- let msg = `[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`;
30037
- if (result.cacheStats && result.cacheStats.hits > 0) {
30038
- msg += ` [cache: ${result.cacheStats.hitRate.toFixed(0)}% hits]`;
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
- program2.command("unlock").description("Enter encryption password to enable auto-sync").action(async () => {
30048
- const configDir = getConfigDir();
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
- const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
30054
- const { password } = await inquirer2.default.prompt([
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 rm(join9(configDir, ".cached-key"));
30083
- console.log("Locked. Auto-sync paused until next unlock.");
30084
- } catch {
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
- // src/commands/doctor.ts
30091
- init_source();
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
- await access2(claudeDir);
30104
- console.log(source_default.green(" \u2713 Claude Code directory found"));
30105
- } catch {
30106
- console.log(source_default.red(" \u2717 Claude Code directory not found"));
30107
- allGood = false;
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
- const config = await loadKeys(configDir);
30117
- console.log(source_default.green(` \u2713 Device: ${config.deviceName} (${config.deviceId.slice(0, 8)}...)`));
30118
- if (await hooksInstalled(claudeDir)) {
30119
- console.log(source_default.green(" \u2713 Hooks installed"));
30120
- const { healthy, issues } = await checkHooksHealth(claudeDir);
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
- console.log(source_default.yellow(" \u26A0 Hooks not installed (run 'ccsini init')"));
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
- function registerSelfCommands(program2) {
30173
- program2.command("update").description("Update ccsini to the latest version").action(async () => {
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
- console.log(`New version available: ${latest}`);
30185
- console.log(`Updating...
30186
- `);
30187
- let updated = false;
30188
- try {
30189
- execSync2(`bun add -g ccsini@${latest}`, { stdio: "inherit" });
30190
- updated = true;
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
- execSync2(`npm install -g ccsini@${latest}`, { stdio: "inherit" });
30196
- updated = true;
30197
- } catch {}
30198
- }
30199
- if (!updated) {
30200
- console.error(`
30201
- Update failed. Try manually:`);
30202
- console.error(` npm install -g ccsini@${latest}`);
30203
- process.exit(1);
30204
- }
30205
- console.log(`
30206
- Updated to v${latest}`);
30207
- try {
30208
- const { fixed } = await fixHooks(getClaudeDir());
30209
- if (fixed > 0) {
30210
- console.log(`Repaired ${fixed} hook issue${fixed > 1 ? "s" : ""} in Claude Code settings.`);
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
- // src/commands/sync.ts
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
- async function getAuthenticatedClient2(configDir) {
30270
- const config = await loadKeys(configDir);
30271
- const privateKey = await importPrivateKey(config.devicePrivateKey);
30272
- const jwt = await createDeviceJWT(privateKey, config.deviceId);
30273
- return new CcsiniClient(config.apiUrl, jwt);
30274
- }
30275
- function formatBytes(bytes) {
30276
- if (bytes < 1024)
30277
- return `${bytes} B`;
30278
- if (bytes < 1048576)
30279
- return `${(bytes / 1024).toFixed(1)} KB`;
30280
- return `${(bytes / 1048576).toFixed(1)} MB`;
30281
- }
30282
- function registerSyncCommands(program2) {
30283
- const syncCmd = program2.command("sync").description("Sync management commands");
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
- // src/commands/reset.ts
30526
- init_auth();
30527
- import { rm } from "fs/promises";
30528
- function registerResetCommand(program2) {
30529
- program2.command("reset").description("Wipe all server data and local config (full account reset)").action(async () => {
30530
- const configDir = getConfigDir();
30531
- if (!await configExists(configDir)) {
30532
- console.error("Not initialized. Nothing to reset.");
30533
- process.exit(1);
30534
- }
30535
- const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
30536
- const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
30537
- console.log(chalk2.red.bold(`
30538
- WARNING: This will permanently delete:`));
30539
- console.log(chalk2.red(" - All encrypted files on the server"));
30540
- console.log(chalk2.red(" - Your encryption salt"));
30541
- console.log(chalk2.red(` - Your local config (~/.ccsini/)
30542
- `));
30543
- const { confirm } = await inquirer2.default.prompt([
30544
- {
30545
- type: "confirm",
30546
- name: "confirm",
30547
- message: "Are you sure you want to wipe everything?",
30548
- default: false
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
- container.add(item);
31031
+ str = str.replace(DOUBLE_SLASH_RE, SLASH);
31032
+ if (prepend) {
31033
+ str = SLASH + str;
31034
+ }
31035
+ return str;
31323
31036
  };
31324
- var clearItem = (cont) => (key) => {
31325
- const set = cont[key];
31326
- if (set instanceof Set) {
31327
- set.clear();
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
- delete cont[key];
31042
+ return path2;
31330
31043
  }
31331
31044
  };
31332
- var delFromSet = (main2, prop, item) => {
31333
- const container = main2[prop];
31334
- if (container instanceof Set) {
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 isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
31341
- var FsWatchInstances = new Map;
31342
- function createFsWatchInstance(path2, options, listener, errHandler, emitRaw) {
31343
- const handleEvent = (rawEvent, evPath) => {
31344
- listener(path2);
31345
- emitRaw(rawEvent, evPath, { watchedPath: path2 });
31346
- if (evPath && path2 !== evPath) {
31347
- fsWatchBroadcast(sp.resolve(path2, evPath), KEY_LISTENERS, sp.join(path2, evPath));
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
- var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
31360
- const cont = FsWatchInstances.get(fullPath);
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
- return watcher.close.bind(watcher);
31066
+ if (item !== ONE_DOT && item !== TWO_DOTS)
31067
+ items.add(item);
31376
31068
  }
31377
- if (cont) {
31378
- addAndConvert(cont, KEY_LISTENERS, listener);
31379
- addAndConvert(cont, KEY_ERR, errHandler);
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
- watcher.on(EV.ERROR, async (error) => {
31386
- const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
31387
- if (cont)
31388
- cont.watcherUnusable = true;
31389
- if (isWindows && error.code === "EPERM") {
31390
- try {
31391
- const fd = await open(path2, "r");
31392
- await fd.close();
31393
- broadcastErr(error);
31394
- } catch (err) {}
31395
- } else {
31396
- broadcastErr(error);
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
- FsWatchInstances.set(fullPath, cont);
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
- return () => {
31408
- delFromSet(cont, KEY_LISTENERS, listener);
31409
- delFromSet(cont, KEY_ERR, errHandler);
31410
- delFromSet(cont, KEY_RAW, rawEmitter);
31411
- if (isEmptySet(cont.listeners)) {
31412
- cont.watcher.close();
31413
- FsWatchInstances.delete(fullPath);
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
- if (cont) {
31430
- addAndConvert(cont, KEY_LISTENERS, listener);
31431
- addAndConvert(cont, KEY_RAW, rawEmitter);
31432
- } else {
31433
- cont = {
31434
- listeners: listener,
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
- _watchWithNodeFs(path2, listener) {
31469
- const opts = this.fsw.options;
31470
- const directory = sp.dirname(path2);
31471
- const basename2 = sp.basename(path2);
31472
- const parent = this.fsw._getWatchedDir(directory);
31473
- parent.add(basename2);
31474
- const absolutePath = sp.resolve(path2);
31475
- const options = {
31476
- persistent: opts.persistent
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
- } else {
31489
- closer = setFsWatchListener(path2, absolutePath, options, {
31490
- listener,
31491
- errHandler: this._boundHandleError,
31492
- rawEmitter: this.fsw._emitRaw
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
- return closer;
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
- _handleFile(file, stats, initialAdd) {
31498
- if (this.fsw.closed) {
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
- const dirname3 = sp.dirname(file);
31502
- const basename2 = sp.basename(file);
31503
- const parent = this.fsw._getWatchedDir(dirname3);
31504
- let prevStats = stats;
31505
- if (parent.has(basename2))
31506
- return;
31507
- const listener = async (path2, newStats) => {
31508
- if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
31509
- return;
31510
- if (!newStats || newStats.mtimeMs === 0) {
31511
- try {
31512
- const newStats2 = await stat3(file);
31513
- if (this.fsw.closed)
31514
- return;
31515
- const at = newStats2.atimeMs;
31516
- const mt = newStats2.mtimeMs;
31517
- if (!at || at <= mt || mt !== prevStats.mtimeMs) {
31518
- this.fsw._emit(EV.CHANGE, file, newStats2);
31519
- }
31520
- if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
31521
- this.fsw._closeFile(path2);
31522
- prevStats = newStats2;
31523
- const closer2 = this._watchWithNodeFs(file, listener);
31524
- if (closer2)
31525
- this.fsw._addPathCloser(path2, closer2);
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
- prevStats = newStats2;
31385
+ args.push(stats2);
31528
31386
  }
31529
- } catch (error) {
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
- prevStats = newStats;
31539
- }
31540
- };
31541
- const closer = this._watchWithNodeFs(file, listener);
31542
- if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
31543
- if (!this.fsw._throttle(EV.ADD, file, 0))
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
- this.fsw._emit(EV.ADD, file, stats);
31406
+ args.push(stats2);
31546
31407
  }
31547
- return closer;
31408
+ this.emitWithAll(event, args);
31409
+ return this;
31548
31410
  }
31549
- async _handleSymlink(entry, directory, path2, item) {
31550
- if (this.fsw.closed) {
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 full = entry.fullPath;
31554
- const dir = this.fsw._getWatchedDir(directory);
31555
- if (!this.fsw.options.followSymlinks) {
31556
- this.fsw._incrReadyCount();
31557
- let linkPath;
31558
- try {
31559
- linkPath = await fsrealpath(path2);
31560
- } catch (e) {
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
- } else {
31572
- dir.add(item);
31573
- this.fsw._symlinkPaths.set(full, linkPath);
31574
- this.fsw._emit(EV.ADD, path2, entry.stats);
31575
- }
31576
- this.fsw._emitReady();
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
- if (this.fsw._symlinkPaths.has(full)) {
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.fsw._symlinkPaths.set(full, true);
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
- _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
31585
- directory = sp.join(directory, "");
31586
- const throttleKey = target ? `${directory}:${target}` : directory;
31587
- throttler = this.fsw._throttle("readdir", throttleKey, 1000);
31588
- if (!throttler)
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
- const previous = this.fsw._getWatchedDir(wh.path);
31591
- const current = new Set;
31592
- let stream = this.fsw._readdirp(directory, {
31593
- fileFilter: (entry) => wh.filterPath(entry),
31594
- directoryFilter: (entry) => wh.filterDir(entry)
31595
- });
31596
- if (!stream)
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
- stream.on(STR_DATA, async (entry) => {
31599
- if (this.fsw.closed) {
31600
- stream = undefined;
31601
- return;
31602
- }
31603
- const item = entry.path;
31604
- let path2 = sp.join(directory, item);
31605
- current.add(item);
31606
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path2, item)) {
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
- async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
31641
- const parentDir = this.fsw._getWatchedDir(sp.dirname(dir));
31642
- const tracked = parentDir.has(sp.basename(dir));
31643
- if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
31644
- this.fsw._emit(EV.ADD_DIR, dir, stats);
31645
- }
31646
- parentDir.add(sp.basename(dir));
31647
- this.fsw._getWatchedDir(dir);
31648
- let throttler;
31649
- let closer;
31650
- const oDepth = this.fsw.options.depth;
31651
- if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
31652
- if (!target) {
31653
- await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
31654
- if (this.fsw.closed)
31655
- return;
31656
- }
31657
- closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
31658
- if (stats2 && stats2.mtimeMs === 0)
31659
- return;
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
- try {
31677
- const stats = await statMethods[wh.statMethod](wh.watchPath);
31678
- if (this.fsw.closed)
31616
+ });
31617
+ const triggerSync = () => {
31618
+ if (debounceTimer)
31619
+ clearTimeout(debounceTimer);
31620
+ debounceTimer = setTimeout(async () => {
31621
+ if (syncing)
31679
31622
  return;
31680
- if (this.fsw._isIgnored(wh.watchPath, stats)) {
31681
- ready();
31682
- return false;
31683
- }
31684
- const follow = this.fsw.options.followSymlinks;
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
- // ../../node_modules/.bun/chokidar@5.0.0/node_modules/chokidar/index.js
31727
- /*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
31728
- var SLASH = "/";
31729
- var SLASH_SLASH = "//";
31730
- var ONE_DOT = ".";
31731
- var TWO_DOTS = "..";
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
- var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
31741
- function createPattern(matcher) {
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 normalizePath(path2) {
31765
- if (typeof path2 !== "string")
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 matchPatterns(patterns, testString, stats) {
31778
- const path2 = normalizePath(testString);
31779
- for (let index = 0;index < patterns.length; index++) {
31780
- const pattern = patterns[index];
31781
- if (pattern(path2, stats)) {
31782
- return true;
31783
- }
31784
- }
31785
- return false;
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 anymatch(matchers, testString) {
31788
- if (matchers == null) {
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
- var unifyPaths = (paths_) => {
31801
- const paths = arrify(paths_).flat();
31802
- if (!paths.every((p) => typeof p === STRING_TYPE)) {
31803
- throw new TypeError(`Non-string provided as watch path: ${paths}`);
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
- add(item) {
31845
- const { items } = this;
31846
- if (!items)
31847
- return;
31848
- if (item !== ONE_DOT && item !== TWO_DOTS)
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
- async remove(item) {
31852
- const { items } = this;
31853
- if (!items)
31854
- return;
31855
- items.delete(item);
31856
- if (items.size > 0)
31857
- return;
31858
- const dir = this.path;
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 readdir4(dir);
31861
- } catch (err) {
31862
- if (this._removeWatcher) {
31863
- this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
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
- has(item) {
31868
- const { items } = this;
31869
- if (!items)
31870
- return;
31871
- return items.has(item);
31872
- }
31873
- getChildren() {
31874
- const { items } = this;
31875
- if (!items)
31876
- return [];
31877
- return [...items.values()];
31878
- }
31879
- dispose() {
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
- this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
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
- _removeIgnoredPath(matcher) {
32017
- this._ignoredPaths.delete(matcher);
32018
- if (typeof matcher === "string") {
32019
- for (const ignored of this._ignoredPaths) {
32020
- if (isMatcherObject(ignored) && ignored.path === matcher) {
32021
- this._ignoredPaths.delete(ignored);
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
- add(paths_, _origAdd, _internal) {
32027
- const { cwd } = this.options;
32028
- this.closed = false;
32029
- this._closePromise = undefined;
32030
- let paths = unifyPaths(paths_);
32031
- if (cwd) {
32032
- paths = paths.map((path2) => {
32033
- const absPath = getAbsolutePath(path2, cwd);
32034
- return absPath;
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
- paths.forEach((path2) => {
32038
- this._removeIgnoredPath(path2);
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
- this._userIgnored = undefined;
32041
- if (!this._readyCount)
32042
- this._readyCount = 0;
32043
- this._readyCount += paths.length;
32044
- Promise.all(paths.map(async (path2) => {
32045
- const res = await this._nodeFsHandler._addToNodeFs(path2, !_internal, undefined, 0, _origAdd);
32046
- if (res)
32047
- this._emitReady();
32048
- return res;
32049
- })).then((results) => {
32050
- if (this.closed)
32051
- return;
32052
- results.forEach((item) => {
32053
- if (item)
32054
- this.add(sp2.dirname(item), sp2.basename(_origAdd || item));
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
- return this;
32058
- }
32059
- unwatch(paths_) {
32060
- if (this.closed)
32061
- return this;
32062
- const paths = unifyPaths(paths_);
32063
- const { cwd } = this.options;
32064
- paths.forEach((path2) => {
32065
- if (!sp2.isAbsolute(path2) && !this._closers.has(path2)) {
32066
- if (cwd)
32067
- path2 = sp2.join(cwd, path2);
32068
- path2 = sp2.resolve(path2);
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
- this._closePath(path2);
32071
- this._addIgnoredPath(path2);
32072
- if (this._watched.has(path2)) {
32073
- this._addIgnoredPath({
32074
- path: path2,
32075
- recursive: true
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
- this._userIgnored = undefined;
32079
- });
32080
- return this;
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
- this.closed = true;
32087
- this.removeAllListeners();
32088
- const closers = [];
32089
- this._closers.forEach((closerList) => closerList.forEach((closer) => {
32090
- const promise = closer();
32091
- if (promise instanceof Promise)
32092
- closers.push(promise);
32093
- }));
32094
- this._streams.forEach((stream) => stream.destroy());
32095
- this._userIgnored = undefined;
32096
- this._readyCount = 0;
32097
- this._readyEmitted = false;
32098
- this._watched.forEach((dirent) => dirent.dispose());
32099
- this._closers.clear();
32100
- this._watched.clear();
32101
- this._streams.clear();
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
- async _emit(event, path2, stats) {
32124
- if (this.closed)
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
- if (opts.atomic) {
32141
- if (event === EVENTS.UNLINK) {
32142
- this._pendingUnlinks.set(path2, [event, ...args]);
32143
- setTimeout(() => {
32144
- this._pendingUnlinks.forEach((entry, path3) => {
32145
- this.emit(...entry);
32146
- this.emit(EVENTS.ALL, ...entry);
32147
- this._pendingUnlinks.delete(path3);
32148
- });
32149
- }, typeof opts.atomic === "number" ? opts.atomic : 100);
32150
- return this;
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
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path2)) {
32153
- event = EVENTS.CHANGE;
32154
- this._pendingUnlinks.delete(path2);
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
- if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
32158
- const awfEmit = (err, stats2) => {
32159
- if (err) {
32160
- event = EVENTS.ERROR;
32161
- args[0] = err;
32162
- this.emitWithAll(event, args);
32163
- } else if (stats2) {
32164
- if (args.length > 1) {
32165
- args[1] = stats2;
32166
- } else {
32167
- args.push(stats2);
32168
- }
32169
- this.emitWithAll(event, args);
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
- this._awaitWriteFinish(path2, awf.stabilityThreshold, event, awfEmit);
32173
- return this;
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
- if (event === EVENTS.CHANGE) {
32176
- const isThrottled = !this._throttle(EVENTS.CHANGE, path2, 50);
32177
- if (isThrottled)
32178
- return this;
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
- if (opts.alwaysStat && stats === undefined && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
32181
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path2) : path2;
32182
- let stats2;
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
- stats2 = await stat4(fullPath);
32185
- } catch (err) {}
32186
- if (!stats2 || this.closed)
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
- args.push(stats2);
32189
- }
32190
- this.emitWithAll(event, args);
32191
- return this;
32192
- }
32193
- _handleError(error) {
32194
- const code = error && error.code;
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
- const now2 = Number(new Date);
32250
- if (prevStat && curStat.size !== prevStat.size) {
32251
- writes.get(path2).lastChange = now2;
32242
+ if (orphans.length > 10) {
32243
+ console.log(chalk2.dim(` ... and ${orphans.length - 10} more`));
32252
32244
  }
32253
- const pw = writes.get(path2);
32254
- const df = now2 - pw.lastChange;
32255
- if (df >= threshold) {
32256
- writes.delete(path2);
32257
- awfEmit(undefined, curStat);
32258
- } else {
32259
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
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
- if (!writes.has(path2)) {
32264
- writes.set(path2, {
32265
- lastChange: now,
32266
- cancelWait: () => {
32267
- writes.delete(path2);
32268
- clearTimeout(timeoutHandler);
32269
- return event;
32270
- }
32271
- });
32272
- timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
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
- _isIgnored(path2, stats) {
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
- return this._userIgnored(path2, stats);
32287
- }
32288
- _isntIgnored(path2, stat5) {
32289
- return !this._isIgnored(path2, stat5);
32290
- }
32291
- _getWatchHelpers(path2) {
32292
- return new WatchHelper(path2, this.options.followSymlinks, this);
32293
- }
32294
- _getWatchedDir(directory) {
32295
- const dir = sp2.resolve(directory);
32296
- if (!this._watched.has(dir))
32297
- this._watched.set(dir, new DirEntry(dir, this._boundRemove));
32298
- return this._watched.get(dir);
32299
- }
32300
- _hasReadPermissions(stats) {
32301
- if (this.options.ignorePermissionErrors)
32302
- return true;
32303
- return Boolean(Number(stats.mode) & 256);
32304
- }
32305
- _remove(directory, item, isDirectory) {
32306
- const path2 = sp2.join(directory, item);
32307
- const fullPath = sp2.resolve(path2);
32308
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path2) || this._watched.has(fullPath);
32309
- if (!this._throttle("remove", path2, 100))
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 wp = this._getWatchedDir(path2);
32315
- const nestedDirectoryChildren = wp.getChildren();
32316
- nestedDirectoryChildren.forEach((nested) => this._remove(path2, nested));
32317
- const parent = this._getWatchedDir(directory);
32318
- const wasTracked = parent.has(item);
32319
- parent.remove(item);
32320
- if (this._symlinkPaths.has(fullPath)) {
32321
- this._symlinkPaths.delete(fullPath);
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
- let relPath = path2;
32324
- if (this.options.cwd)
32325
- relPath = sp2.relative(this.options.cwd, path2);
32326
- if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
32327
- const event = this._pendingWrites.get(relPath).cancelWait();
32328
- if (event === EVENTS.ADD)
32329
- return;
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
- this._watched.delete(path2);
32332
- this._watched.delete(fullPath);
32333
- const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
32334
- if (wasTracked && !this._isIgnored(path2))
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
- list.push(closer);
32359
- }
32360
- _readdirp(root, opts) {
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
- stream.once(STR_END, () => {
32370
- if (stream) {
32371
- this._streams.delete(stream);
32372
- stream = undefined;
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
- return stream;
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/daemon/runner.ts
32426
- init_src();
32427
- init_src();
32428
- var HEARTBEAT_INTERVAL_MS = 3 * 60 * 1000;
32429
- function getStatusPath(configDir) {
32430
- return join15(configDir, "daemon.status.json");
32431
- }
32432
- function getLogPath(configDir) {
32433
- return join15(configDir, "daemon.log");
32434
- }
32435
- function getPidPath(configDir) {
32436
- return join15(configDir, "daemon.pid");
32437
- }
32438
- function log(configDir, message2) {
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
- const pid = process.pid;
32481
- await writeFile10(getPidPath(configDir), String(pid));
32482
- const status = {
32483
- pid,
32484
- startedAt: Date.now(),
32485
- lastPushAt: null,
32486
- lastPullAt: null,
32487
- lastError: null,
32488
- pushCount: 0,
32489
- pullCount: 0
32490
- };
32491
- await writeStatus(configDir, status);
32492
- log(configDir, `Daemon started (PID ${pid})`);
32493
- async function doPush() {
32494
- try {
32495
- const masterKey = await getMasterKey3(configDir);
32496
- const client = await getAuthenticatedClient3(configDir);
32497
- const config = await loadKeys(configDir);
32498
- const sessionOptions = await getSessionOptions(configDir);
32499
- const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
32500
- status.lastPushAt = Date.now();
32501
- status.pushCount++;
32502
- status.lastError = null;
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
- } catch (e) {
32511
- status.lastError = e.message;
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
- async function doPull() {
32517
- try {
32518
- const masterKey = await getMasterKey3(configDir);
32519
- const client = await getAuthenticatedClient3(configDir);
32520
- const config = await loadKeys(configDir);
32521
- const sessionOptions = await getSessionOptions(configDir);
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
- if (result.conflicts.length > 0) {
32531
- log(configDir, `Pull: ${result.conflicts.length} conflict(s)`);
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
- async function doHeartbeat() {
32540
- try {
32541
- const client = await getAuthenticatedClient3(configDir);
32542
- await client.heartbeat("daemon");
32543
- } catch (e) {
32544
- log(configDir, `Heartbeat error: ${e.message}`);
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 = platform4() === "win32";
32611
- const child = spawn3("ccsini", ["daemon", "_run"], {
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 = platform4() === "win32";
32662
+ const isWin = platform5() === "win32";
32639
32663
  if (isWin) {
32640
32664
  const { execSync: execSync3 } = await import("child_process");
32641
32665
  try {