repowise 0.1.82 → 0.1.83

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/bin/repowise.js +1547 -762
  2. package/package.json +4 -2
@@ -3,63 +3,100 @@
3
3
  // bin/repowise.ts
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
5
  import { fileURLToPath as fileURLToPath3 } from "url";
6
- import { dirname as dirname6, join as join16 } from "path";
6
+ import { dirname as dirname9, join as join22 } from "path";
7
7
  import { Command } from "commander";
8
8
 
9
9
  // ../listener/dist/main.js
10
- import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir5 } from "fs/promises";
11
- import { homedir as homedir4 } from "os";
12
- import { join as join6 } from "path";
10
+ import { readFile as readFile5, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
11
+ import { join as join12, dirname as dirname4 } from "path";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
+ import lockfile2 from "proper-lockfile";
13
14
 
14
- // ../listener/dist/lib/config.js
15
- import { readFile, writeFile, rename, mkdir, chmod } from "fs/promises";
15
+ // ../../packages/shared/src/config-dir.ts
16
16
  import { homedir } from "os";
17
17
  import { join } from "path";
18
- var CONFIG_DIR = join(homedir(), ".repowise");
19
- var CONFIG_PATH = join(CONFIG_DIR, "config.json");
20
- var DEFAULT_API_URL = "https://api.repowise.ai";
21
- var rawConfigCache = null;
18
+ function getConfigDir() {
19
+ const isStaging = true ? false : false;
20
+ return join(homedir(), isStaging ? ".repowise-staging" : ".repowise");
21
+ }
22
+
23
+ // ../listener/dist/lib/config.js
24
+ import { readFile, writeFile, rename, unlink, mkdir, chmod } from "fs/promises";
25
+ import { join as join2 } from "path";
26
+ import lockfile from "proper-lockfile";
27
+ var DEFAULT_API_URL = false ? "https://staging-api.repowise.ai" : "https://api.repowise.ai";
22
28
  async function getListenerConfig() {
29
+ const configDir = getConfigDir();
30
+ const configPath = join2(configDir, "config.json");
23
31
  const apiUrl = process.env["REPOWISE_API_URL"] ?? DEFAULT_API_URL;
24
32
  try {
25
- const data = await readFile(CONFIG_PATH, "utf-8");
33
+ const data = await readFile(configPath, "utf-8");
26
34
  const raw = JSON.parse(data);
27
- rawConfigCache = raw;
28
35
  const validRepos = (raw.repos ?? []).filter((r) => typeof r === "object" && r !== null && typeof r.repoId === "string" && typeof r.localPath === "string");
29
36
  return {
30
37
  defaultApiUrl: raw.apiUrl ?? apiUrl,
31
- repos: validRepos
38
+ repos: validRepos,
39
+ autoDiscoverRepos: raw.autoDiscoverRepos ?? false,
40
+ noAutoUpdate: raw.noAutoUpdate ?? false
32
41
  };
33
42
  } catch {
34
- return { defaultApiUrl: apiUrl, repos: [] };
43
+ return { defaultApiUrl: apiUrl, repos: [], autoDiscoverRepos: false, noAutoUpdate: false };
35
44
  }
36
45
  }
37
46
  async function saveListenerConfig(config2) {
38
- await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
39
- const output = {
40
- ...rawConfigCache ?? {},
41
- apiUrl: config2.defaultApiUrl,
42
- repos: config2.repos
43
- };
44
- const tmpPath = CONFIG_PATH + ".tmp";
45
- await writeFile(tmpPath, JSON.stringify(output, null, 2));
46
- await chmod(tmpPath, 384);
47
- await rename(tmpPath, CONFIG_PATH);
48
- rawConfigCache = output;
47
+ const configDir = getConfigDir();
48
+ const configPath = join2(configDir, "config.json");
49
+ await mkdir(configDir, { recursive: true, mode: 448 });
50
+ try {
51
+ await writeFile(configPath, "", { flag: "a" });
52
+ } catch {
53
+ }
54
+ let release = null;
55
+ try {
56
+ release = await lockfile.lock(configPath, { stale: 1e4, retries: 3, realpath: false });
57
+ let raw = {};
58
+ try {
59
+ const data = await readFile(configPath, "utf-8");
60
+ raw = JSON.parse(data);
61
+ } catch {
62
+ }
63
+ const output = {
64
+ ...raw,
65
+ apiUrl: config2.defaultApiUrl,
66
+ repos: config2.repos
67
+ };
68
+ const tmpPath = configPath + ".tmp";
69
+ try {
70
+ await writeFile(tmpPath, JSON.stringify(output, null, 2));
71
+ await chmod(tmpPath, 384);
72
+ await rename(tmpPath, configPath);
73
+ } catch (err) {
74
+ try {
75
+ await unlink(tmpPath);
76
+ } catch {
77
+ }
78
+ throw err;
79
+ }
80
+ } finally {
81
+ if (release) {
82
+ try {
83
+ await release();
84
+ } catch {
85
+ }
86
+ }
87
+ }
49
88
  }
50
89
 
51
90
  // ../listener/dist/lib/state.js
52
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
53
- import { homedir as homedir2 } from "os";
54
- import { join as join2 } from "path";
55
- var CONFIG_DIR2 = join2(homedir2(), ".repowise");
56
- var STATE_PATH = join2(CONFIG_DIR2, "listener-state.json");
91
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2, rename as rename2, unlink as unlink2 } from "fs/promises";
92
+ import { join as join3 } from "path";
57
93
  function emptyState() {
58
94
  return { repos: {} };
59
95
  }
