repowise 0.1.86 → 0.1.87

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 +192 -24
  2. package/package.json +1 -1
@@ -1831,6 +1831,101 @@ import { dirname as dirname6, join as join18 } from "path";
1831
1831
  import chalk5 from "chalk";
1832
1832
  import ora from "ora";
1833
1833
 
1834
+ // ../../packages/shared/src/constants/tiers.ts
1835
+ var SUBSCRIPTION_TIERS = {
1836
+ STARTER: "starter",
1837
+ PRO: "pro",
1838
+ TEAM: "team"
1839
+ };
1840
+ var TIER_LIMITS = {
1841
+ [SUBSCRIPTION_TIERS.STARTER]: {
1842
+ maxRepos: 1,
1843
+ maxSeats: 1,
1844
+ maxSyncsPerMonth: 10,
1845
+ maxConcurrentSyncs: 1,
1846
+ bedrockEndpoint: "public"
1847
+ },
1848
+ [SUBSCRIPTION_TIERS.PRO]: {
1849
+ maxRepos: 2,
1850
+ maxSeats: 1,
1851
+ maxSyncsPerMonth: 30,
1852
+ maxConcurrentSyncs: 2,
1853
+ bedrockEndpoint: "public"
1854
+ },
1855
+ [SUBSCRIPTION_TIERS.TEAM]: {
1856
+ maxRepos: 3,
1857
+ maxSeats: 999999,
1858
+ maxSyncsPerMonth: 30,
1859
+ maxConcurrentSyncs: 10,
1860
+ bedrockEndpoint: "public"
1861
+ }
1862
+ };
1863
+
1864
+ // ../../packages/shared/src/constants/roles.ts
1865
+ var ROLES = {
1866
+ OWNER: "owner",
1867
+ ADMIN: "admin",
1868
+ REPO_MANAGER: "repo_manager",
1869
+ MEMBER: "member"
1870
+ };
1871
+ var PERMISSIONS = {
1872
+ [ROLES.OWNER]: {
1873
+ connectRepos: true,
1874
+ viewSync: true,
1875
+ triggerRetry: true,
1876
+ configWatcher: true,
1877
+ inviteMembers: true,
1878
+ manageBilling: true,
1879
+ configAiTools: true,
1880
+ manageTeam: true
1881
+ },
1882
+ [ROLES.ADMIN]: {
1883
+ connectRepos: true,
1884
+ viewSync: true,
1885
+ triggerRetry: true,
1886
+ configWatcher: true,
1887
+ inviteMembers: true,
1888
+ manageBilling: false,
1889
+ configAiTools: true,
1890
+ manageTeam: true
1891
+ },
1892
+ [ROLES.REPO_MANAGER]: {
1893
+ connectRepos: true,
1894
+ viewSync: true,
1895
+ triggerRetry: true,
1896
+ configWatcher: true,
1897
+ inviteMembers: false,
1898
+ manageBilling: false,
1899
+ configAiTools: true,
1900
+ manageTeam: false
1901
+ },
1902
+ [ROLES.MEMBER]: {
1903
+ connectRepos: false,
1904
+ viewSync: true,
1905
+ triggerRetry: true,
1906
+ configWatcher: false,
1907
+ inviteMembers: false,
1908
+ manageBilling: false,
1909
+ configAiTools: true,
1910
+ manageTeam: false
1911
+ }
1912
+ };
1913
+ function hasPermission(role, permission) {
1914
+ return PERMISSIONS[role][permission];
1915
+ }
1916
+ var ROLE_PRIORITY = [
1917
+ ROLES.OWNER,
1918
+ ROLES.ADMIN,
1919
+ ROLES.REPO_MANAGER,
1920
+ ROLES.MEMBER
1921
+ ];
1922
+ function resolveRole(groups) {
1923
+ for (const role of ROLE_PRIORITY) {
1924
+ if (groups.includes(role)) return role;
1925
+ }
1926
+ return ROLES.MEMBER;
1927
+ }
1928
+
1834
1929
  // src/lib/auth.ts
1835
1930
  import { createHash, randomBytes } from "crypto";
1836
1931
  import { readFile as readFile7, writeFile as writeFile9, mkdir as mkdir9, chmod as chmod4, unlink as unlink7 } from "fs/promises";
