repowise 0.1.82 → 0.1.84

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 +1549 -763
  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,820 @@ 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();
1423
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
679
1424
  for (const repoId of allRepoIds) {
680
1425
  if (!state.repos[repoId]) {
681
- state.repos[repoId] = { lastSyncTimestamp: now, lastSyncCommitSha: null };
1426
+ state.repos[repoId] = { lastSyncTimestamp: oneDayAgo, lastSyncCommitSha: null };
682
1427
  }
683
1428
  }
1429
+ await saveState(state);
1430
+ const packageName = true ? "repowise" : "repowise";
1431
+ let currentVersion = "";
1432
+ try {
1433
+ const selfDir = dirname4(fileURLToPath2(import.meta.url));
1434
+ const pkgJsonPath = join12(selfDir, "..", "..", "package.json");
1435
+ const pkgJson = JSON.parse(await readFile5(pkgJsonPath, "utf-8"));
1436
+ currentVersion = pkgJson.version;
1437
+ } catch (err) {
1438
+ console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
1439
+ }
684
1440
  let pollIntervalMs = 5e3;
685
1441
  let pollCycleCount = 0;
686
1442
  const RECONCILE_EVERY_N_CYCLES = 60;
@@ -700,10 +1456,24 @@ async function startListener(options) {
700
1456
  console.log("Shutting down...");
701
1457
  stop();
702
1458
  await saveState(state);
703
- await removePidFile();
1459
+ if (releaseLock) {
1460
+ try {
1461
+ await releaseLock();
1462
+ } catch {
1463
+ }
1464
+ releaseLock = null;
1465
+ }
704
1466
  };