60
96
  async function loadState() {
97
+ const statePath = join3(getConfigDir(), "listener-state.json");
61
98
  try {
62
- const data = await readFile2(STATE_PATH, "utf-8");
99
+ const data = await readFile2(statePath, "utf-8");
63
100
  return JSON.parse(data);
64
101
  } catch (err) {
65
102
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -69,19 +106,34 @@ async function loadState() {
69
106
  }
70
107
  }
71
108
  async function saveState(state) {
72
- await mkdir2(CONFIG_DIR2, { recursive: true, mode: 448 });
73
- await writeFile2(STATE_PATH, JSON.stringify(state, null, 2));
74
- await chmod2(STATE_PATH, 384);
109
+ const configDir = getConfigDir();
110
+ const statePath = join3(configDir, "listener-state.json");
111
+ await mkdir2(configDir, { recursive: true, mode: 448 });
112
+ const tmpPath = statePath + ".tmp";
113
+ try {
114
+ await writeFile2(tmpPath, JSON.stringify(state, null, 2));
115
+ await chmod2(tmpPath, 384);
116
+ await rename2(tmpPath, statePath);
117
+ } catch (err) {
118
+ try {
119
+ await unlink2(tmpPath);
120
+ } catch {
121
+ }
122
+ throw err;
123
+ }
75
124
  }
76
125
 
77
126
  // ../listener/dist/lib/reconcile.js
78
- import { basename, dirname } from "path";
79
- function reconcileRepos(configRepos, activeRepos, state, apiUrl) {
127
+ import { statSync, readdirSync } from "fs";
128
+ import { basename, dirname, join as join4 } from "path";
129
+ function reconcileRepos(configRepos, activeRepos, state, apiUrl, options) {
80
130
  if (activeRepos.length === 0) {
81
- return { updated: false, repos: configRepos, changes: [] };
131
+ return { updated: false, repos: configRepos, changes: [], addedRepos: [] };
82
132
  }
83
133
  const changes = [];
84
134
  let updated = false;
135
+ const addedRepos = [];
136
+ const usedPaths = new Set(configRepos.map((r) => r.localPath));
85
137
  const updatedRepos = configRepos.map((repo) => {
86
138
  if (repo.apiUrl && repo.apiUrl !== apiUrl) {
87
139
  return repo;
@@ -151,19 +203,78 @@ function reconcileRepos(configRepos, activeRepos, state, apiUrl) {
151
203
  }
152
204
  return repo;
153
205
  });
154
- return { updated, repos: updatedRepos, changes };
206
+ if (options?.autoDiscover) {
207
+ const configuredRepoIds = new Set(updatedRepos.map((r) => r.repoId));
208
+ const unmatchedActiveRepos = activeRepos.filter((ar) => !configuredRepoIds.has(ar.repoId));
209
+ if (unmatchedActiveRepos.length > 0) {
210
+ const parentDirs = /* @__PURE__ */ new Set();
211
+ for (const repo of updatedRepos) {
212
+ parentDirs.add(dirname(repo.localPath));
213
+ }
214
+ for (const activeRepo of unmatchedActiveRepos) {
215
+ const found = findLocalRepo(activeRepo, parentDirs, usedPaths);
216
+ if (found) {
217
+ const newRepo = {
218
+ repoId: activeRepo.repoId,
219
+ localPath: found,
220
+ apiUrl,
221
+ platform: activeRepo.platform,
222
+ externalId: activeRepo.externalId
223
+ };
224
+ updatedRepos.push(newRepo);
225
+ addedRepos.push(newRepo);
226
+ usedPaths.add(found);
227
+ configuredRepoIds.add(activeRepo.repoId);
228
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
229
+ state.repos[activeRepo.repoId] = {
230
+ ...state.repos[activeRepo.repoId],
231
+ lastSyncTimestamp: twentyFourHoursAgo,
232
+ lastSyncCommitSha: null
233
+ };
234
+ changes.push(`[self-heal] Auto-added ${activeRepo.fullName} from ${found}`);
235
+ updated = true;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ return { updated, repos: updatedRepos, changes, addedRepos };
241
+ }
242
+ function findLocalRepo(activeRepo, parentDirs, usedPaths) {
243
+ const nameParts = activeRepo.fullName.split("/");
244
+ const repoName = nameParts[nameParts.length - 1];
245
+ for (const parentDir of parentDirs) {
246
+ const candidate = join4(parentDir, repoName);
247
+ if (!usedPaths.has(candidate) && hasContextFolder(candidate)) {
248
+ return candidate;
249
+ }
250
+ }
251
+ return null;
252
+ }
253
+ function hasContextFolder(dirPath) {
254
+ try {
255
+ const contextPath = join4(dirPath, "repowise-context");
256
+ const s = statSync(contextPath);
257
+ if (!s.isDirectory())
258
+ return false;
259
+ const entries = readdirSync(contextPath);
260
+ return entries.length > 0;
261
+ } catch {
262
+ return false;
263
+ }
155
264
  }
156
265
  function migrateState(state, oldId, newId) {
157
266
  const oldState = state.repos[oldId];
158
267
  if (oldState) {
159
268
  const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
160
269
  state.repos[newId] = {
270
+ ...state.repos[newId],
161
271
  lastSyncTimestamp: twentyFourHoursAgo,
162
272
  lastSyncCommitSha: null
163
273
  };
164
274
  delete state.repos[oldId];
165
275
  } else {
166
276
  state.repos[newId] = {
277
+ ...state.repos[newId],
167
278
  lastSyncTimestamp: new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString(),
168
279
  lastSyncCommitSha: null
169
280
  };
@@ -172,10 +283,7 @@ function migrateState(state, oldId, newId) {
172
283
 
173
284
  // ../listener/dist/lib/auth.js
174
285
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod3 } from "fs/promises";
175
- import { homedir as homedir3 } from "os";
176
- import { join as join3 } from "path";
177
- var CONFIG_DIR3 = join3(homedir3(), ".repowise");
178
- var CREDENTIALS_PATH = join3(CONFIG_DIR3, "credentials.json");
286
+ import { join as join5 } from "path";
179
287
  function getTokenUrl(creds) {
180
288
  const cognito = creds?.cognito;
181
289
  const domain = process.env["REPOWISE_COGNITO_DOMAIN"] ?? cognito?.domain ?? "auth-repowise-dev";
@@ -214,7 +322,8 @@ async function refreshTokens(refreshToken, creds) {
214
322
  }
215
323
  async function getStoredCredentials() {
216
324
  try {
217
- const data = await readFile3(CREDENTIALS_PATH, "utf-8");
325
+ const credPath = join5(getConfigDir(), "credentials.json");
326
+ const data = await readFile3(credPath, "utf-8");
218
327
  return JSON.parse(data);
219
328
  } catch (err) {
220
329
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -224,9 +333,11 @@ async function getStoredCredentials() {
224
333
  }
225
334
  }
226
335
  async function storeCredentials(credentials) {
227
- await mkdir3(CONFIG_DIR3, { recursive: true, mode: 448 });
228
- await writeFile3(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2));
229
- await chmod3(CREDENTIALS_PATH, 384);
336
+ const dir = getConfigDir();
337
+ const credPath = join5(dir, "credentials.json");
338
+ await mkdir3(dir, { recursive: true, mode: 448 });
339
+ await writeFile3(credPath, JSON.stringify(credentials, null, 2));
340
+ await chmod3(credPath, 384);
230
341
  }
231
342
  async function getValidCredentials(options) {
232
343
  const creds = await getStoredCredentials();
@@ -377,15 +488,15 @@ function notifyContextUpdated(repoId, fileCount) {
377
488
  // ../listener/dist/context-fetcher.js
378
489
  import { execFile } from "child_process";
379
490
  import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
380
- import { dirname as dirname2, join as join5 } from "path";
491
+ import { dirname as dirname2, join as join7 } from "path";
381
492
  import { promisify } from "util";
382
493
 
383
494
  // ../listener/dist/file-writer.js
384
495
  import { access } from "fs/promises";
385
- import { join as join4 } from "path";
496
+ import { join as join6 } from "path";
386
497
  async function verifyContextFolder(localPath) {
387
498
  try {
388
- await access(join4(localPath, "repowise-context"));
499
+ await access(join6(localPath, "repowise-context"));
389
500
  return true;
390
501
  } catch {
391
502
  return false;
@@ -473,7 +584,7 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
473
584
  if (files.length === 0) {
474
585
  return { success: true, updatedFiles: [] };
475
586
  }
476
- const contextDir = join5(localPath, "repowise-context");
587
+ const contextDir = join7(localPath, "repowise-context");
477
588
  await mkdir4(contextDir, { recursive: true });
478
589
  const updatedFiles = [];
479
590
  for (const file of files) {
@@ -498,7 +609,7 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
498
609
  continue;
499
610
  }
500
611
  const content = await contentRes.text();
501
- const filePath = join5(contextDir, file.fileName);
612
+ const filePath = join7(contextDir, file.fileName);
502
613
  await mkdir4(dirname2(filePath), { recursive: true });
503
614
  await writeFile4(filePath, content, "utf-8");
504
615
  updatedFiles.push(file.fileName);
@@ -512,175 +623,819 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
512
623
  }
513
624
  }
514
625
 
515
- // ../listener/dist/main.js
516
- var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
517
- var PID_PATH = join6(homedir4(), ".repowise", "listener.pid");
518
- var running = false;
519
- var sleepResolve = null;
520
- async function writePidFile() {
521
- await mkdir5(join6(homedir4(), ".repowise"), { recursive: true });
522
- await writeFile5(PID_PATH, String(process.pid));
626
+ // ../listener/dist/process-manager.js
627
+ import { spawn } from "child_process";
628
+ import { openSync, closeSync } from "fs";
629
+ import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir5, unlink as unlink3 } from "fs/promises";
630
+ import { homedir as homedir2 } from "os";
631
+ import { join as join8 } from "path";
632
+ import { createRequire } from "module";
633
+ import { fileURLToPath } from "url";
634
+ function repowiseDir() {
635
+ return getConfigDir();
523
636
  }
524
- async function removePidFile() {
637
+ function pidPath() {
638
+ return join8(repowiseDir(), "listener.pid");
639
+ }
640
+ function logDirPath() {
641
+ return join8(repowiseDir(), "logs");
642
+ }
643
+ function resolveListenerCommand() {
525
644
  try {
526
- await unlink(PID_PATH);
645
+ const require2 = createRequire(import.meta.url);
646
+ const mainPath = require2.resolve("@repowise/listener/main");
647
+ return { script: mainPath, args: [] };
527
648
  } catch {
649
+ const bundlePath = fileURLToPath(import.meta.url);
650
+ return { script: bundlePath, args: ["__listener"] };
528
651
  }
529
652
  }
530
- async function handleStalePid() {
653
+ async function readPid() {
531
654
  try {
532
- const content = await readFile4(PID_PATH, "utf-8");
655
+ const content = await readFile4(pidPath(), "utf-8");
533
656
  const pid = parseInt(content.trim(), 10);
534
- if (!Number.isNaN(pid) && pid !== process.pid) {
535
- try {
536
- process.kill(pid, 0);
537
- console.error(`Listener already running (PID: ${pid}). Stop it first with \`repowise stop\`.`);
538
- process.exitCode = 1;
539
- return true;
540
- } catch {
541
- }
542
- }
543
- } catch {
657
+ return Number.isNaN(pid) ? null : pid;
658
+ } catch (err) {
659
+ if (err.code === "ENOENT")
660
+ return null;
661
+ throw err;
544
662
  }
545
- return false;
546
663
  }
547
- function stop() {
548
- running = false;
549
- if (sleepResolve) {
550
- sleepResolve();
551
- sleepResolve = null;
664
+ function isAlive(pid) {
665
+ try {
666
+ process.kill(pid, 0);
667
+ return true;
668
+ } catch {
669
+ return false;
552
670
  }
553
671
  }
554
- function sleep(ms) {
555
- return new Promise((resolve) => {
556
- sleepResolve = resolve;
557
- setTimeout(() => {
558
- sleepResolve = null;
559
- resolve();
560
- }, ms);
672
+ async function startBackground() {
673
+ if (await isRunning()) {
674
+ const pid2 = await readPid();
675
+ if (pid2)
676
+ return pid2;
677
+ }
678
+ const logDir2 = logDirPath();
679
+ await mkdir5(logDir2, { recursive: true });
680
+ const cmd = resolveListenerCommand();
681
+ const stdoutFd = openSync(join8(logDir2, "listener-stdout.log"), "a");
682
+ const stderrFd = openSync(join8(logDir2, "listener-stderr.log"), "a");
683
+ const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
684
+ detached: true,
685
+ stdio: ["ignore", stdoutFd, stderrFd],
686
+ cwd: homedir2(),
687
+ env: { ...process.env }
561
688
  });
689
+ child.unref();
690
+ closeSync(stdoutFd);
691
+ closeSync(stderrFd);
692
+ const pid = child.pid;
693
+ if (!pid)
694
+ throw new Error("Failed to spawn listener process");
695
+ await writeFile5(pidPath(), String(pid));
696
+ return pid;
562
697
  }
563
- function decodeEmailFromIdToken(idToken) {
698
+ async function stopProcess() {
699
+ const pid = await readPid();
700
+ if (pid === null)
701
+ return;
702
+ if (!isAlive(pid)) {
703
+ await removePidFile();
704
+ return;
705
+ }
564
706
  try {
565
- const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
566
- return typeof payload.email === "string" ? payload.email : null;
707
+ process.kill(pid, "SIGTERM");
567
708
  } catch {
568
- return null;
569
709
  }
570
- }
571
- async function processNotifications(notifications, state, repoLocalPaths, apiUrl) {
572
- let updateCount = 0;
573
- for (const notif of notifications) {
574
- const repoState = state.repos[notif.repoId];
575
- if (repoState && notif.createdAt <= repoState.lastSyncTimestamp) {
576
- continue;
577
- }
578
- if (notif.type === "sync.completed") {
579
- const localPath = repoLocalPaths.get(notif.repoId);
580
- if (localPath) {
581
- let result;
582
- if (apiUrl) {
583
- result = await fetchContextFromServer(notif.repoId, localPath, apiUrl);
584
- } else {
585
- result = await fetchContextUpdates(localPath);
586
- }
587
- if (result.success) {
588
- updateCount++;
589
- notifyContextUpdated(notif.repoId, result.updatedFiles.length);
590
- state.repos[notif.repoId] = {
591
- lastSyncTimestamp: notif.createdAt,
592
- lastSyncCommitSha: notif.commitSha
593
- };
594
- } else {
595
- console.error(`Download failed for repo ${notif.repoId} \u2014 will retry on next poll`);
596
- }
597
- } else {
598
- state.repos[notif.repoId] = {
599
- lastSyncTimestamp: notif.createdAt,
600
- lastSyncCommitSha: notif.commitSha
601
- };
602
- }
603
- } else {
604
- state.repos[notif.repoId] = {
605
- lastSyncTimestamp: notif.createdAt,
606
- lastSyncCommitSha: notif.commitSha
607
- };
710
+ const deadline = Date.now() + 5e3;
711
+ while (Date.now() < deadline && isAlive(pid)) {
712
+ await new Promise((r) => setTimeout(r, 200));
713
+ }
714
+ if (isAlive(pid)) {
715
+ try {
716
+ process.kill(pid, "SIGKILL");
717
+ } catch {
608
718
  }
609
719
  }
610
- return updateCount;
720
+ await removePidFile();
611
721
  }
612
- async function handleCatchUp(offlineState, pollClient, repoIds, state, repoLocalPaths, apiUrl) {
613
- if (!offlineState.offlineSince)
614
- return;
615
- const offlineDuration = Date.now() - new Date(offlineState.offlineSince).getTime();
616
- if (offlineDuration >= TWENTY_FOUR_HOURS_MS) {
617
- let syncCount = 0;
618
- for (const [repoId, localPath] of repoLocalPaths) {
619
- let result;
620
- if (apiUrl) {
621
- result = await fetchContextFromServer(repoId, localPath, apiUrl);
622
- } else {
623
- result = await fetchContextUpdates(localPath);
624
- }
625
- if (result.success)
626
- syncCount++;
627
- }
628
- notifyBackOnline(syncCount);
629
- } else {
630
- const sinceTimestamp = offlineState.offlineSince;
631
- const response = await pollClient.poll(repoIds, sinceTimestamp);
632
- const updateCount = await processNotifications(response.notifications, state, repoLocalPaths, apiUrl);
633
- await saveState(state);
634
- notifyBackOnline(updateCount);
722
+ async function isRunning() {
723
+ const pid = await readPid();
724
+ if (pid === null)
725
+ return false;
726
+ return isAlive(pid);
727
+ }
728
+ async function removePidFile() {
729
+ try {
730
+ await unlink3(pidPath());
731
+ } catch {
635
732
  }
636
733
  }
637
- async function startListener(options) {
638
- running = true;
639
- if (!options?.skipPidCheck) {
640
- const alreadyRunning = await handleStalePid();
641
- if (alreadyRunning)
642
- return;
734
+
735
+ // ../listener/dist/lib/auto-updater.js
736
+ import { execFile as execFile2 } from "child_process";
737
+ import { access as access2, constants, realpath } from "fs/promises";
738
+ import { dirname as dirname3, join as join9 } from "path";
739
+ import { promisify as promisify2 } from "util";
740
+ var execFileAsync2 = promisify2(execFile2);
741
+ async function installUpdate(currentVersion, packageName, targetVersion) {
742
+ if (process.env["CI"] === "true")
743
+ return { updated: false };
744
+ if (!/^\d+\.\d+\.\d+/.test(targetVersion))
745
+ return { updated: false };
746
+ if (!isNewer(targetVersion, currentVersion)) {
747
+ console.log(`[auto-update] ${targetVersion} is not newer than ${currentVersion} \u2014 skipping`);
748
+ return { updated: false };
749
+ }
750
+ const npmWrapper = join9(dirname3(process.execPath), "npm");
751
+ const npmScript = await realpath(npmWrapper);
752
+ const runNpm = (args) => execFileAsync2(process.execPath, [npmScript, ...args], { timeout: 6e4 });
753
+ try {
754
+ const { stdout: prefix } = await runNpm(["prefix", "-g"]);
755
+ const npmDir = join9(prefix.trim(), "lib", "node_modules");
756
+ const checkDir = process.platform === "win32" ? prefix.trim() : npmDir;
757
+ await access2(checkDir, constants.W_OK);
758
+ } catch (err) {
759
+ const msg = err instanceof Error ? err.message : String(err);
760
+ console.log(`[auto-update] npm global prefix not writable \u2014 skipping update (${msg})`);
761
+ return { updated: false };
643
762
  }
644
- await writePidFile();
645
- const config2 = await getListenerConfig();
646
- if (config2.repos.length === 0) {
647
- console.error("No repos configured. Add repos to ~/.repowise/config.json");
648
- process.exitCode = 1;
649
- return;
763
+ console.log(`[auto-update] Updating ${packageName} from ${currentVersion} to ${targetVersion}...`);
764
+ try {
765
+ await runNpm(["install", "-g", "--ignore-scripts", `${packageName}@${targetVersion}`]);
766
+ console.log(`[auto-update] Successfully updated to ${targetVersion}`);
767
+ return { updated: true, latestVersion: targetVersion };
768
+ } catch (err) {
769
+ const msg = err instanceof Error ? err.message : String(err);
770
+ console.error(`[auto-update] Installation failed: ${msg}`);
771
+ return { updated: false, error: msg };
650
772
  }
651
- const credentials = await getValidCredentials();
652
- if (!credentials) {
653
- console.error("Not logged in. Run `repowise login` first.");
654
- process.exitCode = 1;
655
- return;
773
+ }
774
+ function isNewer(a, b) {
775
+ const parseVer = (v) => {
776
+ const [main, pre] = v.split("-", 2);
777
+ const parts = main.split(".").map(Number);
778
+ return { parts, pre: pre ?? "" };
779
+ };
780
+ const va = parseVer(a);
781
+ const vb = parseVer(b);
782
+ for (let i = 0; i < Math.max(va.parts.length, vb.parts.length); i++) {
783
+ const na = va.parts[i] ?? 0;
784
+ const nb = vb.parts[i] ?? 0;
785
+ if (na > nb)
786
+ return true;
787
+ if (na < nb)
788
+ return false;
656
789
  }
657
- const state = await loadState();
658
- const groupMap = /* @__PURE__ */ new Map();
659
- for (const repo of config2.repos) {
660
- const apiUrl = repo.apiUrl ?? config2.defaultApiUrl;
661
- let group = groupMap.get(apiUrl);
662
- if (!group) {
663
- group = {
664
- apiUrl,
665
- pollClient: new PollClient(apiUrl),
666
- backoff: new BackoffCalculator(),
667
- repoIds: [],
668
- repoLocalPaths: /* @__PURE__ */ new Map(),
669
- offline: { isOffline: false, offlineSince: null, attemptCount: 0, nextRetryAt: 0 }
670
- };
671
- groupMap.set(apiUrl, group);
790
+ if (vb.pre && !va.pre)
791
+ return true;
792
+ if (va.pre && vb.pre)
793
+ return comparePrerelease(va.pre, vb.pre) > 0;
794
+ return false;
795
+ }
796
+ function comparePrerelease(a, b) {
797
+ const aParts = a.split(".");
798
+ const bParts = b.split(".");
799
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
800
+ const ap = aParts[i];
801
+ const bp = bParts[i];
802
+ if (ap === void 0)
803
+ return -1;
804
+ if (bp === void 0)
805
+ return 1;
806
+ const aNum = /^\d+$/.test(ap) ? parseInt(ap, 10) : NaN;
807
+ const bNum = /^\d+$/.test(bp) ? parseInt(bp, 10) : NaN;
808
+ if (!isNaN(aNum) && !isNaN(bNum)) {
809
+ if (aNum > bNum)
810
+ return 1;
811
+ if (aNum < bNum)
812
+ return -1;
813
+ } else {
814
+ if (ap > bp)
815
+ return 1;
816
+ if (ap < bp)
817
+ return -1;
672
818
  }
673
- group.repoIds.push(repo.repoId);
674
- group.repoLocalPaths.set(repo.repoId, repo.localPath);
675
819
  }
676
- const groups = Array.from(groupMap.values());
677
- const allRepoIds = config2.repos.map((r) => r.repoId);
820
+ return 0;
821
+ }
822
+
823
+ // ../listener/dist/lifecycle.js
824
+ import { unlink as unlink5 } from "fs/promises";
825
+ import { join as join11 } from "path";
826
+
827
+ // ../listener/dist/service-installer.js
828
+ import { execFile as execFile3 } from "child_process";
829
+ import { writeFile as writeFile6, mkdir as mkdir6, unlink as unlink4 } from "fs/promises";
830
+ import { homedir as homedir3 } from "os";
831
+ import { join as join10 } from "path";
832
+ var IS_STAGING = true ? false : false;
833
+ function exec(cmd, args) {
834
+ return new Promise((resolve, reject) => {
835
+ execFile3(cmd, args, (err, stdout) => {
836
+ if (err) {
837
+ reject(err);
838
+ return;
839
+ }
840
+ resolve(String(stdout ?? ""));
841
+ });
842
+ });
843
+ }
844
+ var PLIST_LABEL = IS_STAGING ? "com.repowise-staging.listener" : "com.repowise.listener";
845
+ function plistPath() {
846
+ return join10(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
847
+ }
848
+ function logDir() {
849
+ return join10(getConfigDir(), "logs");
850
+ }
851
+ function buildPlist() {
852
+ const cmd = resolveListenerCommand();
853
+ const logs = logDir();
854
+ const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${a}</string>`).join("\n");
855
+ return `<?xml version="1.0" encoding="UTF-8"?>
856
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
857
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
858
+ <plist version="1.0">
859
+ <dict>
860
+ <key>Label</key>
861
+ <string>${PLIST_LABEL}</string>
862
+ <key>ProgramArguments</key>
863
+ <array>
864
+ ${programArgs}
865
+ </array>
866
+ <key>RunAtLoad</key>
867
+ <true/>
868
+ <key>KeepAlive</key>
869
+ <true/>
870
+ <key>StandardOutPath</key>
871
+ <string>${join10(logs, "listener-stdout.log")}</string>
872
+ <key>StandardErrorPath</key>
873
+ <string>${join10(logs, "listener-stderr.log")}</string>
874
+ <key>ProcessType</key>
875
+ <string>Background</string>
876
+ </dict>
877
+ </plist>`;
878
+ }
879
+ async function darwinInstall() {
880
+ await mkdir6(logDir(), { recursive: true });
881
+ await mkdir6(join10(homedir3(), "Library", "LaunchAgents"), { recursive: true });
882
+ try {
883
+ await exec("launchctl", ["unload", plistPath()]);
884
+ } catch {
885
+ }
886
+ await writeFile6(plistPath(), buildPlist());
887
+ await exec("launchctl", ["load", plistPath()]);
888
+ }
889
+ async function darwinUninstall() {
890
+ try {
891
+ await exec("launchctl", ["unload", plistPath()]);
892
+ } catch {
893
+ }
894
+ try {
895
+ await unlink4(plistPath());
896
+ } catch {
897
+ }
898
+ }
899
+ async function darwinIsInstalled() {
900
+ try {
901
+ const stdout = await exec("launchctl", ["list"]);
902
+ return stdout.includes(PLIST_LABEL);
903
+ } catch {
904
+ return false;
905
+ }
906
+ }
907
+ var SYSTEMD_SERVICE = IS_STAGING ? "repowise-staging-listener" : "repowise-listener";
908
+ function unitPath() {
909
+ return join10(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
910
+ }
911
+ function buildUnit() {
912
+ const cmd = resolveListenerCommand();
913
+ const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
914
+ const logs = logDir();
915
+ return `[Unit]
916
+ Description=RepoWise Listener
917
+ After=network-online.target
918
+ Wants=network-online.target
919
+
920
+ [Service]
921
+ Type=simple
922
+ ExecStart=${execStart}
923
+ Restart=always
924
+ RestartSec=10
925
+ StandardOutput=append:${join10(logs, "listener-stdout.log")}
926
+ StandardError=append:${join10(logs, "listener-stderr.log")}
927
+
928
+ [Install]
929
+ WantedBy=default.target`;
930
+ }
931
+ async function linuxInstall() {
932
+ await mkdir6(logDir(), { recursive: true });
933
+ await mkdir6(join10(homedir3(), ".config", "systemd", "user"), { recursive: true });
934
+ await writeFile6(unitPath(), buildUnit());
935
+ await exec("systemctl", ["--user", "daemon-reload"]);
936
+ await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
937
+ await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
938
+ }
939
+ async function linuxUninstall() {
940
+ try {
941
+ await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
942
+ } catch {
943
+ }
944
+ try {
945
+ await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
946
+ } catch {
947
+ }
948
+ try {
949
+ await unlink4(unitPath());
950
+ } catch {
951
+ }
952
+ try {
953
+ await exec("systemctl", ["--user", "daemon-reload"]);
954
+ } catch {
955
+ }
956
+ }
957
+ async function linuxIsInstalled() {
958
+ try {
959
+ const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
960
+ return stdout.trim() === "enabled";
961
+ } catch {
962
+ return false;
963
+ }
964
+ }
965
+ var TASK_NAME = IS_STAGING ? "RepoWise Staging Listener" : "RepoWise Listener";
966
+ async function win32Install() {
967
+ await mkdir6(logDir(), { recursive: true });
968
+ const cmd = resolveListenerCommand();
969
+ const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
970
+ await exec("schtasks", [
971
+ "/create",
972
+ "/tn",
973
+ TASK_NAME,
974
+ "/tr",
975
+ taskCmd,
976
+ "/sc",
977
+ "onlogon",
978
+ "/ru",
979
+ process.env.USERNAME ?? "",
980
+ "/f"
981
+ ]);
982
+ await exec("schtasks", ["/run", "/tn", TASK_NAME]);
983
+ }
984
+ async function win32Uninstall() {
985
+ try {
986
+ await exec("schtasks", ["/end", "/tn", TASK_NAME]);
987
+ } catch {
988
+ }
989
+ try {
990
+ await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
991
+ } catch {
992
+ }
993
+ }
994
+ async function win32IsInstalled() {
995
+ try {
996
+ await exec("schtasks", ["/query", "/tn", TASK_NAME]);
997
+ return true;
998
+ } catch {
999
+ return false;
1000
+ }
1001
+ }
1002
+ async function darwinStop() {
1003
+ try {
1004
+ await exec("launchctl", ["unload", plistPath()]);
1005
+ } catch {
1006
+ }
1007
+ }
1008
+ async function linuxStop() {
1009
+ await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
1010
+ }
1011
+ async function win32Stop() {
1012
+ await exec("schtasks", ["/end", "/tn", TASK_NAME]);
1013
+ }
1014
+ async function darwinIsServiceRunning() {
1015
+ try {
1016
+ const stdout = await exec("launchctl", ["list", PLIST_LABEL]);
1017
+ const pidMatch = stdout.match(/"PID"\s*=\s*(\d+)/);
1018
+ return pidMatch !== null && parseInt(pidMatch[1], 10) > 0;
1019
+ } catch {
1020
+ return false;
1021
+ }
1022
+ }
1023
+ async function linuxIsServiceRunning() {
1024
+ try {
1025
+ const stdout = await exec("systemctl", ["--user", "is-active", SYSTEMD_SERVICE]);
1026
+ return stdout.trim() === "active";
1027
+ } catch {
1028
+ return false;
1029
+ }
1030
+ }
1031
+ async function win32IsServiceRunning() {
1032
+ try {
1033
+ const stdout = await exec("schtasks", ["/query", "/tn", TASK_NAME, "/fo", "CSV"]);
1034
+ return stdout.includes("Running");
1035
+ } catch {
1036
+ return false;
1037
+ }
1038
+ }
1039
+ async function stopService() {
1040
+ switch (process.platform) {
1041
+ case "darwin":
1042
+ await darwinStop();
1043
+ break;
1044
+ case "linux":
1045
+ await linuxStop();
1046
+ break;
1047
+ case "win32":
1048
+ await win32Stop();
1049
+ break;
1050
+ default:
1051
+ throw new Error(`Unsupported platform: ${process.platform}`);
1052
+ }
1053
+ }
1054
+ async function install() {
1055
+ switch (process.platform) {
1056
+ case "darwin":
1057
+ await darwinInstall();
1058
+ break;
1059
+ case "linux":
1060
+ await linuxInstall();
1061
+ break;
1062
+ case "win32":
1063
+ await win32Install();
1064
+ break;
1065
+ default:
1066
+ throw new Error(`Unsupported platform: ${process.platform}`);
1067
+ }
1068
+ }
1069
+ async function uninstall() {
1070
+ switch (process.platform) {
1071
+ case "darwin":
1072
+ await darwinUninstall();
1073
+ break;
1074
+ case "linux":
1075
+ await linuxUninstall();
1076
+ break;
1077
+ case "win32":
1078
+ await win32Uninstall();
1079
+ break;
1080
+ default:
1081
+ throw new Error(`Unsupported platform: ${process.platform}`);
1082
+ }
1083
+ }
1084
+ async function isInstalled() {
1085
+ switch (process.platform) {
1086
+ case "darwin":
1087
+ return darwinIsInstalled();
1088
+ case "linux":
1089
+ return linuxIsInstalled();
1090
+ case "win32":
1091
+ return win32IsInstalled();
1092
+ default:
1093
+ return false;
1094
+ }
1095
+ }
1096
+ async function isServiceRunning() {
1097
+ switch (process.platform) {
1098
+ case "darwin":
1099
+ return darwinIsServiceRunning();
1100
+ case "linux":
1101
+ return linuxIsServiceRunning();
1102
+ case "win32":
1103
+ return win32IsServiceRunning();
1104
+ default:
1105
+ return false;
1106
+ }
1107
+ }
1108
+
1109
+ // ../listener/dist/lifecycle.js
1110
+ async function getListenerStatus() {
1111
+ const pid = await readPid();
1112
+ if (pid !== null) {
1113
+ if (isAlive(pid)) {
1114
+ const serviceInstalled2 = await isInstalled();
1115
+ return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
1116
+ }
1117
+ try {
1118
+ await unlink5(join11(getConfigDir(), "listener.pid"));
1119
+ } catch {
1120
+ }
1121
+ }
1122
+ try {
1123
+ if (await isServiceRunning()) {
1124
+ return { running: true, method: "service", serviceInstalled: true };
1125
+ }
1126
+ } catch {
1127
+ }
1128
+ const serviceInstalled = await isInstalled();
1129
+ return { running: false, method: "none", serviceInstalled };
1130
+ }
1131
+ async function ensureListenerRunning() {
1132
+ const currentStatus = await getListenerStatus();
1133
+ if (currentStatus.running)
1134
+ return currentStatus;
1135
+ try {
1136
+ await install();
1137
+ await new Promise((r) => setTimeout(r, 2e3));
1138
+ const postInstall = await getListenerStatus();
1139
+ if (postInstall.running)
1140
+ return postInstall;
1141
+ } catch {
1142
+ }
1143
+ const pid = await startBackground();
1144
+ await new Promise((r) => setTimeout(r, 1e3));
1145
+ if (!isAlive(pid)) {
1146
+ return { running: false, method: "none", serviceInstalled: await isInstalled() };
1147
+ }
1148
+ return { running: true, method: "pid", pid, serviceInstalled: await isInstalled() };
1149
+ }
1150
+ async function stopListener() {
1151
+ try {
1152
+ await stopProcess();
1153
+ } catch {
1154
+ }
1155
+ try {
1156
+ await stopService();
1157
+ } catch {
1158
+ }
1159
+ }
1160
+
1161
+ // ../listener/dist/main.js
1162
+ var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
1163
+ var STALE_CHECK_COOLDOWN_MS = 60 * 60 * 1e3;
1164
+ var CRASH_LOOP_WINDOW_MS = 3e4;
1165
+ var CRASH_LOOP_THRESHOLD = 3;
1166
+ var running = false;
1167
+ var sleepResolve = null;
1168
+ var releaseLock = null;
1169
+ function stop() {
1170
+ running = false;
1171
+ if (sleepResolve) {
1172
+ sleepResolve();
1173
+ sleepResolve = null;
1174
+ }
1175
+ }
1176
+ function sleep(ms) {
1177
+ return new Promise((resolve) => {
1178
+ sleepResolve = resolve;
1179
+ setTimeout(() => {
1180
+ sleepResolve = null;
1181
+ resolve();
1182
+ }, ms);
1183
+ });
1184
+ }
1185
+ function decodeEmailFromIdToken(idToken) {
1186
+ try {
1187
+ const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
1188
+ return typeof payload.email === "string" ? payload.email : null;
1189
+ } catch {
1190
+ return null;
1191
+ }
1192
+ }
1193
+ async function processNotifications(notifications, state, repoLocalPaths, apiUrl) {
1194
+ let updateCount = 0;
1195
+ for (const notif of notifications) {
1196
+ const repoState = state.repos[notif.repoId];
1197
+ if (repoState && notif.createdAt <= repoState.lastSyncTimestamp) {
1198
+ continue;
1199
+ }
1200
+ if (notif.type === "sync.completed") {
1201
+ if (!notif.commitSha) {
1202
+ console.warn(`sync.completed for ${notif.repoId} has no commitSha`);
1203
+ }
1204
+ const localPath = repoLocalPaths.get(notif.repoId);
1205
+ if (localPath) {
1206
+ let result;
1207
+ if (apiUrl) {
1208
+ result = await fetchContextFromServer(notif.repoId, localPath, apiUrl);
1209
+ } else {
1210
+ result = await fetchContextUpdates(localPath);
1211
+ }
1212
+ if (result.success) {
1213
+ updateCount++;
1214
+ notifyContextUpdated(notif.repoId, result.updatedFiles.length);
1215
+ state.repos[notif.repoId] = {
1216
+ ...state.repos[notif.repoId],
1217
+ lastSyncTimestamp: notif.createdAt,
1218
+ lastSyncCommitSha: notif.commitSha
1219
+ };
1220
+ } else {
1221
+ console.error(`Download failed for repo ${notif.repoId} \u2014 will retry on next poll`);
1222
+ }
1223
+ } else {
1224
+ state.repos[notif.repoId] = {
1225
+ ...state.repos[notif.repoId],
1226
+ lastSyncTimestamp: notif.createdAt,
1227
+ lastSyncCommitSha: notif.commitSha
1228
+ };
1229
+ }
1230
+ } else {
1231
+ state.repos[notif.repoId] = {
1232
+ ...state.repos[notif.repoId],
1233
+ lastSyncTimestamp: notif.createdAt,
1234
+ lastSyncCommitSha: notif.commitSha
1235
+ };
1236
+ }
1237
+ }
1238
+ return updateCount;
1239
+ }
1240
+ async function handleCatchUp(offlineState, pollClient, repoIds, state, repoLocalPaths, apiUrl) {
1241
+ if (!offlineState.offlineSince)
1242
+ return;
1243
+ const offlineDuration = Date.now() - new Date(offlineState.offlineSince).getTime();
1244
+ if (offlineDuration >= TWENTY_FOUR_HOURS_MS) {
1245
+ let syncCount = 0;
1246
+ for (const [repoId, localPath] of repoLocalPaths) {
1247
+ let result;
1248
+ if (apiUrl) {
1249
+ result = await fetchContextFromServer(repoId, localPath, apiUrl);
1250
+ } else {
1251
+ result = await fetchContextUpdates(localPath);
1252
+ }
1253
+ if (result.success) {
1254
+ syncCount++;
1255
+ state.repos[repoId] = {
1256
+ ...state.repos[repoId],
1257
+ lastSyncTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
1258
+ lastSyncCommitSha: null
1259
+ // unknown from catch-up, but timestamp prevents re-download
1260
+ };
1261
+ }
1262
+ }
1263
+ await saveState(state);
1264
+ notifyBackOnline(syncCount);
1265
+ } else {
1266
+ const sinceTimestamp = offlineState.offlineSince;
1267
+ const response = await pollClient.poll(repoIds, sinceTimestamp);
1268
+ const updateCount = await processNotifications(response.notifications, state, repoLocalPaths, apiUrl);
1269
+ await saveState(state);
1270
+ notifyBackOnline(updateCount);
1271
+ }
1272
+ }
1273
+ async function checkStaleContext(repos, state, groups) {
1274
+ const now = Date.now();
1275
+ let dirty = false;
1276
+ for (const repo of repos) {
1277
+ const repoState = state.repos[repo.repoId];
1278
+ if (!repoState)
1279
+ continue;
1280
+ if (repoState.lastContextCheckAt) {
1281
+ const lastCheck = new Date(repoState.lastContextCheckAt).getTime();
1282
+ if (now - lastCheck < STALE_CHECK_COOLDOWN_MS)
1283
+ continue;
1284
+ }
1285
+ const apiUrl = repo.apiUrl;
1286
+ const group = groups.find((g) => g.apiUrl === apiUrl);
1287
+ if (group?.offline.isOffline)
1288
+ continue;
1289
+ const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
1290
+ const contextPath = join12(repo.localPath, "repowise-context");
1291
+ let isMissingOrEmpty = false;
1292
+ try {
1293
+ const s = statSync2(contextPath);
1294
+ if (!s.isDirectory()) {
1295
+ isMissingOrEmpty = true;
1296
+ } else {
1297
+ const entries = readdirSync2(contextPath);
1298
+ isMissingOrEmpty = entries.length === 0;
1299
+ }
1300
+ } catch {
1301
+ isMissingOrEmpty = true;
1302
+ }
1303
+ state.repos[repo.repoId] = {
1304
+ ...state.repos[repo.repoId],
1305
+ lastContextCheckAt: (/* @__PURE__ */ new Date()).toISOString()
1306
+ };
1307
+ dirty = true;
1308
+ if (isMissingOrEmpty && apiUrl) {
1309
+ console.log(`[self-heal] Context missing/empty for ${repo.repoId}, re-downloading...`);
1310
+ const result = await fetchContextFromServer(repo.repoId, repo.localPath, apiUrl);
1311
+ if (result.success && result.updatedFiles.length > 0) {
1312
+ console.log(`[self-heal] Re-downloaded ${result.updatedFiles.length} files for ${repo.repoId}`);
1313
+ notifyContextUpdated(repo.repoId, result.updatedFiles.length);
1314
+ }
1315
+ }
1316
+ }
1317
+ return dirty;
1318
+ }
1319
+ async function startListener() {
1320
+ running = true;
1321
+ const configDir = getConfigDir();
1322
+ await mkdir7(configDir, { recursive: true });
1323
+ const lockPath = join12(configDir, "listener.lock");
1324
+ await writeFile7(lockPath, "", { flag: "a" });
1325
+ let lockIsHeld = false;
1326
+ try {
1327
+ lockIsHeld = await lockfile2.check(lockPath, { stale: 3e4, realpath: false });
1328
+ } catch {
1329
+ }
1330
+ if (!lockIsHeld) {
1331
+ const existingPid = await readPid();
1332
+ if (existingPid && existingPid !== process.pid && isAlive(existingPid)) {
1333
+ try {
1334
+ process.kill(existingPid, "SIGTERM");
1335
+ await new Promise((r) => setTimeout(r, 1e3));
1336
+ } catch {
1337
+ }
1338
+ }
1339
+ }
1340
+ try {
1341
+ releaseLock = await lockfile2.lock(lockPath, { stale: 3e4, realpath: false });
1342
+ } catch {
1343
+ console.error(`Listener already running. Stop it first with \`${true ? "repowise" : "repowise"} stop\`.`);
1344
+ process.exitCode = 1;
1345
+ return;
1346
+ }
1347
+ const releaseLockAndExit = async () => {
1348
+ if (releaseLock) {
1349
+ try {
1350
+ await releaseLock();
1351
+ } catch {
1352
+ }
1353
+ releaseLock = null;
1354
+ }
1355
+ };
1356
+ let config2;
1357
+ try {
1358
+ config2 = await getListenerConfig();
1359
+ } catch (err) {
1360
+ console.error("Failed to load config:", err);
1361
+ await releaseLockAndExit();
1362
+ process.exitCode = 1;
1363
+ return;
1364
+ }
1365
+ if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
1366
+ console.error(`No repos configured. Add repos to ${join12(configDir, "config.json")}`);
1367
+ await releaseLockAndExit();
1368
+ process.exitCode = 1;
1369
+ return;
1370
+ }
1371
+ const credentials = await getValidCredentials();
1372
+ if (!credentials) {
1373
+ console.error("Not logged in. Run `repowise login` first.");
1374
+ await releaseLockAndExit();
1375
+ process.exitCode = 1;
1376
+ return;
1377
+ }
1378
+ let state;
1379
+ try {
1380
+ state = await loadState();
1381
+ } catch (err) {
1382
+ console.error("Failed to load state:", err);
1383
+ await releaseLockAndExit();
1384
+ process.exitCode = 1;
1385
+ return;
1386
+ }
1387
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1388
+ const nowMs = Date.now();
1389
+ if (state.lastBootAt) {
1390
+ const lastBootMs = new Date(state.lastBootAt).getTime();
1391
+ if (nowMs - lastBootMs < CRASH_LOOP_WINDOW_MS) {
1392
+ state.crashCount = (state.crashCount ?? 0) + 1;
1393
+ } else {
1394
+ state.crashCount = 0;
1395
+ }
1396
+ }
1397
+ state.lastBootAt = nowIso;
1398
+ const crashLoopDetected = (state.crashCount ?? 0) >= CRASH_LOOP_THRESHOLD;
1399
+ if (crashLoopDetected) {
1400
+ console.warn(`[self-heal] Crash-loop detected (${state.crashCount} rapid restarts). Auto-update disabled until stable.`);
1401
+ }
1402
+ const groupMap = /* @__PURE__ */ new Map();
1403
+ for (const repo of config2.repos) {
1404
+ const apiUrl = repo.apiUrl ?? config2.defaultApiUrl;
1405
+ let group = groupMap.get(apiUrl);
1406
+ if (!group) {
1407
+ group = {
1408
+ apiUrl,
1409
+ pollClient: new PollClient(apiUrl),
1410
+ backoff: new BackoffCalculator(),
1411
+ repoIds: [],
1412
+ repoLocalPaths: /* @__PURE__ */ new Map(),
1413
+ offline: { isOffline: false, offlineSince: null, attemptCount: 0, nextRetryAt: 0 }
1414
+ };
1415
+ groupMap.set(apiUrl, group);
1416
+ }
1417
+ group.repoIds.push(repo.repoId);
1418
+ group.repoLocalPaths.set(repo.repoId, repo.localPath);
1419
+ }
1420
+ const groups = Array.from(groupMap.values());
1421
+ const allRepoIds = config2.repos.map((r) => r.repoId);
678
1422
  const now = (/* @__PURE__ */ new Date()).toISOString();
679
1423
  for (const repoId of allRepoIds) {
680
1424
  if (!state.repos[repoId]) {
681
1425
  state.repos[repoId] = { lastSyncTimestamp: now, lastSyncCommitSha: null };
682
1426
  }
683
1427
  }
1428
+ await saveState(state);
1429
+ const packageName = true ? "repowise" : "repowise";
1430
+ let currentVersion = "";
1431
+ try {
1432
+ const selfDir = dirname4(fileURLToPath2(import.meta.url));
1433
+ const pkgJsonPath = join12(selfDir, "..", "..", "package.json");
1434
+ const pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf-8"));
1435
+ currentVersion = pkgJson.version;
1436
+ } catch (err) {
1437
+ console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
1438
+ }
684
1439
  let pollIntervalMs = 5e3;
685
1440
  let pollCycleCount = 0;
686
1441
  const RECONCILE_EVERY_N_CYCLES = 60;
@@ -700,10 +1455,24 @@ async function startListener(options) {
700
1455
  console.log("Shutting down...");
701
1456
  stop();
702
1457
  await saveState(state);
703
- await removePidFile();
1458
+ if (releaseLock) {
1459
+ try {
1460
+ await releaseLock();
1461
+ } catch {
1462
+ }
1463
+ releaseLock = null;
1464
+ }
704
1465
  };
705
- process.on("SIGTERM", () => void shutdown());
706
- process.on("SIGINT", () => void shutdown());
1466
+ process.on("SIGTERM", () => {
1467
+ shutdown().catch((err) => {
1468
+ console.error("Shutdown error:", err);
1469
+ }).finally(() => process.exit(0));
1470
+ });
1471
+ process.on("SIGINT", () => {
1472
+ shutdown().catch((err) => {
1473
+ console.error("Shutdown error:", err);
1474
+ }).finally(() => process.exit(0));
1475
+ });
707
1476
  while (running) {
708
1477
  let anyAuthError = null;
709
1478
  let authErrorGroup = null;
@@ -711,6 +1480,7 @@ async function startListener(options) {
711
1480
  let connectionLostNotified = false;
712
1481
  pollCycleCount++;
713
1482
  const shouldReconcile = pollCycleCount % RECONCILE_EVERY_N_CYCLES === 0;
1483
+ let latestCliVersion;
714
1484
  for (const group of groups) {
715
1485
  if (!running)
716
1486
  break;
@@ -725,9 +1495,14 @@ async function startListener(options) {
725
1495
  const response = await group.pollClient.poll(group.repoIds, sinceTimestamp, {
726
1496
  includeActiveRepos: shouldReconcile
727
1497
  });
1498
+ if (response.latestCliVersion) {
1499
+ latestCliVersion = response.latestCliVersion;
1500
+ }
728
1501
  if (response.activeRepos && response.activeRepos.length > 0) {
729
1502
  try {
730
- const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl);
1503
+ const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl, {
1504
+ autoDiscover: config2.autoDiscoverRepos
1505
+ });
731
1506
  if (result.updated) {
732
1507
  config2.repos = result.repos;
733
1508
  await saveListenerConfig(config2);
@@ -742,6 +1517,19 @@ async function startListener(options) {
742
1517
  for (const change of result.changes) {
743
1518
  console.log(`[reconcile] ${change}`);
744
1519
  }
1520
+ if (result.addedRepos.length > 0) {
1521
+ for (const addedRepo of result.addedRepos) {
1522
+ const apiUrl = addedRepo.apiUrl ?? config2.defaultApiUrl;
1523
+ try {
1524
+ const fetchResult = await fetchContextFromServer(addedRepo.repoId, addedRepo.localPath, apiUrl);
1525
+ if (fetchResult.success) {
1526
+ console.log(`[self-heal] Downloaded ${fetchResult.updatedFiles.length} context files for ${addedRepo.repoId}`);
1527
+ }
1528
+ } catch (fetchErr) {
1529
+ console.warn(`[self-heal] Context download failed for ${addedRepo.repoId}:`, fetchErr instanceof Error ? fetchErr.message : String(fetchErr));
1530
+ }
1531
+ }
1532
+ }
745
1533
  }
746
1534
  } catch (reconcileErr) {
747
1535
  console.warn("Repo reconciliation failed \u2014 will retry next cycle", reconcileErr instanceof Error ? reconcileErr.message : String(reconcileErr));
@@ -783,6 +1571,49 @@ async function startListener(options) {
783
1571
  console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay2 / 1e3)}s`);
784
1572
  }
785
1573
  }
1574
+ if (shouldReconcile) {
1575
+ try {
1576
+ const staleDirty = await checkStaleContext(config2.repos, state, groups);
1577
+ if (staleDirty) {
1578
+ await saveState(state);
1579
+ }
1580
+ } catch (err) {
1581
+ console.warn("[self-heal] Stale context check failed:", err instanceof Error ? err.message : String(err));
1582
+ }
1583
+ }
1584
+ if (latestCliVersion && currentVersion && !config2.noAutoUpdate && (state.crashCount ?? 0) < CRASH_LOOP_THRESHOLD) {
1585
+ if (latestCliVersion !== state.lastUpdateTargetVersion) {
1586
+ state.updateFailCount = 0;
1587
+ state.lastUpdateAttemptAt = void 0;
1588
+ state.lastUpdateTargetVersion = latestCliVersion;
1589
+ }
1590
+ const backoffMs = Math.min(5 * 60 * 1e3 * Math.pow(2, state.updateFailCount ?? 0), 60 * 60 * 1e3);
1591
+ const lastAttempt = state.lastUpdateAttemptAt ? new Date(state.lastUpdateAttemptAt).getTime() : 0;
1592
+ if (Date.now() - lastAttempt >= backoffMs) {
1593
+ const listenerStatus = await getListenerStatus();
1594
+ state.lastUpdateAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
1595
+ const updateResult = await installUpdate(currentVersion, packageName, latestCliVersion);
1596
+ if (updateResult.updated) {
1597
+ state.updateFailCount = 0;
1598
+ await saveState(state);
1599
+ if (listenerStatus.serviceInstalled) {
1600
+ console.log("[auto-update] Restarting with new version...");
1601
+ await shutdown();
1602
+ process.exit(0);
1603
+ } else {
1604
+ console.log("[auto-update] Spawning new listener with updated version...");
1605
+ await shutdown();
1606
+ await startBackground();
1607
+ process.exit(0);
1608
+ }
1609
+ } else if (updateResult.error) {
1610
+ state.updateFailCount = (state.updateFailCount ?? 0) + 1;
1611
+ await saveState(state);
1612
+ } else {
1613
+ await saveState(state);
1614
+ }
1615
+ }
1616
+ }
786
1617
  if (anyAuthError) {
787
1618
  let recovered = false;
788
1619
  for (let attempt = 1; attempt <= 3; attempt++) {
@@ -812,8 +1643,19 @@ async function startListener(options) {
812
1643
  } catch {
813
1644
  }
814
1645
  }
1646
+ const credentialsPath = join12(getConfigDir(), "credentials.json");
1647
+ let credentialsChanged = false;
1648
+ let watcher = null;
1649
+ try {
1650
+ const fs = await import("fs");
1651
+ watcher = fs.watch(credentialsPath, () => {
1652
+ credentialsChanged = true;
1653
+ });
1654
+ } catch {
1655
+ }
815
1656
  while (running) {
816
- await sleep(5 * 60 * 1e3);
1657
+ await sleep(credentialsChanged ? 0 : 1e4);
1658
+ credentialsChanged = false;
817
1659
  if (!running)
818
1660
  break;
819
1661
  const fresh = await getValidCredentials({ forceRefresh: true });
@@ -822,452 +1664,170 @@ async function startListener(options) {
822
1664
  break;
823
1665
  }
824
1666
  }
1667
+ if (watcher)
1668
+ watcher.close();
825
1669
  continue;
826
- }
827
- pollIntervalMs = minPollInterval;
828
- await sleep(pollIntervalMs);
829
- }
830
- await removePidFile();
831
- }
832
- var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
833
- if (isDirectRun) {
834
- startListener().catch((err) => {
835
- console.error("Listener fatal error:", err);
836
- process.exitCode = 1;
837
- });
838
- }
839
-
840
- // src/lib/env.ts
841
- var staging = false;
842
- function setStagingMode(value) {
843
- staging = value;
844
- }
845
- function isStagingMode() {
846
- return staging;
847
- }
848
- var PRODUCTION = {
849
- apiUrl: "https://api.repowise.ai",
850
- cognitoDomain: "auth.repowise.ai",
851
- cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
852
- cognitoRegion: "us-east-1",
853
- customDomain: true
854
- };
855
- var STAGING = {
856
- apiUrl: "https://staging-api.repowise.ai",
857
- cognitoDomain: "auth-staging.repowise.ai",
858
- cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
859
- cognitoRegion: "us-east-1",
860
- customDomain: true
861
- };
862
- function getEnvConfig() {
863
- return staging ? STAGING : PRODUCTION;
864
- }
865
-
866
- // src/lib/welcome.ts
867
- import chalk from "chalk";
868
-
869
- // src/lib/config.ts
870
- import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
871
- import { homedir as homedir5 } from "os";
872
- import { join as join7 } from "path";
873
- var CONFIG_DIR4 = join7(homedir5(), ".repowise");
874
- var CONFIG_PATH2 = join7(CONFIG_DIR4, "config.json");
875
- async function getConfig() {
876
- try {
877
- const data = await readFile5(CONFIG_PATH2, "utf-8");
878
- return JSON.parse(data);
879
- } catch {
880
- return {};
881
- }
882
- }
883
- async function saveConfig(config2) {
884
- await mkdir6(CONFIG_DIR4, { recursive: true });
885
- await writeFile6(CONFIG_PATH2, JSON.stringify(config2, null, 2));
886
- }
887
-
888
- // src/lib/welcome.ts
889
- var W = 41;
890
- function row(styled, visible) {
891
- return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
892
- }
893
- async function showWelcome(currentVersion) {
894
- try {
895
- const config2 = await getConfig();
896
- if (config2.lastSeenVersion === currentVersion) return;
897
- const isUpgrade = !!config2.lastSeenVersion;
898
- const tag = isUpgrade ? "updated" : "installed";
899
- const titleText = `RepoWise v${currentVersion}`;
900
- const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
901
- const titleVisible = 3 + titleText.length + 3 + tag.length;
902
- const border = "\u2500".repeat(W);
903
- console.log("");
904
- console.log(chalk.cyan(` \u256D${border}\u256E`));
905
- console.log(row("", 0));
906
- console.log(row(titleStyled, titleVisible));
907
- console.log(row("", 0));
908
- console.log(row(" " + chalk.dim("Get started:"), 15));
909
- console.log(row(" $ " + chalk.bold("repowise create"), 22));
910
- console.log(row("", 0));
911
- console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
912
- console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
913
- console.log(row("", 0));
914
- console.log(chalk.cyan(` \u2570${border}\u256F`));
915
- console.log("");
916
- await saveConfig({ ...config2, lastSeenVersion: currentVersion });
917
- } catch {
918
- }
919
- }
920
-
921
- // src/commands/create.ts
922
- import { execSync } from "child_process";
923
- import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
924
- import { dirname as dirname4, join as join13 } from "path";
925
-
926
- // ../listener/dist/process-manager.js
927
- import { spawn } from "child_process";
928
- import { openSync, closeSync } from "fs";
929
- import { readFile as readFile6, writeFile as writeFile7, mkdir as mkdir7, unlink as unlink2 } from "fs/promises";
930
- import { homedir as homedir6 } from "os";
931
- import { join as join8 } from "path";
932
- import { createRequire } from "module";
933
- import { fileURLToPath } from "url";
934
- var REPOWISE_DIR = join8(homedir6(), ".repowise");
935
- var PID_PATH2 = join8(REPOWISE_DIR, "listener.pid");
936
- var LOG_DIR = join8(REPOWISE_DIR, "logs");
937
- function resolveListenerCommand() {
938
- try {
939
- const require2 = createRequire(import.meta.url);
940
- const mainPath = require2.resolve("@repowise/listener/main");
941
- return { script: mainPath, args: [] };
942
- } catch {
943
- const bundlePath = fileURLToPath(import.meta.url);
944
- return { script: bundlePath, args: ["__listener"] };
945
- }
946
- }
947
- async function readPid() {
948
- try {
949
- const content = await readFile6(PID_PATH2, "utf-8");
950
- const pid = parseInt(content.trim(), 10);
951
- return Number.isNaN(pid) ? null : pid;
952
- } catch (err) {
953
- if (err.code === "ENOENT")
954
- return null;
955
- throw err;
956
- }
957
- }
958
- function isAlive(pid) {
959
- try {
960
- process.kill(pid, 0);
961
- return true;
962
- } catch {
963
- return false;
964
- }
965
- }
966
- async function startBackground() {
967
- await mkdir7(LOG_DIR, { recursive: true });
968
- const cmd = resolveListenerCommand();
969
- const stdoutFd = openSync(join8(LOG_DIR, "listener-stdout.log"), "a");
970
- const stderrFd = openSync(join8(LOG_DIR, "listener-stderr.log"), "a");
971
- const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
972
- detached: true,
973
- stdio: ["ignore", stdoutFd, stderrFd],
974
- cwd: homedir6(),
975
- env: { ...process.env }
976
- });
977
- child.unref();
978
- closeSync(stdoutFd);
979
- closeSync(stderrFd);
980
- const pid = child.pid;
981
- if (!pid)
982
- throw new Error("Failed to spawn listener process");
983
- await writeFile7(PID_PATH2, String(pid));
984
- return pid;
985
- }
986
- async function stopProcess() {
987
- const pid = await readPid();
988
- if (pid === null)
989
- return;
990
- if (!isAlive(pid)) {
991
- await removePidFile2();
992
- return;
993
- }
994
- try {
995
- process.kill(pid, "SIGTERM");
996
- } catch {
997
- }
998
- const deadline = Date.now() + 5e3;
999
- while (Date.now() < deadline && isAlive(pid)) {
1000
- await new Promise((r) => setTimeout(r, 200));
1670
+ }
1671
+ pollIntervalMs = minPollInterval;
1672
+ await sleep(pollIntervalMs);
1001
1673
  }
1002
- if (isAlive(pid)) {
1674
+ if (releaseLock) {
1003
1675
  try {
1004
- process.kill(pid, "SIGKILL");
1676
+ await releaseLock();
1005
1677
  } catch {
1006
1678
  }
1007
- }
1008
- await removePidFile2();
1009
- }
1010
- async function isRunning() {
1011
- const pid = await readPid();
1012
- if (pid === null)
1013
- return false;
1014
- return isAlive(pid);
1015
- }
1016
- async function getStatus() {
1017
- const pid = await readPid();
1018
- if (pid === null)
1019
- return { running: false, pid: null };
1020
- const alive = isAlive(pid);
1021
- return { running: alive, pid: alive ? pid : null };
1022
- }
1023
- async function removePidFile2() {
1024
- try {
1025
- await unlink2(PID_PATH2);
1026
- } catch {
1027
- }
1028
- }
1029
-
1030
- // ../listener/dist/service-installer.js
1031
- import { execFile as execFile2 } from "child_process";
1032
- import { writeFile as writeFile8, mkdir as mkdir8, unlink as unlink3 } from "fs/promises";
1033
- import { homedir as homedir7 } from "os";
1034
- import { join as join9 } from "path";
1035
- import { createRequire as createRequire2 } from "module";
1036
- import { fileURLToPath as fileURLToPath2 } from "url";
1037
- function resolveListenerCommand2() {
1038
- try {
1039
- const require2 = createRequire2(import.meta.url);
1040
- const mainPath = require2.resolve("@repowise/listener/main");
1041
- return { script: mainPath, args: [] };
1042
- } catch {
1043
- const bundlePath = fileURLToPath2(import.meta.url);
1044
- return { script: bundlePath, args: ["__listener"] };
1679
+ releaseLock = null;
1045
1680
  }
1046
1681
  }
1047
- function exec(cmd, args) {
1048
- return new Promise((resolve, reject) => {
1049
- execFile2(cmd, args, (err, stdout) => {
1050
- if (err) {
1051
- reject(err);
1052
- return;
1053
- }
1054
- resolve(String(stdout ?? ""));
1055
- });
1682
+ var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
1683
+ if (isDirectRun) {
1684
+ startListener().catch((err) => {
1685
+ console.error("Listener fatal error:", err);
1686
+ process.exitCode = 1;
1056
1687
  });
1057
1688
  }
1058
- var PLIST_LABEL = "com.repowise.listener";
1059
- function plistPath() {
1060
- return join9(homedir7(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
1061
- }
1062
- function logDir() {
1063
- return join9(homedir7(), ".repowise", "logs");
1064
- }
1065
- function buildPlist() {
1066
- const cmd = resolveListenerCommand2();
1067
- const logs = logDir();
1068
- const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${a}</string>`).join("\n");
1069
- return `<?xml version="1.0" encoding="UTF-8"?>
1070
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
1071
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1072
- <plist version="1.0">
1073
- <dict>
1074
- <key>Label</key>
1075
- <string>${PLIST_LABEL}</string>
1076
- <key>ProgramArguments</key>
1077
- <array>
1078
- ${programArgs}
1079
- </array>
1080
- <key>RunAtLoad</key>
1081
- <true/>
1082
- <key>KeepAlive</key>
1083
- <true/>
1084
- <key>StandardOutPath</key>
1085
- <string>${join9(logs, "listener-stdout.log")}</string>
1086
- <key>StandardErrorPath</key>
1087
- <string>${join9(logs, "listener-stderr.log")}</string>
1088
- <key>ProcessType</key>
1089
- <string>Background</string>
1090
- </dict>
1091
- </plist>`;
1092
- }
1093
- async function darwinInstall() {
1094
- await mkdir8(logDir(), { recursive: true });
1095
- await mkdir8(join9(homedir7(), "Library", "LaunchAgents"), { recursive: true });
1096
- try {
1097
- await exec("launchctl", ["unload", plistPath()]);
1098
- } catch {
1099
- }
1100
- await writeFile8(plistPath(), buildPlist());
1101
- await exec("launchctl", ["load", plistPath()]);
1102
- }
1103
- async function darwinUninstall() {
1104
- try {
1105
- await exec("launchctl", ["unload", plistPath()]);
1106
- } catch {
1107
- }
1108
- try {
1109
- await unlink3(plistPath());
1110
- } catch {
1111
- }
1689
+
1690
+ // src/lib/env.ts
1691
+ import { homedir as homedir4 } from "os";
1692
+ import { join as join13 } from "path";
1693
+ var IS_STAGING2 = true ? false : false;
1694
+ var PRODUCTION = {
1695
+ apiUrl: "https://api.repowise.ai",
1696
+ cognitoDomain: "auth.repowise.ai",
1697
+ cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
1698
+ cognitoRegion: "us-east-1",
1699
+ customDomain: true
1700
+ };
1701
+ var STAGING = {
1702
+ apiUrl: "https://staging-api.repowise.ai",
1703
+ cognitoDomain: "auth-staging.repowise.ai",
1704
+ cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
1705
+ cognitoRegion: "us-east-1",
1706
+ customDomain: true
1707
+ };
1708
+ function getEnvConfig() {
1709
+ return IS_STAGING2 ? STAGING : PRODUCTION;
1112
1710
  }
1113
- async function darwinIsInstalled() {
1114
- try {
1115
- const stdout = await exec("launchctl", ["list"]);
1116
- return stdout.includes(PLIST_LABEL);
1117
- } catch {
1118
- return false;
1119
- }
1711
+ function getConfigDir2() {
1712
+ return join13(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
1120
1713
  }
1121
- var SYSTEMD_SERVICE = "repowise-listener";
1122
- function unitPath() {
1123
- return join9(homedir7(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
1714
+ function getPackageName() {
1715
+ return true ? "repowise" : "repowise";
1124
1716
  }
1125
- function buildUnit() {
1126
- const cmd = resolveListenerCommand2();
1127
- const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
1128
- const logs = logDir();
1129
- return `[Unit]
1130
- Description=RepoWise Listener
1131
- After=network-online.target
1132
- Wants=network-online.target
1133
1717
 
1134
- [Service]
1135
- Type=simple
1136
- ExecStart=${execStart}
1137
- Restart=on-failure
1138
- RestartSec=10
1139
- StandardOutput=append:${join9(logs, "listener-stdout.log")}
1140
- StandardError=append:${join9(logs, "listener-stderr.log")}
1718
+ // src/lib/welcome.ts
1719
+ import chalk from "chalk";
1141
1720
 
1142
- [Install]
1143
- WantedBy=default.target`;
1144
- }
1145
- async function linuxInstall() {
1146
- await mkdir8(logDir(), { recursive: true });
1147
- await mkdir8(join9(homedir7(), ".config", "systemd", "user"), { recursive: true });
1148
- await writeFile8(unitPath(), buildUnit());
1149
- await exec("systemctl", ["--user", "daemon-reload"]);
1150
- await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
1151
- await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
1152
- }
1153
- async function linuxUninstall() {
1154
- try {
1155
- await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
1156
- } catch {
1157
- }
1158
- try {
1159
- await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
1160
- } catch {
1161
- }
1162
- try {
1163
- await unlink3(unitPath());
1164
- } catch {
1165
- }
1721
+ // src/lib/config.ts
1722
+ import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8, rename as rename3, unlink as unlink6 } from "fs/promises";
1723
+ import { join as join14 } from "path";
1724
+ import lockfile3 from "proper-lockfile";
1725
+ async function getConfig() {
1166
1726
  try {
1167
- await exec("systemctl", ["--user", "daemon-reload"]);
1727
+ const data = await readFile6(join14(getConfigDir2(), "config.json"), "utf-8");
1728
+ return JSON.parse(data);
1168
1729
  } catch {
1730
+ return {};
1169
1731
  }
1170
1732
  }
1171
- async function linuxIsInstalled() {
1733
+ async function saveConfig(config2) {
1734
+ const dir = getConfigDir2();
1735
+ const path = join14(dir, "config.json");
1736
+ await mkdir8(dir, { recursive: true });
1737
+ const tmpPath = path + ".tmp";
1172
1738
  try {
1173
- const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
1174
- return stdout.trim() === "enabled";
1175
- } catch {
1176
- return false;
1739
+ await writeFile8(tmpPath, JSON.stringify(config2, null, 2));
1740
+ await rename3(tmpPath, path);
1741
+ } catch (err) {
1742
+ try {
1743
+ await unlink6(tmpPath);
1744
+ } catch {
1745
+ }
1746
+ throw err;
1177
1747
  }
1178
1748
  }
1179
- var TASK_NAME = "RepoWise Listener";
1180
- async function win32Install() {
1181
- await mkdir8(logDir(), { recursive: true });
1182
- const cmd = resolveListenerCommand2();
1183
- const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
1184
- await exec("schtasks", [
1185
- "/create",
1186
- "/tn",
1187
- TASK_NAME,
1188
- "/tr",
1189
- taskCmd,
1190
- "/sc",
1191
- "onlogon",
1192
- "/ru",
1193
- process.env.USERNAME ?? "",
1194
- "/f"
1195
- ]);
1196
- await exec("schtasks", ["/run", "/tn", TASK_NAME]);
1197
- }
1198
- async function win32Uninstall() {
1199
- try {
1200
- await exec("schtasks", ["/end", "/tn", TASK_NAME]);
1201
- } catch {
1202
- }
1749
+ async function mergeAndSaveConfig(updates) {
1750
+ const dir = getConfigDir2();
1751
+ const path = join14(dir, "config.json");
1752
+ await mkdir8(dir, { recursive: true });
1203
1753
  try {
1204
- await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
1754
+ await writeFile8(path, "", { flag: "a" });
1205
1755
  } catch {
1206
1756
  }
1207
- }
1208
- async function win32IsInstalled() {
1757
+ let release = null;
1209
1758
  try {
1210
- await exec("schtasks", ["/query", "/tn", TASK_NAME]);
1211
- return true;
1212
- } catch {
1213
- return false;
1214
- }
1215
- }
1216
- async function install() {
1217
- switch (process.platform) {
1218
- case "darwin":
1219
- await darwinInstall();
1220
- break;
1221
- case "linux":
1222
- await linuxInstall();
1223
- break;
1224
- case "win32":
1225
- await win32Install();
1226
- break;
1227
- default:
1228
- throw new Error(`Unsupported platform: ${process.platform}`);
1759
+ release = await lockfile3.lock(path, { stale: 1e4, retries: 3, realpath: false });
1760
+ let raw = {};
1761
+ try {
1762
+ raw = JSON.parse(await readFile6(path, "utf-8"));
1763
+ } catch {
1764
+ }
1765
+ const merged = { ...raw, ...updates };
1766
+ if (updates.repos) {
1767
+ const existingRepos = Array.isArray(raw["repos"]) ? raw["repos"] : [];
1768
+ const updatedRepoIds = new Set(updates.repos.map((r) => r.repoId));
1769
+ merged.repos = [
1770
+ ...existingRepos.filter((r) => !updatedRepoIds.has(r.repoId)),
1771
+ ...updates.repos
1772
+ ];
1773
+ }
1774
+ await saveConfig(merged);
1775
+ } finally {
1776
+ if (release) {
1777
+ try {
1778
+ await release();
1779
+ } catch {
1780
+ }
1781
+ }
1229
1782
  }
1230
1783
  }
1231
- async function uninstall() {
1232
- switch (process.platform) {
1233
- case "darwin":
1234
- await darwinUninstall();
1235
- break;
1236
- case "linux":
1237
- await linuxUninstall();
1238
- break;
1239
- case "win32":
1240
- await win32Uninstall();
1241
- break;
1242
- default:
1243
- throw new Error(`Unsupported platform: ${process.platform}`);
1244
- }
1784
+
1785
+ // src/lib/welcome.ts
1786
+ var W = 41;
1787
+ function row(styled, visible) {
1788
+ return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
1245
1789
  }
1246
- async function isInstalled() {
1247
- switch (process.platform) {
1248
- case "darwin":
1249
- return darwinIsInstalled();
1250
- case "linux":
1251
- return linuxIsInstalled();
1252
- case "win32":
1253
- return win32IsInstalled();
1254
- default:
1255
- return false;
1790
+ async function showWelcome(currentVersion) {
1791
+ try {
1792
+ const config2 = await getConfig();
1793
+ if (config2.lastSeenVersion === currentVersion) {
1794
+ return;
1795
+ }
1796
+ const isUpgrade = !!config2.lastSeenVersion;
1797
+ const tag = isUpgrade ? "updated" : "installed";
1798
+ const titleText = `RepoWise v${currentVersion}`;
1799
+ const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
1800
+ const titleVisible = 3 + titleText.length + 3 + tag.length;
1801
+ const border = "\u2500".repeat(W);
1802
+ console.log("");
1803
+ console.log(chalk.cyan(` \u256D${border}\u256E`));
1804
+ console.log(row("", 0));
1805
+ console.log(row(titleStyled, titleVisible));
1806
+ console.log(row("", 0));
1807
+ console.log(row(" " + chalk.dim("Get started:"), 15));
1808
+ console.log(row(" $ " + chalk.bold("repowise create"), 22));
1809
+ console.log(row("", 0));
1810
+ console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
1811
+ console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
1812
+ console.log(row("", 0));
1813
+ console.log(chalk.cyan(` \u2570${border}\u256F`));
1814
+ console.log("");
1815
+ await mergeAndSaveConfig({ lastSeenVersion: currentVersion });
1816
+ } catch {
1256
1817
  }
1257
1818
  }
1258
1819
 
1259
1820
  // src/commands/create.ts
1821
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
1822
+ import { dirname as dirname6, join as join18 } from "path";
1260
1823
  import chalk5 from "chalk";
1261
1824
  import ora from "ora";
1262
1825
 
1263
1826
  // src/lib/auth.ts
1264
1827
  import { createHash, randomBytes } from "crypto";
1265
- import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink4 } from "fs/promises";
1828
+ import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
1266
1829
  import http from "http";
1267
- import { homedir as homedir8 } from "os";
1268
- import { join as join10 } from "path";
1269
- var CONFIG_DIR5 = join10(homedir8(), ".repowise");
1270
- var CREDENTIALS_PATH2 = join10(CONFIG_DIR5, "credentials.json");
1830
+ import { join as join15 } from "path";
1271
1831
  var CLI_CALLBACK_PORT = 19876;
1272
1832
  var CALLBACK_TIMEOUT_MS = 12e4;
1273
1833
  function getCognitoConfigForStorage() {
@@ -1433,7 +1993,8 @@ async function refreshTokens2(refreshToken) {
1433
1993
  }
1434
1994
  async function getStoredCredentials2() {
1435
1995
  try {
1436
- const data = await readFile7(CREDENTIALS_PATH2, "utf-8");
1996
+ const credPath = join15(getConfigDir2(), "credentials.json");
1997
+ const data = await readFile7(credPath, "utf-8");
1437
1998
  return JSON.parse(data);
1438
1999
  } catch (err) {
1439
2000
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -1443,13 +2004,15 @@ async function getStoredCredentials2() {
1443
2004
  }
1444
2005
  }
1445
2006
  async function storeCredentials2(credentials) {
1446
- await mkdir9(CONFIG_DIR5, { recursive: true, mode: 448 });
1447
- await writeFile9(CREDENTIALS_PATH2, JSON.stringify(credentials, null, 2));
1448
- await chmod4(CREDENTIALS_PATH2, 384);
2007
+ const dir = getConfigDir2();
2008
+ const credPath = join15(dir, "credentials.json");
2009
+ await mkdir9(dir, { recursive: true, mode: 448 });
2010
+ await writeFile9(credPath, JSON.stringify(credentials, null, 2));
2011
+ await chmod4(credPath, 384);
1449
2012
  }
1450
2013
  async function clearCredentials() {
1451
2014
  try {
1452
- await unlink4(CREDENTIALS_PATH2);
2015
+ await unlink7(join15(getConfigDir2(), "credentials.json"));
1453
2016
  } catch (err) {
1454
2017
  if (err.code !== "ENOENT") throw err;
1455
2018
  }
@@ -1641,7 +2204,7 @@ async function selectAiTools() {
1641
2204
 
1642
2205
  // src/lib/ai-tools.ts
1643
2206
  import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, readdir } from "fs/promises";
1644
- import { join as join11, dirname as dirname3 } from "path";
2207
+ import { join as join16, dirname as dirname5 } from "path";
1645
2208
  var AI_TOOL_CONFIG = {
1646
2209
  cursor: {
1647
2210
  label: "Cursor",
@@ -1770,8 +2333,8 @@ function generateReference(tool, repoName, contextFolder, contextFiles) {
1770
2333
  }
1771
2334
  async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
1772
2335
  const config2 = AI_TOOL_CONFIG[tool];
1773
- const fullPath = join11(repoRoot, config2.filePath);
1774
- const dir = dirname3(fullPath);
2336
+ const fullPath = join16(repoRoot, config2.filePath);
2337
+ const dir = dirname5(fullPath);
1775
2338
  if (dir !== repoRoot) {
1776
2339
  await mkdir10(dir, { recursive: true });
1777
2340
  }
@@ -1799,14 +2362,14 @@ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, context
1799
2362
  return { created };
1800
2363
  }
1801
2364
  async function scanLocalContextFiles(repoRoot, contextFolder) {
1802
- const folderPath = join11(repoRoot, contextFolder);
2365
+ const folderPath = join16(repoRoot, contextFolder);
1803
2366
  try {
1804
2367
  const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
1805
2368
  const results = [];
1806
2369
  for (const entry of entries) {
1807
2370
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1808
2371
  const parentDir = entry.parentPath ?? folderPath;
1809
- const fullPath = join11(parentDir, entry.name);
2372
+ const fullPath = join16(parentDir, entry.name);
1810
2373
  const relFromContext = fullPath.slice(folderPath.length + 1);
1811
2374
  results.push({
1812
2375
  fileName: relFromContext,
@@ -1822,9 +2385,9 @@ async function scanLocalContextFiles(repoRoot, contextFolder) {
1822
2385
 
1823
2386
  // src/lib/gitignore.ts
1824
2387
  import { readFileSync, writeFileSync, existsSync } from "fs";
1825
- import { join as join12 } from "path";
2388
+ import { join as join17 } from "path";
1826
2389
  function ensureGitignore(repoRoot, entry) {
1827
- const gitignorePath = join12(repoRoot, ".gitignore");
2390
+ const gitignorePath = join17(repoRoot, ".gitignore");
1828
2391
  if (existsSync(gitignorePath)) {
1829
2392
  const content = readFileSync(gitignorePath, "utf-8");
1830
2393
  const lines = content.split("\n").map((l) => l.trim());
@@ -1838,6 +2401,24 @@ function ensureGitignore(repoRoot, entry) {
1838
2401
  }
1839
2402
  }
1840
2403
 
2404
+ // src/lib/git.ts
2405
+ import { execSync } from "child_process";
2406
+ function detectRepoRoot() {
2407
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
2408
+ }
2409
+ function detectRepoName(repoRoot) {
2410
+ try {
2411
+ const remoteUrl = execSync("git remote get-url origin", {
2412
+ encoding: "utf-8",
2413
+ cwd: repoRoot
2414
+ }).trim();
2415
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
2416
+ if (match?.[1]) return match[1];
2417
+ } catch {
2418
+ }
2419
+ return repoRoot.split("/").pop() ?? "unknown";
2420
+ }
2421
+
1841
2422
  // src/lib/interview-handler.ts
1842
2423
  import chalk3 from "chalk";
1843
2424
  import { input } from "@inquirer/prompts";
@@ -2081,7 +2662,9 @@ var ProgressRenderer = class {
2081
2662
  spinner.stop();
2082
2663
  console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
2083
2664
  console.log(
2084
- chalk4.cyan(" \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!")
2665
+ chalk4.cyan(
2666
+ " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2667
+ )
2085
2668
  );
2086
2669
  console.log("");
2087
2670
  spinner.start();
@@ -2247,21 +2830,6 @@ var ProgressRenderer = class {
2247
2830
  };
2248
2831
 
2249
2832
  // src/commands/create.ts
2250
- function detectRepoRoot() {
2251
- return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
2252
- }
2253
- function detectRepoName(repoRoot) {
2254
- try {
2255
- const remoteUrl = execSync("git remote get-url origin", {
2256
- encoding: "utf-8",
2257
- cwd: repoRoot
2258
- }).trim();
2259
- const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
2260
- if (match?.[1]) return match[1];
2261
- } catch {
2262
- }
2263
- return repoRoot.split("/").pop() ?? "unknown";
2264
- }
2265
2833
  function formatElapsed(ms) {
2266
2834
  const totalSeconds = Math.round(ms / 1e3);
2267
2835
  const minutes = Math.floor(totalSeconds / 60);
@@ -2354,12 +2922,18 @@ async function create() {
2354
2922
  console.log(
2355
2923
  chalk5.cyan(
2356
2924
  `
2357
- To trigger a new full scan, visit the dashboard:
2358
- https://app.repowise.ai/repos/${repoId}
2925
+ If you're a team member, run: ${chalk5.bold("repowise member")}
2926
+ This will download context and configure your AI tools.
2927
+ `
2928
+ )
2929
+ );
2930
+ console.log(
2931
+ chalk5.dim(
2932
+ ` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
2933
+ To sync recent changes, use: repowise sync
2359
2934
  `
2360
2935
  )
2361
2936
  );
2362
- console.log(chalk5.dim(" To sync recent changes, use: repowise sync\n"));
2363
2937
  process.exitCode = 1;
2364
2938
  return;
2365
2939
  }
@@ -2487,7 +3061,7 @@ async function create() {
2487
3061
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
2488
3062
  const files = listResult.data?.files ?? listResult.files ?? [];
2489
3063
  if (files.length > 0) {
2490
- const contextDir = join13(repoRoot, DEFAULT_CONTEXT_FOLDER);
3064
+ const contextDir = join18(repoRoot, DEFAULT_CONTEXT_FOLDER);
2491
3065
  mkdirSync(contextDir, { recursive: true });
2492
3066
  let downloadedCount = 0;
2493
3067
  let failedCount = 0;
@@ -2501,8 +3075,8 @@ async function create() {
2501
3075
  const response = await fetch(presignedUrl);
2502
3076
  if (response.ok) {
2503
3077
  const content = await response.text();
2504
- const filePath = join13(contextDir, file.fileName);
2505
- mkdirSync(dirname4(filePath), { recursive: true });
3078
+ const filePath = join18(contextDir, file.fileName);
3079
+ mkdirSync(dirname6(filePath), { recursive: true });
2506
3080
  writeFileSync2(filePath, content, "utf-8");
2507
3081
  downloadedCount++;
2508
3082
  } else {
@@ -2563,33 +3137,27 @@ Files are stored on our servers (not in git). Retry when online.`
2563
3137
  spinner.succeed("AI tools configured");
2564
3138
  console.log(chalk5.dim(results.join("\n")));
2565
3139
  }
2566
- const existingConfig = await getConfig();
2567
- const existingRepos = existingConfig.repos ?? [];
2568
- const updatedRepos = existingRepos.filter((r) => r.repoId !== repoId);
3140
+ const updatedRepos = [];
2569
3141
  if (repoRoot) {
2570
3142
  const repoEntry = {
2571
3143
  repoId,
2572
- localPath: repoRoot
3144
+ localPath: repoRoot,
3145
+ apiUrl: getEnvConfig().apiUrl
2573
3146
  };
2574
- if (isStagingMode()) {
2575
- repoEntry.apiUrl = getEnvConfig().apiUrl;
2576
- }
2577
3147
  if (repoPlatform) repoEntry.platform = repoPlatform;
2578
3148
  if (repoExternalId) repoEntry.externalId = repoExternalId;
2579
3149
  updatedRepos.push(repoEntry);
2580
3150
  }
2581
- await saveConfig({
2582
- ...existingConfig,
2583
- aiTools: tools,
2584
- contextFolder,
2585
- repos: updatedRepos
2586
- });
3151
+ const configUpdate = { aiTools: tools, contextFolder };
3152
+ if (updatedRepos.length > 0) configUpdate.repos = updatedRepos;
3153
+ await mergeAndSaveConfig(configUpdate);
2587
3154
  let listenerRunning = false;
2588
3155
  try {
2589
- await install();
2590
- await startBackground();
3156
+ await ensureListenerRunning();
2591
3157
  listenerRunning = true;
2592
3158
  } catch {
3159
+ }
3160
+ if (!listenerRunning) {
2593
3161
  console.log(
2594
3162
  chalk5.yellow(
2595
3163
  "Warning: Could not start listener automatically. Run the following to enable it:"
@@ -2626,11 +3194,255 @@ Files are stored on our servers (not in git). Retry when online.`
2626
3194
  }
2627
3195
  }
2628
3196
 
2629
- // src/commands/login.ts
3197
+ // src/commands/member.ts
3198
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
3199
+ import { dirname as dirname7, join as join19 } from "path";
2630
3200
  import chalk6 from "chalk";
2631
3201
  import ora2 from "ora";
3202
+ var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
3203
+ function formatElapsed2(ms) {
3204
+ const totalSeconds = Math.round(ms / 1e3);
3205
+ const minutes = Math.floor(totalSeconds / 60);
3206
+ const seconds = totalSeconds % 60;
3207
+ if (minutes === 0) return `${seconds}s`;
3208
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
3209
+ }
3210
+ async function member() {
3211
+ const startTime = Date.now();
3212
+ const spinner = ora2("Checking authentication...").start();
3213
+ try {
3214
+ let credentials = await getValidCredentials2();
3215
+ if (!credentials) {
3216
+ spinner.info(chalk6.yellow("Not logged in. Opening browser to authenticate..."));
3217
+ credentials = await performLogin();
3218
+ const { email } = decodeIdToken(credentials.idToken);
3219
+ spinner.succeed(chalk6.green(`Authenticated as ${chalk6.bold(email)}`));
3220
+ } else {
3221
+ spinner.succeed("Authenticated");
3222
+ }
3223
+ let repoId;
3224
+ let repoName;
3225
+ let repoRoot;
3226
+ let repoPlatform;
3227
+ let repoExternalId;
3228
+ spinner.start("Checking for pending repository...");
3229
+ try {
3230
+ const pending = await apiRequest("/v1/onboarding/pending");
3231
+ if (pending?.repoId) {
3232
+ repoId = pending.repoId;
3233
+ repoName = pending.repoName;
3234
+ spinner.succeed(`Found pending repository: ${chalk6.bold(repoName)}`);
3235
+ apiRequest("/v1/onboarding/pending", { method: "DELETE" }).catch(() => {
3236
+ });
3237
+ }
3238
+ } catch {
3239
+ }
3240
+ if (!repoId) {
3241
+ spinner.text = "Detecting repository...";
3242
+ try {
3243
+ repoRoot = detectRepoRoot();
3244
+ repoName = detectRepoName(repoRoot);
3245
+ spinner.succeed(`Repository: ${chalk6.bold(repoName)}`);
3246
+ } catch {
3247
+ spinner.fail(
3248
+ chalk6.red("Not in a git repository. Run this command from your project directory.")
3249
+ );
3250
+ process.exitCode = 1;
3251
+ return;
3252
+ }
3253
+ } else {
3254
+ try {
3255
+ repoRoot = detectRepoRoot();
3256
+ const localRepoName = detectRepoName(repoRoot);
3257
+ if (repoName && localRepoName !== repoName && !repoName.endsWith(`/${localRepoName}`)) {
3258
+ spinner.warn(
3259
+ chalk6.yellow(
3260
+ `Pending repo is ${chalk6.bold(repoName)} but you're in ${chalk6.bold(localRepoName)}.
3261
+ Make sure you're in the right project directory, or the context files will be saved here.`
3262
+ )
3263
+ );
3264
+ }
3265
+ } catch {
3266
+ spinner.fail(
3267
+ chalk6.red(
3268
+ "Please navigate to your project directory first, then run `repowise member` again."
3269
+ )
3270
+ );
3271
+ process.exitCode = 1;
3272
+ return;
3273
+ }
3274
+ }
3275
+ if (!repoId || !repoPlatform) {
3276
+ spinner.start("Looking up repository...");
3277
+ try {
3278
+ const repos = await apiRequest("/v1/repos");
3279
+ const match = repos.find(
3280
+ (r) => r.repoId === repoId || r.name === repoName || r.fullName.endsWith(`/${repoName}`)
3281
+ );
3282
+ if (match) {
3283
+ repoId = match.repoId;
3284
+ repoPlatform = match.platform;
3285
+ repoExternalId = match.externalId;
3286
+ if (!repoName) repoName = match.fullName;
3287
+ spinner.succeed(`Repository: ${chalk6.bold(match.fullName)}`);
3288
+ }
3289
+ } catch {
3290
+ }
3291
+ }
3292
+ if (!repoId) {
3293
+ spinner.fail(
3294
+ chalk6.red(
3295
+ "This repository is not connected to your team. Ask your team admin to add it on the dashboard."
3296
+ )
3297
+ );
3298
+ process.exitCode = 1;
3299
+ return;
3300
+ }
3301
+ spinner.start("Checking for context files...");
3302
+ let files = [];
3303
+ try {
3304
+ const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
3305
+ files = listResult.data?.files ?? listResult.files ?? [];
3306
+ } catch {
3307
+ spinner.fail(chalk6.red("Failed to check context files on server."));
3308
+ process.exitCode = 1;
3309
+ return;
3310
+ }
3311
+ if (files.length === 0) {
3312
+ spinner.fail(
3313
+ chalk6.red(
3314
+ "No context files found for this repository. Your team admin needs to run the initial scan first."
3315
+ )
3316
+ );
3317
+ process.exitCode = 1;
3318
+ return;
3319
+ }
3320
+ spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
3321
+ const { tools } = await selectAiTools();
3322
+ spinner.start("Downloading context files...");
3323
+ const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3324
+ mkdirSync2(contextDir, { recursive: true });
3325
+ let downloadedCount = 0;
3326
+ let failedCount = 0;
3327
+ for (const file of files) {
3328
+ if (file.fileName.includes("..")) {
3329
+ failedCount++;
3330
+ continue;
3331
+ }
3332
+ try {
3333
+ const urlResult = await apiRequest(`/v1/repos/${repoId}/context/files/${file.fileName}`);
3334
+ const presignedUrl = urlResult.data?.url ?? urlResult.url;
3335
+ const response = await fetch(presignedUrl);
3336
+ if (response.ok) {
3337
+ const content = await response.text();
3338
+ const filePath = join19(contextDir, file.fileName);
3339
+ mkdirSync2(dirname7(filePath), { recursive: true });
3340
+ writeFileSync3(filePath, content, "utf-8");
3341
+ downloadedCount++;
3342
+ } else {
3343
+ failedCount++;
3344
+ }
3345
+ } catch {
3346
+ failedCount++;
3347
+ }
3348
+ }
3349
+ if (failedCount > 0) {
3350
+ spinner.warn(
3351
+ `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
3352
+ );
3353
+ } else {
3354
+ spinner.succeed(`Downloaded ${downloadedCount} files to ./${DEFAULT_CONTEXT_FOLDER2}/`);
3355
+ }
3356
+ try {
3357
+ ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3358
+ } catch {
3359
+ }
3360
+ if (tools.length > 0) {
3361
+ spinner.start("Configuring AI tools...");
3362
+ const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3363
+ const configured = [];
3364
+ for (const tool of tools) {
3365
+ const { created } = await updateToolConfig(
3366
+ repoRoot,
3367
+ tool,
3368
+ repoName,
3369
+ DEFAULT_CONTEXT_FOLDER2,
3370
+ contextFiles
3371
+ );
3372
+ const config2 = AI_TOOL_CONFIG[tool];
3373
+ configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
3374
+ }
3375
+ spinner.succeed("AI tools configured");
3376
+ for (const msg of configured) {
3377
+ console.log(chalk6.dim(` ${msg}`));
3378
+ }
3379
+ }
3380
+ spinner.start("Saving configuration...");
3381
+ const repoEntry = {
3382
+ repoId,
3383
+ localPath: repoRoot,
3384
+ apiUrl: getEnvConfig().apiUrl
3385
+ };
3386
+ if (repoPlatform) repoEntry.platform = repoPlatform;
3387
+ if (repoExternalId) repoEntry.externalId = repoExternalId;
3388
+ await mergeAndSaveConfig({
3389
+ aiTools: tools,
3390
+ contextFolder: DEFAULT_CONTEXT_FOLDER2,
3391
+ repos: [repoEntry]
3392
+ });
3393
+ spinner.succeed("Configuration saved");
3394
+ let listenerRunning = false;
3395
+ try {
3396
+ await ensureListenerRunning();
3397
+ listenerRunning = true;
3398
+ } catch {
3399
+ }
3400
+ try {
3401
+ await apiRequest("/v1/onboarding/complete", {
3402
+ method: "POST",
3403
+ body: JSON.stringify({ repoId })
3404
+ });
3405
+ } catch {
3406
+ }
3407
+ const elapsed = formatElapsed2(Date.now() - startTime);
3408
+ console.log("");
3409
+ console.log(chalk6.green.bold("All done! Context downloaded and configured."));
3410
+ console.log("");
3411
+ console.log(`Your AI tools now have access to project context for ${chalk6.bold(repoName)}.`);
3412
+ console.log(
3413
+ `Downloaded ${chalk6.bold(downloadedCount)} context files to ./${DEFAULT_CONTEXT_FOLDER2}/`
3414
+ );
3415
+ console.log("");
3416
+ if (listenerRunning) {
3417
+ console.log(
3418
+ chalk6.dim(
3419
+ "The RepoWise listener is running in the background \u2014\nyour context will stay in sync automatically."
3420
+ )
3421
+ );
3422
+ } else {
3423
+ console.log(
3424
+ chalk6.yellow(
3425
+ "Could not start listener automatically. Run `repowise listen --install` to enable auto-sync."
3426
+ )
3427
+ );
3428
+ }
3429
+ console.log("");
3430
+ console.log(
3431
+ chalk6.dim(`Head back to the dashboard and click "Complete Onboarding" to finish setup.`)
3432
+ );
3433
+ console.log(chalk6.dim(`Total time: ${elapsed}`));
3434
+ } catch (err) {
3435
+ const message = err instanceof Error ? err.message : "Unknown error";
3436
+ spinner.fail(chalk6.red(`Setup failed: ${message}`));
3437
+ process.exitCode = 1;
3438
+ }
3439
+ }
3440
+
3441
+ // src/commands/login.ts
3442
+ import chalk7 from "chalk";
3443
+ import ora3 from "ora";
2632
3444
  async function login(options = {}) {
2633
- const spinner = ora2("Preparing login...").start();
3445
+ const spinner = ora3("Preparing login...").start();
2634
3446
  try {
2635
3447
  const codeVerifier = generateCodeVerifier();
2636
3448
  const codeChallenge = generateCodeChallenge(codeVerifier);
@@ -2642,7 +3454,7 @@ async function login(options = {}) {
2642
3454
  console.log(`
2643
3455
  Open this URL in your browser to authenticate:
2644
3456
  `);
2645
- console.log(chalk6.cyan(authorizeUrl));
3457
+ console.log(chalk7.cyan(authorizeUrl));
2646
3458
  console.log(`
2647
3459
  Waiting for authentication...`);
2648
3460
  } else {
@@ -2656,7 +3468,7 @@ Waiting for authentication...`);
2656
3468
  console.log(`
2657
3469
  Could not open browser automatically. Open this URL:
2658
3470
  `);
2659
- console.log(chalk6.cyan(authorizeUrl));
3471
+ console.log(chalk7.cyan(authorizeUrl));
2660
3472
  console.log(`
2661
3473
  Waiting for authentication...`);
2662
3474
  }
@@ -2670,60 +3482,50 @@ Waiting for authentication...`);
2670
3482
  credentials.cognito = getCognitoConfigForStorage();
2671
3483
  await storeCredentials2(credentials);
2672
3484
  const { email } = decodeIdToken(credentials.idToken);
2673
- spinner.succeed(chalk6.green(`Logged in as ${chalk6.bold(email)}`));
3485
+ spinner.succeed(chalk7.green(`Logged in as ${chalk7.bold(email)}`));
2674
3486
  } catch (err) {
2675
3487
  const message = err instanceof Error ? err.message : "Login failed";
2676
- spinner.fail(chalk6.red(message));
3488
+ spinner.fail(chalk7.red(message));
2677
3489
  process.exitCode = 1;
2678
3490
  }
2679
3491
  }
2680
3492
 
2681
3493
  // src/commands/logout.ts
2682
- import chalk7 from "chalk";
3494
+ import chalk8 from "chalk";
2683
3495
  async function logout() {
2684
3496
  const creds = await getStoredCredentials2();
2685
3497
  if (!creds) {
2686
- console.log(chalk7.yellow("Not logged in."));
3498
+ console.log(chalk8.yellow("Not logged in."));
2687
3499
  return;
2688
3500
  }
2689
3501
  await clearCredentials();
2690
- console.log(chalk7.green("Logged out successfully."));
3502
+ console.log(chalk8.green("Logged out successfully."));
2691
3503
  }
2692
3504
 
2693
3505
  // src/commands/status.ts
2694
3506
  import { readFile as readFile9 } from "fs/promises";
2695
- import { homedir as homedir9 } from "os";
2696
- import { basename as basename2, join as join14 } from "path";
2697
- var STATE_PATH2 = join14(homedir9(), ".repowise", "listener-state.json");
2698
- var CONFIG_PATH3 = join14(homedir9(), ".repowise", "config.json");
3507
+ import { basename as basename2, join as join20 } from "path";
2699
3508
  async function status() {
3509
+ const configDir = getConfigDir2();
3510
+ const STATE_PATH = join20(configDir, "listener-state.json");
3511
+ const CONFIG_PATH = join20(configDir, "config.json");
2700
3512
  let state = null;
2701
3513
  try {
2702
- const data = await readFile9(STATE_PATH2, "utf-8");
3514
+ const data = await readFile9(STATE_PATH, "utf-8");
2703
3515
  state = JSON.parse(data);
2704
3516
  } catch {
2705
3517
  }
2706
- let processRunning = false;
2707
- let pid = null;
2708
- try {
2709
- const processStatus = await getStatus();
2710
- processRunning = processStatus.running;
2711
- pid = processStatus.pid;
2712
- } catch {
2713
- }
2714
- let serviceInstalled = false;
2715
- try {
2716
- serviceInstalled = await isInstalled();
2717
- } catch {
2718
- }
3518
+ const listenerStatus = await getListenerStatus();
2719
3519
  console.log("RepoWise Status");
2720
3520
  console.log("===============");
2721
- if (processRunning) {
2722
- console.log(`Listener: running (PID: ${pid})`);
3521
+ if (listenerStatus.running) {
3522
+ console.log(
3523
+ listenerStatus.method === "service" ? "Listener: running (managed by system service)" : `Listener: running (PID: ${listenerStatus.pid})`
3524
+ );
2723
3525
  } else {
2724
3526
  console.log("Listener: stopped");
2725
3527
  }
2726
- if (serviceInstalled) {
3528
+ if (listenerStatus.serviceInstalled) {
2727
3529
  console.log("Auto-start: enabled");
2728
3530
  } else {
2729
3531
  console.log("Auto-start: disabled");
@@ -2735,7 +3537,7 @@ async function status() {
2735
3537
  }
2736
3538
  const repoNames = /* @__PURE__ */ new Map();
2737
3539
  try {
2738
- const configData = await readFile9(CONFIG_PATH3, "utf-8");
3540
+ const configData = await readFile9(CONFIG_PATH, "utf-8");
2739
3541
  const config2 = JSON.parse(configData);
2740
3542
  for (const repo of config2.repos ?? []) {
2741
3543
  repoNames.set(repo.repoId, basename2(repo.localPath));
@@ -2753,30 +3555,14 @@ async function status() {
2753
3555
  }
2754
3556
 
2755
3557
  // src/commands/sync.ts
2756
- import { execSync as execSync2 } from "child_process";
2757
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
2758
- import { dirname as dirname5, join as join15 } from "path";
2759
- import chalk8 from "chalk";
2760
- import ora3 from "ora";
3558
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
3559
+ import { dirname as dirname8, join as join21 } from "path";
3560
+ import chalk9 from "chalk";
3561
+ import ora4 from "ora";
2761
3562
  var POLL_INTERVAL_MS2 = 3e3;
2762
3563
  var MAX_POLL_ATTEMPTS2 = 7200;
2763
- var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
2764
- function detectRepoRoot2() {
2765
- return execSync2("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
2766
- }
2767
- function detectRepoName2(repoRoot) {
2768
- try {
2769
- const remoteUrl = execSync2("git remote get-url origin", {
2770
- encoding: "utf-8",
2771
- cwd: repoRoot
2772
- }).trim();
2773
- const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
2774
- if (match?.[1]) return match[1];
2775
- } catch {
2776
- }
2777
- return repoRoot.split("/").pop() ?? "unknown";
2778
- }
2779
- function formatElapsed2(ms) {
3564
+ var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
3565
+ function formatElapsed3(ms) {
2780
3566
  const totalSeconds = Math.round(ms / 1e3);
2781
3567
  const minutes = Math.floor(totalSeconds / 60);
2782
3568
  const seconds = totalSeconds % 60;
@@ -2785,14 +3571,14 @@ function formatElapsed2(ms) {
2785
3571
  }
2786
3572
  async function sync() {
2787
3573
  const startTime = Date.now();
2788
- const spinner = ora3("Checking authentication...").start();
3574
+ const spinner = ora4("Checking authentication...").start();
2789
3575
  try {
2790
3576
  let credentials = await getValidCredentials2();
2791
3577
  if (!credentials) {
2792
- spinner.info(chalk8.yellow("Not logged in. Opening browser to authenticate..."));
3578
+ spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
2793
3579
  credentials = await performLogin();
2794
3580
  const { email } = decodeIdToken(credentials.idToken);
2795
- spinner.succeed(chalk8.green(`Authenticated as ${chalk8.bold(email)}`));
3581
+ spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
2796
3582
  } else {
2797
3583
  spinner.succeed("Authenticated");
2798
3584
  }
@@ -2800,12 +3586,12 @@ async function sync() {
2800
3586
  let repoName;
2801
3587
  spinner.start("Detecting repository...");
2802
3588
  try {
2803
- repoRoot = detectRepoRoot2();
2804
- repoName = detectRepoName2(repoRoot);
2805
- spinner.succeed(`Repository: ${chalk8.bold(repoName)}`);
3589
+ repoRoot = detectRepoRoot();
3590
+ repoName = detectRepoName(repoRoot);
3591
+ spinner.succeed(`Repository: ${chalk9.bold(repoName)}`);
2806
3592
  } catch {
2807
3593
  spinner.fail(
2808
- chalk8.red("Not in a git repository. Run this command from your repo directory.")
3594
+ chalk9.red("Not in a git repository. Run this command from your repo directory.")
2809
3595
  );
2810
3596
  process.exitCode = 1;
2811
3597
  return;
@@ -2826,7 +3612,7 @@ async function sync() {
2826
3612
  }
2827
3613
  if (!repoId) {
2828
3614
  spinner.fail(
2829
- chalk8.red(
3615
+ chalk9.red(
2830
3616
  "Could not find this repository in your RepoWise account. Run `repowise create` first."
2831
3617
  )
2832
3618
  );
@@ -2863,7 +3649,7 @@ async function sync() {
2863
3649
  syncId = retryResult.syncId;
2864
3650
  } else {
2865
3651
  syncId = active.syncId;
2866
- spinner.info(chalk8.cyan("Resuming existing sync..."));
3652
+ spinner.info(chalk9.cyan("Resuming existing sync..."));
2867
3653
  spinner.start();
2868
3654
  }
2869
3655
  }
@@ -2871,7 +3657,7 @@ async function sync() {
2871
3657
  const progressRenderer = new ProgressRenderer();
2872
3658
  while (true) {
2873
3659
  if (++pollAttempts > MAX_POLL_ATTEMPTS2) {
2874
- spinner.fail(chalk8.red("Sync timed out. Check dashboard for status."));
3660
+ spinner.fail(chalk9.red("Sync timed out. Check dashboard for status."));
2875
3661
  process.exitCode = 1;
2876
3662
  return;
2877
3663
  }
@@ -2895,15 +3681,15 @@ async function sync() {
2895
3681
  const generatedFiles = syncResult.filesGenerated ?? [];
2896
3682
  const fileCount = generatedFiles.length;
2897
3683
  if (fileCount > 0) {
2898
- spinner.succeed(chalk8.green(`Incremental update complete \u2014 ${fileCount} files updated`));
3684
+ spinner.succeed(chalk9.green(`Incremental update complete \u2014 ${fileCount} files updated`));
2899
3685
  } else {
2900
- spinner.succeed(chalk8.green("Incremental sync complete \u2014 no files needed updating"));
3686
+ spinner.succeed(chalk9.green("Incremental sync complete \u2014 no files needed updating"));
2901
3687
  }
2902
3688
  break;
2903
3689
  }
2904
3690
  if (syncResult.status === "failed") {
2905
3691
  progressRenderer.finalize();
2906
- spinner.fail(chalk8.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
3692
+ spinner.fail(chalk9.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
2907
3693
  process.exitCode = 1;
2908
3694
  return;
2909
3695
  }
@@ -2913,8 +3699,8 @@ async function sync() {
2913
3699
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
2914
3700
  const files = listResult.data?.files ?? listResult.files ?? [];
2915
3701
  if (files.length > 0) {
2916
- const contextDir = join15(repoRoot, DEFAULT_CONTEXT_FOLDER2);
2917
- mkdirSync2(contextDir, { recursive: true });
3702
+ const contextDir = join21(repoRoot, DEFAULT_CONTEXT_FOLDER3);
3703
+ mkdirSync3(contextDir, { recursive: true });
2918
3704
  let downloadedCount = 0;
2919
3705
  let failedCount = 0;
2920
3706
  for (const file of files) {
@@ -2927,9 +3713,9 @@ async function sync() {
2927
3713
  const response = await fetch(presignedUrl);
2928
3714
  if (response.ok) {
2929
3715
  const content = await response.text();
2930
- const filePath = join15(contextDir, file.fileName);
2931
- mkdirSync2(dirname5(filePath), { recursive: true });
2932
- writeFileSync3(filePath, content, "utf-8");
3716
+ const filePath = join21(contextDir, file.fileName);
3717
+ mkdirSync3(dirname8(filePath), { recursive: true });
3718
+ writeFileSync4(filePath, content, "utf-8");
2933
3719
  downloadedCount++;
2934
3720
  } else {
2935
3721
  failedCount++;
@@ -2937,13 +3723,13 @@ async function sync() {
2937
3723
  }
2938
3724
  if (failedCount > 0) {
2939
3725
  spinner.warn(
2940
- `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
3726
+ `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER3}/ (${failedCount} failed)`
2941
3727
  );
2942
3728
  } else {
2943
- spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER2}/`);
3729
+ spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER3}/`);
2944
3730
  }
2945
3731
  try {
2946
- ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3732
+ ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
2947
3733
  } catch {
2948
3734
  }
2949
3735
  } else {
@@ -2951,7 +3737,7 @@ async function sync() {
2951
3737
  }
2952
3738
  } catch (err) {
2953
3739
  const msg = err instanceof Error ? err.message : "Unknown error";
2954
- spinner.warn(chalk8.yellow(`Could not download context files: ${msg}
3740
+ spinner.warn(chalk9.yellow(`Could not download context files: ${msg}
2955
3741
  Retry when online.`));
2956
3742
  }
2957
3743
  if (repoRoot && repoId) {
@@ -2959,29 +3745,24 @@ Retry when online.`));
2959
3745
  const existingConfig = await getConfig();
2960
3746
  const existingRepos = existingConfig.repos ?? [];
2961
3747
  if (!existingRepos.some((r) => r.repoId === repoId)) {
2962
- const repoEntry = { repoId, localPath: repoRoot };
2963
- if (isStagingMode()) {
2964
- repoEntry.apiUrl = getEnvConfig().apiUrl;
2965
- }
3748
+ const repoEntry = { repoId, localPath: repoRoot, apiUrl: getEnvConfig().apiUrl };
2966
3749
  if (repoPlatform) repoEntry.platform = repoPlatform;
2967
3750
  if (repoExternalId) repoEntry.externalId = repoExternalId;
2968
- existingRepos.push(repoEntry);
2969
- await saveConfig({ ...existingConfig, repos: existingRepos });
3751
+ await mergeAndSaveConfig({ repos: [repoEntry] });
2970
3752
  }
2971
3753
  } catch {
2972
3754
  }
2973
3755
  try {
2974
- await install();
2975
- await startBackground();
3756
+ await ensureListenerRunning();
2976
3757
  } catch {
2977
3758
  }
2978
3759
  }
2979
- const elapsed = formatElapsed2(Date.now() - startTime);
2980
- console.log(chalk8.dim(`
3760
+ const elapsed = formatElapsed3(Date.now() - startTime);
3761
+ console.log(chalk9.dim(`
2981
3762
  Total time: ${elapsed}`));
2982
3763
  } catch (err) {
2983
3764
  const message = err instanceof Error ? err.message : "Sync failed";
2984
- spinner.fail(chalk8.red(message));
3765
+ spinner.fail(chalk9.red(message));
2985
3766
  process.exitCode = 1;
2986
3767
  }
2987
3768
  }
@@ -3029,12 +3810,15 @@ async function listen(options) {
3029
3810
  // src/commands/start.ts
3030
3811
  async function start() {
3031
3812
  try {
3032
- if (await isRunning()) {
3813
+ const status2 = await getListenerStatus();
3814
+ if (status2.running) {
3033
3815
  console.log("Listener is already running.");
3034
3816
  return;
3035
3817
  }
3036
- const pid = await startBackground();
3037
- console.log(`Listener started (PID: ${pid}).`);
3818
+ const result = await ensureListenerRunning();
3819
+ console.log(
3820
+ result.method === "service" ? "Listener service installed and started." : `Listener started (PID: ${result.pid}).`
3821
+ );
3038
3822
  } catch (err) {
3039
3823
  const message = err instanceof Error ? err.message : "Unknown error";
3040
3824
  console.error(`Failed to start listener: ${message}`);
@@ -3045,11 +3829,12 @@ async function start() {
3045
3829
  // src/commands/stop.ts
3046
3830
  async function stop2() {
3047
3831
  try {
3048
- if (!await isRunning()) {
3832
+ const status2 = await getListenerStatus();
3833
+ if (!status2.running) {
3049
3834
  console.log("Listener is not running.");
3050
3835
  return;
3051
3836
  }
3052
- await stopProcess();
3837
+ await stopListener();
3053
3838
  console.log("Listener stopped.");
3054
3839
  } catch (err) {
3055
3840
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -3059,18 +3844,18 @@ async function stop2() {
3059
3844
  }
3060
3845
 
3061
3846
  // src/commands/config.ts
3062
- import chalk9 from "chalk";
3063
- import ora4 from "ora";
3847
+ import chalk10 from "chalk";
3848
+ import ora5 from "ora";
3064
3849
  import { select } from "@inquirer/prompts";
3065
3850
  async function config() {
3066
- const spinner = ora4("Checking authentication...").start();
3851
+ const spinner = ora5("Checking authentication...").start();
3067
3852
  try {
3068
3853
  let credentials = await getValidCredentials2();
3069
3854
  if (!credentials) {
3070
- spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
3855
+ spinner.info(chalk10.yellow("Not logged in. Opening browser to authenticate..."));
3071
3856
  credentials = await performLogin();
3072
3857
  const { email } = decodeIdToken(credentials.idToken);
3073
- spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
3858
+ spinner.succeed(chalk10.green(`Authenticated as ${chalk10.bold(email)}`));
3074
3859
  } else {
3075
3860
  spinner.succeed("Authenticated");
3076
3861
  }
@@ -3078,7 +3863,7 @@ async function config() {
3078
3863
  const repos = await apiRequest("/v1/repos");
3079
3864
  spinner.stop();
3080
3865
  if (repos.length === 0) {
3081
- console.log(chalk9.yellow("No repositories connected. Run `repowise create` first."));
3866
+ console.log(chalk10.yellow("No repositories connected. Run `repowise create` first."));
3082
3867
  return;
3083
3868
  }
3084
3869
  const repoId = await select({
@@ -3092,10 +3877,10 @@ async function config() {
3092
3877
  const currentDelivery = repo.deliveryMode ?? "direct";
3093
3878
  const currentBranch = repo.monitoredBranch ?? repo.defaultBranch;
3094
3879
  console.log("");
3095
- console.log(chalk9.bold(`Settings for ${repo.fullName}`));
3096
- console.log(chalk9.dim(` Context storage: RepoWise servers`));
3097
- console.log(chalk9.dim(` Delivery mode: ${currentDelivery}`));
3098
- console.log(chalk9.dim(` Monitored branch: ${currentBranch}`));
3880
+ console.log(chalk10.bold(`Settings for ${repo.fullName}`));
3881
+ console.log(chalk10.dim(` Context storage: RepoWise servers`));
3882
+ console.log(chalk10.dim(` Delivery mode: ${currentDelivery}`));
3883
+ console.log(chalk10.dim(` Monitored branch: ${currentBranch}`));
3099
3884
  console.log("");
3100
3885
  const setting = await select({
3101
3886
  message: "What would you like to change?",
@@ -3115,7 +3900,7 @@ async function config() {
3115
3900
  default: currentDelivery
3116
3901
  });
3117
3902
  if (newMode === currentDelivery) {
3118
- console.log(chalk9.dim("No change."));
3903
+ console.log(chalk10.dim("No change."));
3119
3904
  return;
3120
3905
  }
3121
3906
  patch.deliveryMode = newMode;
@@ -3126,7 +3911,7 @@ async function config() {
3126
3911
  default: currentBranch
3127
3912
  });
3128
3913
  if (newBranch === currentBranch) {
3129
- console.log(chalk9.dim("No change."));
3914
+ console.log(chalk10.dim("No change."));
3130
3915
  return;
3131
3916
  }
3132
3917
  patch.monitoredBranch = newBranch;
@@ -3136,29 +3921,29 @@ async function config() {
3136
3921
  method: "PATCH",
3137
3922
  body: JSON.stringify(patch)
3138
3923
  });
3139
- spinner.succeed(chalk9.green("Setting updated"));
3924
+ spinner.succeed(chalk10.green("Setting updated"));
3140
3925
  } catch (err) {
3141
3926
  spinner.stop();
3142
3927
  const message = err instanceof Error ? err.message : "Config failed";
3143
- console.error(chalk9.red(message));
3928
+ console.error(chalk10.red(message));
3144
3929
  process.exitCode = 1;
3145
3930
  }
3146
3931
  }
3147
3932
 
3148
3933
  // bin/repowise.ts
3149
3934
  var __filename = fileURLToPath3(import.meta.url);
3150
- var __dirname = dirname6(__filename);
3151
- var pkg = JSON.parse(readFileSync2(join16(__dirname, "..", "..", "package.json"), "utf-8"));
3935
+ var __dirname = dirname9(__filename);
3936
+ var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "..", "package.json"), "utf-8"));
3152
3937
  var program = new Command();
3153
- program.name("repowise").description("AI-optimized codebase context generator").version(pkg.version).option("--staging", "Use the staging environment").hook("preAction", async () => {
3154
- if (program.opts()["staging"]) {
3155
- setStagingMode(true);
3156
- }
3938
+ program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
3157
3939
  await showWelcome(pkg.version);
3158
3940
  });
3159
3941
  program.command("create").description("Create context for a repository").action(async () => {
3160
3942
  await create();
3161
3943
  });
3944
+ program.command("member").description("Download context and configure AI tools (for team members)").action(async () => {
3945
+ await member();
3946
+ });
3162
3947
  program.command("login").description("Authenticate with RepoWise").option("--no-browser", "Print login URL instead of opening browser").action(async (options) => {
3163
3948
  await login(options);
3164
3949
  });
@@ -3190,7 +3975,7 @@ if (process.argv[2] === "__listener") {
3190
3975
  process.on("unhandledRejection", (reason) => {
3191
3976
  console.error("Listener unhandled rejection:", reason);
3192
3977
  });
3193
- startListener({ skipPidCheck: true }).catch((err) => {
3978
+ startListener().catch((err) => {
3194
3979
  console.error("Listener fatal error:", err);
3195
3980
  process.exitCode = 1;
3196
3981
  });