@@ -2110,7 +2205,11 @@ function decodeIdToken(idToken) {
2110
2205
  const parts = idToken.split(".");
2111
2206
  if (parts.length < 2) return { email: "unknown" };
2112
2207
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2113
- return { email: payload.email ?? "unknown", tenantId: payload["custom:tenant_id"] };
2208
+ return {
2209
+ email: payload.email ?? "unknown",
2210
+ tenantId: payload["custom:tenant_id"],
2211
+ groups: payload["cognito:groups"]
2212
+ };
2114
2213
  } catch {
2115
2214
  return { email: "unknown" };
2116
2215
  }
@@ -2428,10 +2527,57 @@ function detectRepoName(repoRoot) {
2428
2527
  }
2429
2528
 
2430
2529
  // src/lib/interview-handler.ts
2530
+ import { createInterface } from "readline";
2431
2531
  import chalk3 from "chalk";
2432
- import { input } from "@inquirer/prompts";
2433
2532
  var INTERVIEW_TIMEOUT_MS = 5 * 60 * 1e3;
2434
2533
  var MAX_QUESTIONS = 10;
2534
+ function multilineInput() {
2535
+ return new Promise((resolve, reject) => {
2536
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2537
+ const lines = [];
2538
+ let lastLineTime = 0;
2539
+ let resolved = false;
2540
+ const done = (value) => {
2541
+ if (resolved) return;
2542
+ resolved = true;
2543
+ rl.close();
2544
+ resolve(value);
2545
+ };
2546
+ rl.setPrompt(" > ");
2547
+ rl.prompt();
2548
+ rl.on("line", (line) => {
2549
+ const now = Date.now();
2550
+ const elapsed = now - lastLineTime;
2551
+ lastLineTime = now;
2552
+ const lower = line.trim().toLowerCase();
2553
+ if (lower === "done" || lower === "skip") {
2554
+ done(lower);
2555
+ return;
2556
+ }
2557
+ if (lines.length === 0) {
2558
+ if (line === "") {
2559
+ done("");
2560
+ return;
2561
+ }
2562
+ lines.push(line);
2563
+ rl.setPrompt(" > ");
2564
+ rl.prompt();
2565
+ return;
2566
+ }
2567
+ if (elapsed > 50 && line === "") {
2568
+ done(lines.join("\n").trim());
2569
+ return;
2570
+ }
2571
+ lines.push(line);
2572
+ rl.setPrompt(" > ");
2573
+ rl.prompt();
2574
+ });
2575
+ rl.on("close", () => {
2576
+ done(lines.length > 0 ? lines.join("\n").trim() : "");
2577
+ });
2578
+ rl.on("error", reject);
2579
+ });
2580
+ }
2435
2581
  var questionCounter = 0;