705
- process.on("SIGTERM", () => void shutdown());
706
- process.on("SIGINT", () => void shutdown());
1467
+ process.on("SIGTERM", () => {
1468
+ shutdown().catch((err) => {
1469
+ console.error("Shutdown error:", err);
1470
+ }).finally(() => process.exit(0));
1471
+ });
1472
+ process.on("SIGINT", () => {
1473
+ shutdown().catch((err) => {
1474
+ console.error("Shutdown error:", err);
1475
+ }).finally(() => process.exit(0));
1476
+ });
707
1477
  while (running) {
708
1478
  let anyAuthError = null;
709
1479
  let authErrorGroup = null;
@@ -711,6 +1481,7 @@ async function startListener(options) {
711
1481
  let connectionLostNotified = false;
712
1482
  pollCycleCount++;
713
1483
  const shouldReconcile = pollCycleCount % RECONCILE_EVERY_N_CYCLES === 0;
1484
+ let latestCliVersion;
714
1485
  for (const group of groups) {
715
1486
  if (!running)
716
1487
  break;
@@ -725,9 +1496,14 @@ async function startListener(options) {
725
1496
  const response = await group.pollClient.poll(group.repoIds, sinceTimestamp, {
726
1497
  includeActiveRepos: shouldReconcile
727
1498
  });
1499
+ if (response.latestCliVersion) {
1500
+ latestCliVersion = response.latestCliVersion;
1501
+ }
728
1502
  if (response.activeRepos && response.activeRepos.length > 0) {
729
1503
  try {
730
- const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl);
1504
+ const result = reconcileRepos(config2.repos, response.activeRepos, state, group.apiUrl, {
1505
+ autoDiscover: config2.autoDiscoverRepos
1506
+ });
731
1507
  if (result.updated) {
732
1508
  config2.repos = result.repos;
733
1509
  await saveListenerConfig(config2);
@@ -742,6 +1518,19 @@ async function startListener(options) {
742
1518
  for (const change of result.changes) {
743
1519
  console.log(`[reconcile] ${change}`);
744
1520
  }
1521
+ if (result.addedRepos.length > 0) {
1522
+ for (const addedRepo of result.addedRepos) {
1523
+ const apiUrl = addedRepo.apiUrl ?? config2.defaultApiUrl;
1524
+ try {
1525
+ const fetchResult = await fetchContextFromServer(addedRepo.repoId, addedRepo.localPath, apiUrl);
1526
+ if (fetchResult.success) {
1527
+ console.log(`[self-heal] Downloaded ${fetchResult.updatedFiles.length} context files for ${addedRepo.repoId}`);
1528
+ }
1529
+ } catch (fetchErr) {
1530
+ console.warn(`[self-heal] Context download failed for ${addedRepo.repoId}:`, fetchErr instanceof Error ? fetchErr.message : String(fetchErr));
1531
+ }
1532
+ }
1533
+ }
745
1534
  }
746
1535
  } catch (reconcileErr) {
747
1536
  console.warn("Repo reconciliation failed \u2014 will retry next cycle", reconcileErr instanceof Error ? reconcileErr.message : String(reconcileErr));
@@ -783,6 +1572,49 @@ async function startListener(options) {
783
1572
  console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay2 / 1e3)}s`);
784
1573
  }
785
1574
  }
1575
+ if (shouldReconcile) {
1576
+ try {
1577
+ const staleDirty = await checkStaleContext(config2.repos, state, groups);
1578
+ if (staleDirty) {
1579
+ await saveState(state);
1580
+ }
1581
+ } catch (err) {
1582
+ console.warn("[self-heal] Stale context check failed:", err instanceof Error ? err.message : String(err));
1583
+ }
1584
+ }
1585
+ if (latestCliVersion && currentVersion && !config2.noAutoUpdate && (state.crashCount ?? 0) < CRASH_LOOP_THRESHOLD) {
1586
+ if (latestCliVersion !== state.lastUpdateTargetVersion) {
1587
+ state.updateFailCount = 0;
1588
+ state.lastUpdateAttemptAt = void 0;
1589
+ state.lastUpdateTargetVersion = latestCliVersion;
1590
+ }
1591
+ const backoffMs = Math.min(5 * 60 * 1e3 * Math.pow(2, state.updateFailCount ?? 0), 60 * 60 * 1e3);
1592
+ const lastAttempt = state.lastUpdateAttemptAt ? new Date(state.lastUpdateAttemptAt).getTime() : 0;
1593
+ if (Date.now() - lastAttempt >= backoffMs) {
1594
+ const listenerStatus = await getListenerStatus();
1595
+ state.lastUpdateAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
1596
+ const updateResult = await installUpdate(currentVersion, packageName, latestCliVersion);
1597
+ if (updateResult.updated) {
1598
+ state.updateFailCount = 0;
1599
+ await saveState(state);
1600
+ if (listenerStatus.serviceInstalled) {
1601
+ console.log("[auto-update] Restarting with new version...");
1602
+ await shutdown();
1603
+ process.exit(0);
1604
+ } else {
1605
+ console.log("[auto-update] Spawning new listener with updated version...");
1606
+ await shutdown();
1607
+ await startBackground();
1608
+ process.exit(0);
1609
+ }
1610
+ } else if (updateResult.error) {
1611
+ state.updateFailCount = (state.updateFailCount ?? 0) + 1;
1612
+ await saveState(state);
1613
+ } else {
1614
+ await saveState(state);
1615
+ }
1616
+ }
1617
+ }
786
1618
  if (anyAuthError) {
787
1619
  let recovered = false;
788
1620
  for (let attempt = 1; attempt <= 3; attempt++) {
@@ -812,8 +1644,19 @@ async function startListener(options) {
812
1644
  } catch {
813
1645
  }
814
1646
  }
1647
+ const credentialsPath = join12(getConfigDir(), "credentials.json");
1648
+ let credentialsChanged = false;
1649
+ let watcher = null;
1650
+ try {
1651
+ const fs = await import("fs");
1652
+ watcher = fs.watch(credentialsPath, () => {
1653
+ credentialsChanged = true;
1654
+ });
1655
+ } catch {
1656
+ }
815
1657
  while (running) {
816
- await sleep(5 * 60 * 1e3);
1658
+ await sleep(credentialsChanged ? 0 : 1e4);
1659
+ credentialsChanged = false;
817
1660
  if (!running)
818
1661
  break;
819
1662
  const fresh = await getValidCredentials({ forceRefresh: true });
@@ -822,452 +1665,170 @@ async function startListener(options) {
822
1665
  break;
823
1666
  }
824
1667
  }
1668
+ if (watcher)
1669
+ watcher.close();
825
1670
  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));
1671
+ }
1672
+ pollIntervalMs = minPollInterval;
1673
+ await sleep(pollIntervalMs);
1001
1674
  }
1002
- if (isAlive(pid)) {
1675
+ if (releaseLock) {
1003
1676
  try {
1004
- process.kill(pid, "SIGKILL");
1677
+ await releaseLock();
1005
1678
  } catch {
1006
1679
  }
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"] };
1680
+ releaseLock = null;
1045
1681
  }
1046
1682
  }
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
- });
1683
+ var isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
1684
+ if (isDirectRun) {
1685
+ startListener().catch((err) => {
1686
+ console.error("Listener fatal error:", err);
1687
+ process.exitCode = 1;
1056
1688
  });
1057
1689
  }
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
- }
1690
+
1691
+ // src/lib/env.ts
1692
+ import { homedir as homedir4 } from "os";
1693
+ import { join as join13 } from "path";
1694
+ var IS_STAGING2 = true ? false : false;
1695
+ var PRODUCTION = {
1696
+ apiUrl: "https://api.repowise.ai",
1697
+ cognitoDomain: "auth.repowise.ai",
1698
+ cognitoClientId: "50so1fkmjbqt1ufnsmbhsjbtst",
1699
+ cognitoRegion: "us-east-1",
1700
+ customDomain: true
1701
+ };
1702
+ var STAGING = {
1703
+ apiUrl: "https://staging-api.repowise.ai",
1704
+ cognitoDomain: "auth-staging.repowise.ai",
1705
+ cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
1706
+ cognitoRegion: "us-east-1",
1707
+ customDomain: true
1708
+ };
1709
+ function getEnvConfig() {
1710
+ return IS_STAGING2 ? STAGING : PRODUCTION;
1112
1711
  }
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
- }
1712
+ function getConfigDir2() {
1713
+ return join13(homedir4(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
1120
1714
  }
1121
- var SYSTEMD_SERVICE = "repowise-listener";
1122
- function unitPath() {
1123
- return join9(homedir7(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
1715
+ function getPackageName() {
1716
+ return true ? "repowise" : "repowise";
1124
1717
  }
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
1718
 
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")}
1719
+ // src/lib/welcome.ts
1720
+ import chalk from "chalk";
1141
1721
 
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
- }
1722
+ // src/lib/config.ts
1723
+ import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir8, rename as rename3, unlink as unlink6 } from "fs/promises";
1724
+ import { join as join14 } from "path";
1725
+ import lockfile3 from "proper-lockfile";
1726
+ async function getConfig() {
1166
1727
  try {
1167
- await exec("systemctl", ["--user", "daemon-reload"]);
1728
+ const data = await readFile6(join14(getConfigDir2(), "config.json"), "utf-8");
1729
+ return JSON.parse(data);
1168
1730
  } catch {
1731
+ return {};
1169
1732
  }
1170
1733
  }
1171
- async function linuxIsInstalled() {
1734
+ async function saveConfig(config2) {
1735
+ const dir = getConfigDir2();
1736
+ const path = join14(dir, "config.json");
1737
+ await mkdir8(dir, { recursive: true });
1738
+ const tmpPath = path + ".tmp";
1172
1739
  try {
1173
- const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
1174
- return stdout.trim() === "enabled";
1175
- } catch {
1176
- return false;
1740
+ await writeFile8(tmpPath, JSON.stringify(config2, null, 2));
1741
+ await rename3(tmpPath, path);
1742
+ } catch (err) {
1743
+ try {
1744
+ await unlink6(tmpPath);
1745
+ } catch {
1746
+ }
1747
+ throw err;
1177
1748
  }
1178
1749
  }
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
- }
1750
+ async function mergeAndSaveConfig(updates) {
1751
+ const dir = getConfigDir2();
1752
+ const path = join14(dir, "config.json");
1753
+ await mkdir8(dir, { recursive: true });
1203
1754
  try {
1204
- await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
1755
+ await writeFile8(path, "", { flag: "a" });
1205
1756
  } catch {
1206
1757
  }
1207
- }
1208
- async function win32IsInstalled() {
1758
+ let release = null;
1209
1759
  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}`);
1760
+ release = await lockfile3.lock(path, { stale: 1e4, retries: 3, realpath: false });
1761
+ let raw = {};
1762
+ try {
1763
+ raw = JSON.parse(await readFile6(path, "utf-8"));
1764
+ } catch {
1765
+ }
1766
+ const merged = { ...raw, ...updates };
1767
+ if (updates.repos) {
1768
+ const existingRepos = Array.isArray(raw["repos"]) ? raw["repos"] : [];
1769
+ const updatedRepoIds = new Set(updates.repos.map((r) => r.repoId));
1770
+ merged.repos = [
1771
+ ...existingRepos.filter((r) => !updatedRepoIds.has(r.repoId)),
1772
+ ...updates.repos
1773
+ ];
1774
+ }
1775
+ await saveConfig(merged);
1776
+ } finally {
1777
+ if (release) {
1778
+ try {
1779
+ await release();
1780
+ } catch {
1781
+ }
1782
+ }
1229
1783
  }
1230
1784
  }
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
- }
1785
+
1786
+ // src/lib/welcome.ts
1787
+ var W = 41;
1788
+ function row(styled, visible) {
1789
+ return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
1245
1790
  }
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;
1791
+ async function showWelcome(currentVersion) {
1792
+ try {
1793
+ const config2 = await getConfig();
1794
+ if (config2.lastSeenVersion === currentVersion) {
1795
+ return;
1796
+ }
1797
+ const isUpgrade = !!config2.lastSeenVersion;
1798
+ const tag = isUpgrade ? "updated" : "installed";
1799
+ const titleText = `RepoWise v${currentVersion}`;
1800
+ const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
1801
+ const titleVisible = 3 + titleText.length + 3 + tag.length;
1802
+ const border = "\u2500".repeat(W);
1803
+ console.log("");
1804
+ console.log(chalk.cyan(` \u256D${border}\u256E`));
1805
+ console.log(row("", 0));
1806
+ console.log(row(titleStyled, titleVisible));
1807
+ console.log(row("", 0));
1808
+ console.log(row(" " + chalk.dim("Get started:"), 15));
1809
+ console.log(row(" $ " + chalk.bold("repowise create"), 22));
1810
+ console.log(row("", 0));
1811
+ console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
1812
+ console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
1813
+ console.log(row("", 0));
1814
+ console.log(chalk.cyan(` \u2570${border}\u256F`));
1815
+ console.log("");
1816
+ await mergeAndSaveConfig({ lastSeenVersion: currentVersion });
1817
+ } catch {
1256
1818
  }
1257
1819
  }
1258
1820
 
1259
1821
  // src/commands/create.ts
1822
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
1823
+ import { dirname as dirname6, join as join18 } from "path";
1260
1824
  import chalk5 from "chalk";
1261
1825
  import ora from "ora";
1262
1826
 
1263
1827
  // src/lib/auth.ts
1264
1828
  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";
1829
+ import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
1266
1830
  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");
1831
+ import { join as join15 } from "path";
1271
1832
  var CLI_CALLBACK_PORT = 19876;
1272
1833
  var CALLBACK_TIMEOUT_MS = 12e4;
1273
1834
  function getCognitoConfigForStorage() {
@@ -1433,7 +1994,8 @@ async function refreshTokens2(refreshToken) {
1433
1994
  }
1434
1995
  async function getStoredCredentials2() {
1435
1996
  try {
1436
- const data = await readFile7(CREDENTIALS_PATH2, "utf-8");
1997
+ const credPath = join15(getConfigDir2(), "credentials.json");
1998
+ const data = await readFile7(credPath, "utf-8");
1437
1999
  return JSON.parse(data);
1438
2000
  } catch (err) {
1439
2001
  if (err.code === "ENOENT" || err instanceof SyntaxError) {
@@ -1443,13 +2005,15 @@ async function getStoredCredentials2() {
1443
2005
  }
1444
2006
  }
1445
2007
  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);
2008
+ const dir = getConfigDir2();
2009
+ const credPath = join15(dir, "credentials.json");
2010
+ await mkdir9(dir, { recursive: true, mode: 448 });
2011
+ await writeFile9(credPath, JSON.stringify(credentials, null, 2));
2012
+ await chmod4(credPath, 384);
1449
2013
  }
1450
2014
  async function clearCredentials() {
1451
2015
  try {
1452
- await unlink4(CREDENTIALS_PATH2);
2016
+ await unlink7(join15(getConfigDir2(), "credentials.json"));
1453
2017
  } catch (err) {
1454
2018
  if (err.code !== "ENOENT") throw err;
1455
2019
  }
@@ -1641,7 +2205,7 @@ async function selectAiTools() {
1641
2205
 
1642
2206
  // src/lib/ai-tools.ts
1643
2207
  import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir10, readdir } from "fs/promises";
1644
- import { join as join11, dirname as dirname3 } from "path";
2208
+ import { join as join16, dirname as dirname5 } from "path";
1645
2209
  var AI_TOOL_CONFIG = {
1646
2210
  cursor: {
1647
2211
  label: "Cursor",
@@ -1770,8 +2334,8 @@ function generateReference(tool, repoName, contextFolder, contextFiles) {
1770
2334
  }
1771
2335
  async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
1772
2336
  const config2 = AI_TOOL_CONFIG[tool];
1773
- const fullPath = join11(repoRoot, config2.filePath);
1774
- const dir = dirname3(fullPath);
2337
+ const fullPath = join16(repoRoot, config2.filePath);
2338
+ const dir = dirname5(fullPath);
1775
2339
  if (dir !== repoRoot) {
1776
2340
  await mkdir10(dir, { recursive: true });
1777
2341
  }
@@ -1799,14 +2363,14 @@ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, context
1799
2363
  return { created };
1800
2364
  }
1801
2365
  async function scanLocalContextFiles(repoRoot, contextFolder) {
1802
- const folderPath = join11(repoRoot, contextFolder);
2366
+ const folderPath = join16(repoRoot, contextFolder);
1803
2367
  try {
1804
2368
  const entries = await readdir(folderPath, { withFileTypes: true, recursive: true });
1805
2369
  const results = [];
1806
2370
  for (const entry of entries) {
1807
2371
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1808
2372
  const parentDir = entry.parentPath ?? folderPath;
1809
- const fullPath = join11(parentDir, entry.name);
2373
+ const fullPath = join16(parentDir, entry.name);
1810
2374
  const relFromContext = fullPath.slice(folderPath.length + 1);
1811
2375
  results.push({
1812
2376
  fileName: relFromContext,
@@ -1822,9 +2386,9 @@ async function scanLocalContextFiles(repoRoot, contextFolder) {
1822
2386
 
1823
2387
  // src/lib/gitignore.ts
1824
2388
  import { readFileSync, writeFileSync, existsSync } from "fs";
1825
- import { join as join12 } from "path";
2389
+ import { join as join17 } from "path";
1826
2390
  function ensureGitignore(repoRoot, entry) {
1827
- const gitignorePath = join12(repoRoot, ".gitignore");
2391
+ const gitignorePath = join17(repoRoot, ".gitignore");
1828
2392
  if (existsSync(gitignorePath)) {
1829
2393
  const content = readFileSync(gitignorePath, "utf-8");
1830
2394
  const lines = content.split("\n").map((l) => l.trim());
@@ -1838,6 +2402,24 @@ function ensureGitignore(repoRoot, entry) {
1838
2402
  }
1839
2403
  }
1840
2404
 
2405
+ // src/lib/git.ts
2406
+ import { execSync } from "child_process";
2407
+ function detectRepoRoot() {
2408
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
2409
+ }
2410
+ function detectRepoName(repoRoot) {
2411
+ try {
2412
+ const remoteUrl = execSync("git remote get-url origin", {
2413
+ encoding: "utf-8",
2414
+ cwd: repoRoot
2415
+ }).trim();
2416
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
2417
+ if (match?.[1]) return match[1];
2418
+ } catch {
2419
+ }
2420
+ return repoRoot.split("/").pop() ?? "unknown";
2421
+ }
2422
+
1841
2423
  // src/lib/interview-handler.ts
1842
2424
  import chalk3 from "chalk";
1843
2425
  import { input } from "@inquirer/prompts";
@@ -2081,7 +2663,9 @@ var ProgressRenderer = class {
2081
2663
  spinner.stop();
2082
2664
  console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
2083
2665
  console.log(
2084
- chalk4.cyan(" \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!")
2666
+ chalk4.cyan(
2667
+ " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2668
+ )
2085
2669
  );
2086
2670
  console.log("");
2087
2671
  spinner.start();
@@ -2247,21 +2831,6 @@ var ProgressRenderer = class {
2247
2831
  };
2248
2832
 
2249
2833
  // 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
2834
  function formatElapsed(ms) {
2266
2835
  const totalSeconds = Math.round(ms / 1e3);
2267
2836
  const minutes = Math.floor(totalSeconds / 60);
@@ -2354,12 +2923,18 @@ async function create() {
2354
2923
  console.log(
2355
2924
  chalk5.cyan(
2356
2925
  `
2357
- To trigger a new full scan, visit the dashboard:
2358
- https://app.repowise.ai/repos/${repoId}
2926
+ If you're a team member, run: ${chalk5.bold("repowise member")}
2927
+ This will download context and configure your AI tools.
2928
+ `
2929
+ )
2930
+ );
2931
+ console.log(
2932
+ chalk5.dim(
2933
+ ` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
2934
+ To sync recent changes, use: repowise sync
2359
2935
  `
2360
2936
  )
2361
2937
  );
2362
- console.log(chalk5.dim(" To sync recent changes, use: repowise sync\n"));
2363
2938
  process.exitCode = 1;
2364
2939
  return;
2365
2940
  }
@@ -2487,7 +3062,7 @@ async function create() {
2487
3062
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
2488
3063
  const files = listResult.data?.files ?? listResult.files ?? [];
2489
3064
  if (files.length > 0) {
2490
- const contextDir = join13(repoRoot, DEFAULT_CONTEXT_FOLDER);
3065
+ const contextDir = join18(repoRoot, DEFAULT_CONTEXT_FOLDER);
2491
3066
  mkdirSync(contextDir, { recursive: true });
2492
3067
  let downloadedCount = 0;
2493
3068
  let failedCount = 0;
@@ -2501,8 +3076,8 @@ async function create() {
2501
3076
  const response = await fetch(presignedUrl);
2502
3077
  if (response.ok) {
2503
3078
  const content = await response.text();
2504
- const filePath = join13(contextDir, file.fileName);
2505
- mkdirSync(dirname4(filePath), { recursive: true });
3079
+ const filePath = join18(contextDir, file.fileName);
3080
+ mkdirSync(dirname6(filePath), { recursive: true });
2506
3081
  writeFileSync2(filePath, content, "utf-8");
2507
3082
  downloadedCount++;
2508
3083
  } else {
@@ -2563,33 +3138,27 @@ Files are stored on our servers (not in git). Retry when online.`
2563
3138
  spinner.succeed("AI tools configured");
2564
3139
  console.log(chalk5.dim(results.join("\n")));
2565
3140
  }
2566
- const existingConfig = await getConfig();
2567
- const existingRepos = existingConfig.repos ?? [];
2568
- const updatedRepos = existingRepos.filter((r) => r.repoId !== repoId);
3141
+ const updatedRepos = [];
2569
3142
  if (repoRoot) {
2570
3143
  const repoEntry = {
2571
3144
  repoId,
2572
- localPath: repoRoot
3145
+ localPath: repoRoot,
3146
+ apiUrl: getEnvConfig().apiUrl
2573
3147
  };
2574
- if (isStagingMode()) {
2575
- repoEntry.apiUrl = getEnvConfig().apiUrl;
2576
- }
2577
3148
  if (repoPlatform) repoEntry.platform = repoPlatform;
2578
3149
  if (repoExternalId) repoEntry.externalId = repoExternalId;
2579
3150
  updatedRepos.push(repoEntry);
2580
3151
  }
2581
- await saveConfig({
2582
- ...existingConfig,
2583
- aiTools: tools,
2584
- contextFolder,
2585
- repos: updatedRepos
2586
- });
3152
+ const configUpdate = { aiTools: tools, contextFolder };
3153
+ if (updatedRepos.length > 0) configUpdate.repos = updatedRepos;
3154
+ await mergeAndSaveConfig(configUpdate);
2587
3155
  let listenerRunning = false;
2588
3156
  try {
2589
- await install();
2590
- await startBackground();
3157
+ await ensureListenerRunning();
2591
3158
  listenerRunning = true;
2592
3159
  } catch {
3160
+ }
3161
+ if (!listenerRunning) {
2593
3162
  console.log(
2594
3163
  chalk5.yellow(
2595
3164
  "Warning: Could not start listener automatically. Run the following to enable it:"
@@ -2626,11 +3195,255 @@ Files are stored on our servers (not in git). Retry when online.`
2626
3195
  }
2627
3196
  }
2628
3197
 
2629
- // src/commands/login.ts
3198
+ // src/commands/member.ts
3199
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
3200
+ import { dirname as dirname7, join as join19 } from "path";
2630
3201
  import chalk6 from "chalk";
2631
3202
  import ora2 from "ora";
3203
+ var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
3204
+ function formatElapsed2(ms) {
3205
+ const totalSeconds = Math.round(ms / 1e3);
3206
+ const minutes = Math.floor(totalSeconds / 60);
3207
+ const seconds = totalSeconds % 60;
3208
+ if (minutes === 0) return `${seconds}s`;
3209
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
3210
+ }
3211
+ async function member() {
3212
+ const startTime = Date.now();
3213
+ const spinner = ora2("Checking authentication...").start();
3214
+ try {
3215
+ let credentials = await getValidCredentials2();
3216
+ if (!credentials) {
3217
+ spinner.info(chalk6.yellow("Not logged in. Opening browser to authenticate..."));
3218
+ credentials = await performLogin();
3219
+ const { email } = decodeIdToken(credentials.idToken);
3220
+ spinner.succeed(chalk6.green(`Authenticated as ${chalk6.bold(email)}`));
3221
+ } else {
3222
+ spinner.succeed("Authenticated");
3223
+ }
3224
+ let repoId;
3225
+ let repoName;
3226
+ let repoRoot;
3227
+ let repoPlatform;
3228
+ let repoExternalId;
3229
+ spinner.start("Checking for pending repository...");
3230
+ try {
3231
+ const pending = await apiRequest("/v1/onboarding/pending");
3232
+ if (pending?.repoId) {
3233
+ repoId = pending.repoId;
3234
+ repoName = pending.repoName;
3235
+ spinner.succeed(`Found pending repository: ${chalk6.bold(repoName)}`);
3236
+ apiRequest("/v1/onboarding/pending", { method: "DELETE" }).catch(() => {
3237
+ });
3238
+ }
3239
+ } catch {
3240
+ }
3241
+ if (!repoId) {
3242
+ spinner.text = "Detecting repository...";
3243
+ try {
3244
+ repoRoot = detectRepoRoot();
3245
+ repoName = detectRepoName(repoRoot);
3246
+ spinner.succeed(`Repository: ${chalk6.bold(repoName)}`);
3247
+ } catch {
3248
+ spinner.fail(
3249
+ chalk6.red("Not in a git repository. Run this command from your project directory.")
3250
+ );
3251
+ process.exitCode = 1;
3252
+ return;
3253
+ }
3254
+ } else {
3255
+ try {
3256
+ repoRoot = detectRepoRoot();
3257
+ const localRepoName = detectRepoName(repoRoot);
3258
+ if (repoName && localRepoName !== repoName && !repoName.endsWith(`/${localRepoName}`)) {
3259
+ spinner.warn(
3260
+ chalk6.yellow(
3261
+ `Pending repo is ${chalk6.bold(repoName)} but you're in ${chalk6.bold(localRepoName)}.
3262
+ Make sure you're in the right project directory, or the context files will be saved here.`
3263
+ )
3264
+ );
3265
+ }
3266
+ } catch {
3267
+ spinner.fail(
3268
+ chalk6.red(
3269
+ "Please navigate to your project directory first, then run `repowise member` again."
3270
+ )
3271
+ );
3272
+ process.exitCode = 1;
3273
+ return;
3274
+ }
3275
+ }
3276
+ if (!repoId || !repoPlatform) {
3277
+ spinner.start("Looking up repository...");
3278
+ try {
3279
+ const repos = await apiRequest("/v1/repos");
3280
+ const match = repos.find(
3281
+ (r) => r.repoId === repoId || r.name === repoName || r.fullName.endsWith(`/${repoName}`)
3282
+ );
3283
+ if (match) {
3284
+ repoId = match.repoId;
3285
+ repoPlatform = match.platform;
3286
+ repoExternalId = match.externalId;
3287
+ if (!repoName) repoName = match.fullName;
3288
+ spinner.succeed(`Repository: ${chalk6.bold(match.fullName)}`);
3289
+ }
3290
+ } catch {
3291
+ }
3292
+ }
3293
+ if (!repoId) {
3294
+ spinner.fail(
3295
+ chalk6.red(
3296
+ "This repository is not connected to your team. Ask your team admin to add it on the dashboard."
3297
+ )
3298
+ );
3299
+ process.exitCode = 1;
3300
+ return;
3301
+ }
3302
+ spinner.start("Checking for context files...");
3303
+ let files = [];
3304
+ try {
3305
+ const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
3306
+ files = listResult.data?.files ?? listResult.files ?? [];
3307
+ } catch {
3308
+ spinner.fail(chalk6.red("Failed to check context files on server."));
3309
+ process.exitCode = 1;
3310
+ return;
3311
+ }
3312
+ if (files.length === 0) {
3313
+ spinner.fail(
3314
+ chalk6.red(
3315
+ "No context files found for this repository. Your team admin needs to run the initial scan first."
3316
+ )
3317
+ );
3318
+ process.exitCode = 1;
3319
+ return;
3320
+ }
3321
+ spinner.succeed(`Found ${chalk6.bold(files.length)} context files on server`);
3322
+ const { tools } = await selectAiTools();
3323
+ spinner.start("Downloading context files...");
3324
+ const contextDir = join19(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3325
+ mkdirSync2(contextDir, { recursive: true });
3326
+ let downloadedCount = 0;
3327
+ let failedCount = 0;
3328
+ for (const file of files) {
3329
+ if (file.fileName.includes("..")) {
3330
+ failedCount++;
3331
+ continue;
3332
+ }
3333
+ try {
3334
+ const urlResult = await apiRequest(`/v1/repos/${repoId}/context/files/${file.fileName}`);
3335
+ const presignedUrl = urlResult.data?.url ?? urlResult.url;
3336
+ const response = await fetch(presignedUrl);
3337
+ if (response.ok) {
3338
+ const content = await response.text();
3339
+ const filePath = join19(contextDir, file.fileName);
3340
+ mkdirSync2(dirname7(filePath), { recursive: true });
3341
+ writeFileSync3(filePath, content, "utf-8");
3342
+ downloadedCount++;
3343
+ } else {
3344
+ failedCount++;
3345
+ }
3346
+ } catch {
3347
+ failedCount++;
3348
+ }
3349
+ }
3350
+ if (failedCount > 0) {
3351
+ spinner.warn(
3352
+ `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
3353
+ );
3354
+ } else {
3355
+ spinner.succeed(`Downloaded ${downloadedCount} files to ./${DEFAULT_CONTEXT_FOLDER2}/`);
3356
+ }
3357
+ try {
3358
+ ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3359
+ } catch {
3360
+ }
3361
+ if (tools.length > 0) {
3362
+ spinner.start("Configuring AI tools...");
3363
+ const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3364
+ const configured = [];
3365
+ for (const tool of tools) {
3366
+ const { created } = await updateToolConfig(
3367
+ repoRoot,
3368
+ tool,
3369
+ repoName,
3370
+ DEFAULT_CONTEXT_FOLDER2,
3371
+ contextFiles
3372
+ );
3373
+ const config2 = AI_TOOL_CONFIG[tool];
3374
+ configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
3375
+ }
3376
+ spinner.succeed("AI tools configured");
3377
+ for (const msg of configured) {
3378
+ console.log(chalk6.dim(` ${msg}`));
3379
+ }
3380
+ }
3381
+ spinner.start("Saving configuration...");
3382
+ const repoEntry = {
3383
+ repoId,
3384
+ localPath: repoRoot,
3385
+ apiUrl: getEnvConfig().apiUrl
3386
+ };
3387
+ if (repoPlatform) repoEntry.platform = repoPlatform;
3388
+ if (repoExternalId) repoEntry.externalId = repoExternalId;
3389
+ await mergeAndSaveConfig({
3390
+ aiTools: tools,
3391
+ contextFolder: DEFAULT_CONTEXT_FOLDER2,
3392
+ repos: [repoEntry]
3393
+ });
3394
+ spinner.succeed("Configuration saved");
3395
+ let listenerRunning = false;
3396
+ try {
3397
+ await ensureListenerRunning();
3398
+ listenerRunning = true;
3399
+ } catch {
3400
+ }
3401
+ try {
3402
+ await apiRequest("/v1/onboarding/complete", {
3403
+ method: "POST",
3404
+ body: JSON.stringify({ repoId })
3405
+ });
3406
+ } catch {
3407
+ }
3408
+ const elapsed = formatElapsed2(Date.now() - startTime);
3409
+ console.log("");
3410
+ console.log(chalk6.green.bold("All done! Context downloaded and configured."));
3411
+ console.log("");
3412
+ console.log(`Your AI tools now have access to project context for ${chalk6.bold(repoName)}.`);
3413
+ console.log(
3414
+ `Downloaded ${chalk6.bold(downloadedCount)} context files to ./${DEFAULT_CONTEXT_FOLDER2}/`
3415
+ );
3416
+ console.log("");
3417
+ if (listenerRunning) {
3418
+ console.log(
3419
+ chalk6.dim(
3420
+ "The RepoWise listener is running in the background \u2014\nyour context will stay in sync automatically."
3421
+ )
3422
+ );
3423
+ } else {
3424
+ console.log(
3425
+ chalk6.yellow(
3426
+ "Could not start listener automatically. Run `repowise listen --install` to enable auto-sync."
3427
+ )
3428
+ );
3429
+ }
3430
+ console.log("");
3431
+ console.log(
3432
+ chalk6.dim(`Head back to the dashboard and click "Complete Onboarding" to finish setup.`)
3433
+ );
3434
+ console.log(chalk6.dim(`Total time: ${elapsed}`));
3435
+ } catch (err) {
3436
+ const message = err instanceof Error ? err.message : "Unknown error";
3437
+ spinner.fail(chalk6.red(`Setup failed: ${message}`));
3438
+ process.exitCode = 1;
3439
+ }
3440
+ }
3441
+
3442
+ // src/commands/login.ts
3443
+ import chalk7 from "chalk";
3444
+ import ora3 from "ora";
2632
3445
  async function login(options = {}) {
2633
- const spinner = ora2("Preparing login...").start();
3446
+ const spinner = ora3("Preparing login...").start();
2634
3447
  try {
2635
3448
  const codeVerifier = generateCodeVerifier();
2636
3449
  const codeChallenge = generateCodeChallenge(codeVerifier);
@@ -2642,7 +3455,7 @@ async function login(options = {}) {
2642
3455
  console.log(`
2643
3456
  Open this URL in your browser to authenticate:
2644
3457
  `);
2645
- console.log(chalk6.cyan(authorizeUrl));
3458
+ console.log(chalk7.cyan(authorizeUrl));
2646
3459
  console.log(`
2647
3460
  Waiting for authentication...`);
2648
3461
  } else {
@@ -2656,7 +3469,7 @@ Waiting for authentication...`);
2656
3469
  console.log(`
2657
3470
  Could not open browser automatically. Open this URL:
2658
3471
  `);
2659
- console.log(chalk6.cyan(authorizeUrl));
3472
+ console.log(chalk7.cyan(authorizeUrl));
2660
3473
  console.log(`
2661
3474
  Waiting for authentication...`);
2662
3475
  }
@@ -2670,60 +3483,50 @@ Waiting for authentication...`);
2670
3483
  credentials.cognito = getCognitoConfigForStorage();
2671
3484
  await storeCredentials2(credentials);
2672
3485
  const { email } = decodeIdToken(credentials.idToken);
2673
- spinner.succeed(chalk6.green(`Logged in as ${chalk6.bold(email)}`));
3486
+ spinner.succeed(chalk7.green(`Logged in as ${chalk7.bold(email)}`));
2674
3487
  } catch (err) {
2675
3488
  const message = err instanceof Error ? err.message : "Login failed";
2676
- spinner.fail(chalk6.red(message));
3489
+ spinner.fail(chalk7.red(message));
2677
3490
  process.exitCode = 1;
2678
3491
  }
2679
3492
  }
2680
3493
 
2681
3494
  // src/commands/logout.ts
2682
- import chalk7 from "chalk";
3495
+ import chalk8 from "chalk";
2683
3496
  async function logout() {
2684
3497
  const creds = await getStoredCredentials2();
2685
3498
  if (!creds) {
2686
- console.log(chalk7.yellow("Not logged in."));
3499
+ console.log(chalk8.yellow("Not logged in."));
2687
3500
  return;
2688
3501
  }
2689
3502
  await clearCredentials();
2690
- console.log(chalk7.green("Logged out successfully."));
3503
+ console.log(chalk8.green("Logged out successfully."));
2691
3504
  }
2692
3505
 
2693
3506
  // src/commands/status.ts
2694
3507
  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");
3508
+ import { basename as basename2, join as join20 } from "path";
2699
3509
  async function status() {
3510
+ const configDir = getConfigDir2();
3511
+ const STATE_PATH = join20(configDir, "listener-state.json");
3512
+ const CONFIG_PATH = join20(configDir, "config.json");
2700
3513
  let state = null;
2701
3514
  try {
2702
- const data = await readFile9(STATE_PATH2, "utf-8");
3515
+ const data = await readFile9(STATE_PATH, "utf-8");
2703
3516
  state = JSON.parse(data);
2704
3517
  } catch {
2705
3518
  }
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
- }
3519
+ const listenerStatus = await getListenerStatus();
2719
3520
  console.log("RepoWise Status");
2720
3521
  console.log("===============");
2721
- if (processRunning) {
2722
- console.log(`Listener: running (PID: ${pid})`);
3522
+ if (listenerStatus.running) {
3523
+ console.log(
3524
+ listenerStatus.method === "service" ? "Listener: running (managed by system service)" : `Listener: running (PID: ${listenerStatus.pid})`
3525
+ );
2723
3526
  } else {
2724
3527
  console.log("Listener: stopped");
2725
3528
  }
2726
- if (serviceInstalled) {
3529
+ if (listenerStatus.serviceInstalled) {
2727
3530
  console.log("Auto-start: enabled");
2728
3531
  } else {
2729
3532
  console.log("Auto-start: disabled");
@@ -2735,7 +3538,7 @@ async function status() {
2735
3538
  }
2736
3539
  const repoNames = /* @__PURE__ */ new Map();
2737
3540
  try {
2738
- const configData = await readFile9(CONFIG_PATH3, "utf-8");
3541
+ const configData = await readFile9(CONFIG_PATH, "utf-8");
2739
3542
  const config2 = JSON.parse(configData);
2740
3543
  for (const repo of config2.repos ?? []) {
2741
3544
  repoNames.set(repo.repoId, basename2(repo.localPath));
@@ -2753,30 +3556,14 @@ async function status() {
2753
3556
  }
2754
3557
 
2755
3558
  // 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";
3559
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
3560
+ import { dirname as dirname8, join as join21 } from "path";
3561
+ import chalk9 from "chalk";
3562
+ import ora4 from "ora";
2761
3563
  var POLL_INTERVAL_MS2 = 3e3;
2762
3564
  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) {
3565
+ var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
3566
+ function formatElapsed3(ms) {
2780
3567
  const totalSeconds = Math.round(ms / 1e3);
2781
3568
  const minutes = Math.floor(totalSeconds / 60);
2782
3569
  const seconds = totalSeconds % 60;
@@ -2785,14 +3572,14 @@ function formatElapsed2(ms) {
2785
3572
  }
2786
3573
  async function sync() {
2787
3574
  const startTime = Date.now();
2788
- const spinner = ora3("Checking authentication...").start();
3575
+ const spinner = ora4("Checking authentication...").start();
2789
3576
  try {
2790
3577
  let credentials = await getValidCredentials2();
2791
3578
  if (!credentials) {
2792
- spinner.info(chalk8.yellow("Not logged in. Opening browser to authenticate..."));
3579
+ spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
2793
3580
  credentials = await performLogin();
2794
3581
  const { email } = decodeIdToken(credentials.idToken);
2795
- spinner.succeed(chalk8.green(`Authenticated as ${chalk8.bold(email)}`));
3582
+ spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
2796
3583
  } else {
2797
3584
  spinner.succeed("Authenticated");
2798
3585
  }
@@ -2800,12 +3587,12 @@ async function sync() {
2800
3587
  let repoName;
2801
3588
  spinner.start("Detecting repository...");
2802
3589
  try {
2803
- repoRoot = detectRepoRoot2();
2804
- repoName = detectRepoName2(repoRoot);
2805
- spinner.succeed(`Repository: ${chalk8.bold(repoName)}`);
3590
+ repoRoot = detectRepoRoot();
3591
+ repoName = detectRepoName(repoRoot);
3592
+ spinner.succeed(`Repository: ${chalk9.bold(repoName)}`);
2806
3593
  } catch {
2807
3594
  spinner.fail(
2808
- chalk8.red("Not in a git repository. Run this command from your repo directory.")
3595
+ chalk9.red("Not in a git repository. Run this command from your repo directory.")
2809
3596
  );
2810
3597
  process.exitCode = 1;
2811
3598
  return;
@@ -2826,7 +3613,7 @@ async function sync() {
2826
3613
  }
2827
3614
  if (!repoId) {
2828
3615
  spinner.fail(
2829
- chalk8.red(
3616
+ chalk9.red(
2830
3617
  "Could not find this repository in your RepoWise account. Run `repowise create` first."
2831
3618
  )
2832
3619
  );
@@ -2863,7 +3650,7 @@ async function sync() {
2863
3650
  syncId = retryResult.syncId;
2864
3651
  } else {
2865
3652
  syncId = active.syncId;
2866
- spinner.info(chalk8.cyan("Resuming existing sync..."));
3653
+ spinner.info(chalk9.cyan("Resuming existing sync..."));
2867
3654
  spinner.start();
2868
3655
  }
2869
3656
  }
@@ -2871,7 +3658,7 @@ async function sync() {
2871
3658
  const progressRenderer = new ProgressRenderer();
2872
3659
  while (true) {
2873
3660
  if (++pollAttempts > MAX_POLL_ATTEMPTS2) {
2874
- spinner.fail(chalk8.red("Sync timed out. Check dashboard for status."));
3661
+ spinner.fail(chalk9.red("Sync timed out. Check dashboard for status."));
2875
3662
  process.exitCode = 1;
2876
3663
  return;
2877
3664
  }
@@ -2895,15 +3682,15 @@ async function sync() {
2895
3682
  const generatedFiles = syncResult.filesGenerated ?? [];
2896
3683
  const fileCount = generatedFiles.length;
2897
3684
  if (fileCount > 0) {
2898
- spinner.succeed(chalk8.green(`Incremental update complete \u2014 ${fileCount} files updated`));
3685
+ spinner.succeed(chalk9.green(`Incremental update complete \u2014 ${fileCount} files updated`));
2899
3686
  } else {
2900
- spinner.succeed(chalk8.green("Incremental sync complete \u2014 no files needed updating"));
3687
+ spinner.succeed(chalk9.green("Incremental sync complete \u2014 no files needed updating"));
2901
3688
  }
2902
3689
  break;
2903
3690
  }
2904
3691
  if (syncResult.status === "failed") {
2905
3692
  progressRenderer.finalize();
2906
- spinner.fail(chalk8.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
3693
+ spinner.fail(chalk9.red(`Sync failed: ${syncResult.error ?? "Unknown error"}`));
2907
3694
  process.exitCode = 1;
2908
3695
  return;
2909
3696
  }
@@ -2913,8 +3700,8 @@ async function sync() {
2913
3700
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
2914
3701
  const files = listResult.data?.files ?? listResult.files ?? [];
2915
3702
  if (files.length > 0) {
2916
- const contextDir = join15(repoRoot, DEFAULT_CONTEXT_FOLDER2);
2917
- mkdirSync2(contextDir, { recursive: true });
3703
+ const contextDir = join21(repoRoot, DEFAULT_CONTEXT_FOLDER3);
3704
+ mkdirSync3(contextDir, { recursive: true });
2918
3705
  let downloadedCount = 0;
2919
3706
  let failedCount = 0;
2920
3707
  for (const file of files) {
@@ -2927,9 +3714,9 @@ async function sync() {
2927
3714
  const response = await fetch(presignedUrl);
2928
3715
  if (response.ok) {
2929
3716
  const content = await response.text();
2930
- const filePath = join15(contextDir, file.fileName);
2931
- mkdirSync2(dirname5(filePath), { recursive: true });
2932
- writeFileSync3(filePath, content, "utf-8");
3717
+ const filePath = join21(contextDir, file.fileName);
3718
+ mkdirSync3(dirname8(filePath), { recursive: true });
3719
+ writeFileSync4(filePath, content, "utf-8");
2933
3720
  downloadedCount++;
2934
3721
  } else {
2935
3722
  failedCount++;
@@ -2937,13 +3724,13 @@ async function sync() {
2937
3724
  }
2938
3725
  if (failedCount > 0) {
2939
3726
  spinner.warn(
2940
- `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER2}/ (${failedCount} failed)`
3727
+ `Downloaded ${downloadedCount}/${files.length} files to ./${DEFAULT_CONTEXT_FOLDER3}/ (${failedCount} failed)`
2941
3728
  );
2942
3729
  } else {
2943
- spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER2}/`);
3730
+ spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER3}/`);
2944
3731
  }
2945
3732
  try {
2946
- ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER2);
3733
+ ensureGitignore(repoRoot, DEFAULT_CONTEXT_FOLDER3);
2947
3734
  } catch {
2948
3735
  }
2949
3736
  } else {
@@ -2951,7 +3738,7 @@ async function sync() {
2951
3738
  }
2952
3739
  } catch (err) {
2953
3740
  const msg = err instanceof Error ? err.message : "Unknown error";
2954
- spinner.warn(chalk8.yellow(`Could not download context files: ${msg}
3741
+ spinner.warn(chalk9.yellow(`Could not download context files: ${msg}
2955
3742
  Retry when online.`));
2956
3743
  }
2957
3744
  if (repoRoot && repoId) {
@@ -2959,29 +3746,24 @@ Retry when online.`));
2959
3746
  const existingConfig = await getConfig();
2960
3747
  const existingRepos = existingConfig.repos ?? [];
2961
3748
  if (!existingRepos.some((r) => r.repoId === repoId)) {
2962
- const repoEntry = { repoId, localPath: repoRoot };
2963
- if (isStagingMode()) {
2964
- repoEntry.apiUrl = getEnvConfig().apiUrl;
2965
- }
3749
+ const repoEntry = { repoId, localPath: repoRoot, apiUrl: getEnvConfig().apiUrl };
2966
3750
  if (repoPlatform) repoEntry.platform = repoPlatform;
2967
3751
  if (repoExternalId) repoEntry.externalId = repoExternalId;
2968
- existingRepos.push(repoEntry);
2969
- await saveConfig({ ...existingConfig, repos: existingRepos });
3752
+ await mergeAndSaveConfig({ repos: [repoEntry] });
2970
3753
  }
2971
3754
  } catch {
2972
3755
  }
2973
3756
  try {
2974
- await install();
2975
- await startBackground();
3757
+ await ensureListenerRunning();
2976
3758
  } catch {
2977
3759
  }
2978
3760
  }
2979
- const elapsed = formatElapsed2(Date.now() - startTime);
2980
- console.log(chalk8.dim(`
3761
+ const elapsed = formatElapsed3(Date.now() - startTime);
3762
+ console.log(chalk9.dim(`
2981
3763
  Total time: ${elapsed}`));
2982
3764
  } catch (err) {
2983
3765
  const message = err instanceof Error ? err.message : "Sync failed";
2984
- spinner.fail(chalk8.red(message));
3766
+ spinner.fail(chalk9.red(message));
2985
3767
  process.exitCode = 1;
2986
3768
  }
2987
3769
  }
@@ -3029,12 +3811,15 @@ async function listen(options) {
3029
3811
  // src/commands/start.ts
3030
3812
  async function start() {
3031
3813
  try {
3032
- if (await isRunning()) {
3814
+ const status2 = await getListenerStatus();
3815
+ if (status2.running) {
3033
3816
  console.log("Listener is already running.");
3034
3817
  return;
3035
3818
  }
3036
- const pid = await startBackground();
3037
- console.log(`Listener started (PID: ${pid}).`);
3819
+ const result = await ensureListenerRunning();
3820
+ console.log(
3821
+ result.method === "service" ? "Listener service installed and started." : `Listener started (PID: ${result.pid}).`
3822
+ );
3038
3823
  } catch (err) {
3039
3824
  const message = err instanceof Error ? err.message : "Unknown error";
3040
3825
  console.error(`Failed to start listener: ${message}`);
@@ -3045,11 +3830,12 @@ async function start() {
3045
3830
  // src/commands/stop.ts
3046
3831
  async function stop2() {
3047
3832
  try {
3048
- if (!await isRunning()) {
3833
+ const status2 = await getListenerStatus();
3834
+ if (!status2.running) {
3049
3835
  console.log("Listener is not running.");
3050
3836
  return;
3051
3837
  }
3052
- await stopProcess();
3838
+ await stopListener();
3053
3839
  console.log("Listener stopped.");
3054
3840
  } catch (err) {
3055
3841
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -3059,18 +3845,18 @@ async function stop2() {
3059
3845
  }
3060
3846
 
3061
3847
  // src/commands/config.ts
3062
- import chalk9 from "chalk";
3063
- import ora4 from "ora";
3848
+ import chalk10 from "chalk";
3849
+ import ora5 from "ora";
3064
3850
  import { select } from "@inquirer/prompts";
3065
3851
  async function config() {
3066
- const spinner = ora4("Checking authentication...").start();
3852
+ const spinner = ora5("Checking authentication...").start();
3067
3853
  try {
3068
3854
  let credentials = await getValidCredentials2();
3069
3855
  if (!credentials) {
3070
- spinner.info(chalk9.yellow("Not logged in. Opening browser to authenticate..."));
3856
+ spinner.info(chalk10.yellow("Not logged in. Opening browser to authenticate..."));
3071
3857
  credentials = await performLogin();
3072
3858
  const { email } = decodeIdToken(credentials.idToken);
3073
- spinner.succeed(chalk9.green(`Authenticated as ${chalk9.bold(email)}`));
3859
+ spinner.succeed(chalk10.green(`Authenticated as ${chalk10.bold(email)}`));
3074
3860
  } else {
3075
3861
  spinner.succeed("Authenticated");
3076
3862
  }
@@ -3078,7 +3864,7 @@ async function config() {
3078
3864
  const repos = await apiRequest("/v1/repos");
3079
3865
  spinner.stop();
3080
3866
  if (repos.length === 0) {
3081
- console.log(chalk9.yellow("No repositories connected. Run `repowise create` first."));
3867
+ console.log(chalk10.yellow("No repositories connected. Run `repowise create` first."));
3082
3868
  return;
3083
3869
  }
3084
3870
  const repoId = await select({
@@ -3092,10 +3878,10 @@ async function config() {
3092
3878
  const currentDelivery = repo.deliveryMode ?? "direct";
3093
3879
  const currentBranch = repo.monitoredBranch ?? repo.defaultBranch;
3094
3880
  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}`));
3881
+ console.log(chalk10.bold(`Settings for ${repo.fullName}`));
3882
+ console.log(chalk10.dim(` Context storage: RepoWise servers`));
3883
+ console.log(chalk10.dim(` Delivery mode: ${currentDelivery}`));
3884
+ console.log(chalk10.dim(` Monitored branch: ${currentBranch}`));
3099
3885
  console.log("");
3100
3886
  const setting = await select({
3101
3887
  message: "What would you like to change?",
@@ -3115,7 +3901,7 @@ async function config() {
3115
3901
  default: currentDelivery
3116
3902
  });
3117
3903
  if (newMode === currentDelivery) {
3118
- console.log(chalk9.dim("No change."));
3904
+ console.log(chalk10.dim("No change."));
3119
3905
  return;
3120
3906
  }
3121
3907
  patch.deliveryMode = newMode;
@@ -3126,7 +3912,7 @@ async function config() {
3126
3912
  default: currentBranch
3127
3913
  });
3128
3914
  if (newBranch === currentBranch) {
3129
- console.log(chalk9.dim("No change."));
3915
+ console.log(chalk10.dim("No change."));
3130
3916
  return;
3131
3917
  }
3132
3918
  patch.monitoredBranch = newBranch;
@@ -3136,29 +3922,29 @@ async function config() {
3136
3922
  method: "PATCH",
3137
3923
  body: JSON.stringify(patch)
3138
3924
  });
3139
- spinner.succeed(chalk9.green("Setting updated"));
3925
+ spinner.succeed(chalk10.green("Setting updated"));
3140
3926
  } catch (err) {
3141
3927
  spinner.stop();
3142
3928
  const message = err instanceof Error ? err.message : "Config failed";
3143
- console.error(chalk9.red(message));
3929
+ console.error(chalk10.red(message));
3144
3930
  process.exitCode = 1;
3145
3931
  }
3146
3932
  }
3147
3933
 
3148
3934
  // bin/repowise.ts
3149
3935
  var __filename = fileURLToPath3(import.meta.url);
3150
- var __dirname = dirname6(__filename);
3151
- var pkg = JSON.parse(readFileSync2(join16(__dirname, "..", "..", "package.json"), "utf-8"));
3936
+ var __dirname = dirname9(__filename);
3937
+ var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "..", "package.json"), "utf-8"));
3152
3938
  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
- }
3939
+ program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
3157
3940
  await showWelcome(pkg.version);
3158
3941
  });
3159
3942
  program.command("create").description("Create context for a repository").action(async () => {
3160
3943
  await create();
3161
3944
  });
3945
+ program.command("member").description("Download context and configure AI tools (for team members)").action(async () => {
3946
+ await member();
3947
+ });
3162
3948
  program.command("login").description("Authenticate with RepoWise").option("--no-browser", "Print login URL instead of opening browser").action(async (options) => {
3163
3949
  await login(options);
3164
3950
  });
@@ -3190,7 +3976,7 @@ if (process.argv[2] === "__listener") {
3190
3976
  process.on("unhandledRejection", (reason) => {
3191
3977
  console.error("Listener unhandled rejection:", reason);
3192
3978
  });
3193
- startListener({ skipPidCheck: true }).catch((err) => {
3979
+ startListener().catch((err) => {
3194
3980
  console.error("Listener fatal error:", err);
3195
3981
  process.exitCode = 1;
3196
3982
  });