2436
2582
  async function handleInterview(syncId, questionId, questionText, questionContext, estimatedQuestions) {
2437
2583
  questionCounter++;
@@ -2452,14 +2598,11 @@ async function handleInterview(syncId, questionId, questionText, questionContext
2452
2598
  console.log(chalk3.dim(` ${questionContext}`));
2453
2599
  }
2454
2600
  console.log(` ${questionText}`);
2455
- console.log(chalk3.dim(' (Enter to skip \xB7 "done" to finish early)'));
2601
+ console.log(chalk3.dim(' (Enter to submit \xB7 Enter to skip \xB7 "done" to finish early)'));
2456
2602
  let answer;
2457
2603
  try {
2458
2604
  answer = await Promise.race([
2459
- input({
2460
- message: chalk3.cyan(">"),
2461
- theme: { prefix: " " }
2462
- }),
2605
+ multilineInput(),
2463
2606
  new Promise(
2464
2607
  (_, reject) => setTimeout(() => reject(new Error("INTERVIEW_TIMEOUT")), INTERVIEW_TIMEOUT_MS)
2465
2608
  )
@@ -2650,6 +2793,11 @@ var ProgressRenderer = class {
2650
2793
  spinner.stop();
2651
2794
  console.log("");
2652
2795
  console.log(chalk4.cyan.bold(" \u2500\u2500 Code Analysis \u2500\u2500"));
2796
+ console.log(
2797
+ chalk4.cyan(
2798
+ " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2799
+ )
2800
+ );
2653
2801
  console.log(chalk4.dim(" Analyzing your codebase structure, functions, and relationships."));
2654
2802
  spinner.start();
2655
2803
  }
@@ -2669,11 +2817,6 @@ var ProgressRenderer = class {
2669
2817
  this.generationHeaderShown = true;
2670
2818
  spinner.stop();
2671
2819
  console.log(chalk4.cyan.bold(" \u2500\u2500 Context Generation \u2500\u2500"));
2672
- console.log(
2673
- chalk4.cyan(
2674
- " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2675
- )
2676
- );
2677
2820
  console.log("");
2678
2821
  spinner.start();
2679
2822
  }
@@ -2776,7 +2919,7 @@ var ProgressRenderer = class {
2776
2919
  getSpinnerText(syncResult) {
2777
2920
  const overallPct = computeOverallProgress(syncResult);
2778
2921
  const stepLabel = syncResult.stepLabel ?? syncResult.currentStep ?? "Processing";
2779
- if (syncResult.scanProgress && !syncResult.scanProgress.summary) {
2922
+ if (syncResult.scanProgress && !syncResult.scanProgress.summary && syncResult.currentStep === "scan-and-generate") {
2780
2923
  const sp = syncResult.scanProgress;
2781
2924
  const pct = sp.totalBatches > 0 ? Math.round(sp.currentBatch / sp.totalBatches * 100) : 0;
2782
2925
  return `Scanning batch ${sp.currentBatch}/${sp.totalBatches} ${chalk4.dim(`(${pct}%)`)}`;
@@ -2922,26 +3065,51 @@ async function create() {
2922
3065
  try {
2923
3066
  const pricing = await apiRequest(`/v1/repos/${repoId}/rescan-pricing`);
2924
3067
  if (pricing.lastFullScanAt) {
2925
- if (pricing.allowed && pricing.isFree) {
2926
- spinner.succeed(chalk5.cyan("Free rescan available. Will trigger a full rescan."));
2927
- useFreeRescan = true;
2928
- } else {
3068
+ const { groups } = decodeIdToken(credentials.idToken);
3069
+ const role = resolveRole(groups ?? []);
3070
+ const canRescan = hasPermission(role, "connectRepos");
3071
+ if (!canRescan) {
2929
3072
  spinner.fail(chalk5.red("This repository already has context generated."));
2930
3073
  console.log(
2931
3074
  chalk5.cyan(
2932
3075
  `
2933
- If you're a team member, run: ${chalk5.bold("repowise member")}
3076
+ As a team member, run: ${chalk5.bold("repowise member")}
2934
3077
  This will download context and configure your AI tools.
2935
3078
  `
2936
3079
  )
2937
3080
  );
3081
+ process.exitCode = 1;
3082
+ return;
3083
+ }
3084
+ if (pricing.allowed && pricing.isFree) {
3085
+ spinner.succeed(chalk5.cyan("Free rescan available. Will trigger a full rescan."));
3086
+ useFreeRescan = true;
3087
+ } else if (pricing.allowed) {
3088
+ spinner.stop();
3089
+ const costText = pricing.estimatedCost && pricing.estimatedCost > 0 ? `$${pricing.estimatedCost.toFixed(2)}` : "pay-as-you-go (based on actual processing cost)";
2938
3090
  console.log(
2939
- chalk5.dim(
2940
- ` To trigger a new full scan, visit: https://app.repowise.ai/repos/${repoId}
2941
- To sync recent changes, use: repowise sync
2942
- `
3091
+ chalk5.yellow(
3092
+ `
3093
+ This repository already has context generated.
3094
+ A full rescan will cost: ${chalk5.bold(costText)}`
2943
3095
  )
2944
3096
  );
3097
+ if (pricing.costWarning) {
3098
+ console.log(chalk5.dim(` ${pricing.costWarning}`));
3099
+ }
3100
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3101
+ const proceed = await confirm2({
3102
+ message: "Proceed with paid full rescan?",
3103
+ default: false
3104
+ });
3105
+ if (!proceed) {
3106
+ console.log(chalk5.dim("\n To sync recent changes, use: repowise sync\n"));
3107
+ process.exitCode = 0;
3108
+ return;
3109
+ }
3110
+ useFreeRescan = true;
3111
+ } else {
3112
+ spinner.fail(chalk5.red(pricing.reason ?? "Cannot rescan at this time."));
2945
3113
  process.exitCode = 1;
2946
3114
  return;
2947
3115
  }
@@ -3913,8 +4081,8 @@ async function config() {
3913
4081
  }
3914
4082
  patch.deliveryMode = newMode;
3915
4083
  } else if (setting === "monitoredBranch") {
3916
- const { input: input2 } = await import("@inquirer/prompts");
3917
- const newBranch = await input2({
4084
+ const { input } = await import("@inquirer/prompts");
4085
+ const newBranch = await input({
3918
4086
  message: "Monitored branch",
3919
4087
  default: currentBranch
3920
4088
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowise",
3
- "version": "0.1.86",
3
+ "version": "0.1.87",
4
4
  "type": "module",
5
5
  "description": "AI-optimized codebase context generator",
6
6
  "bin": {