repowisestage 0.0.26 → 0.0.28

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 +174 -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,39 @@ 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
+ rl.setPrompt(" > ");
2539
+ rl.prompt();
2540
+ rl.on("line", (line) => {
2541
+ if (line === "" && lines.length > 0) {
2542
+ rl.close();
2543
+ resolve(lines.join("\n"));
2544
+ } else if (line === "" && lines.length === 0) {
2545
+ rl.close();
2546
+ resolve("");
2547
+ } else {
2548
+ lines.push(line);
2549
+ rl.setPrompt(" > ");
2550
+ rl.prompt();
2551
+ }
2552
+ });
2553
+ rl.on("close", () => {
2554
+ if (lines.length > 0) {
2555
+ resolve(lines.join("\n"));
2556
+ } else {
2557
+ resolve("");
2558
+ }
2559
+ });
2560
+ rl.on("error", reject);
2561
+ });
2562
+ }
2435
2563
  var questionCounter = 0;
2436
2564
  async function handleInterview(syncId, questionId, questionText, questionContext, estimatedQuestions) {
2437
2565
  questionCounter++;
@@ -2452,14 +2580,11 @@ async function handleInterview(syncId, questionId, questionText, questionContext
2452
2580
  console.log(chalk3.dim(` ${questionContext}`));
2453
2581
  }
2454
2582
  console.log(` ${questionText}`);
2455
- console.log(chalk3.dim(' (Enter to skip \xB7 "done" to finish early)'));
2583
+ console.log(chalk3.dim(' (Empty line to submit \xB7 Enter to skip \xB7 "done" to finish early)'));
2456
2584
  let answer;
2457
2585
  try {
2458
2586
  answer = await Promise.race([
2459
- input({
2460
- message: chalk3.cyan(">"),
2461
- theme: { prefix: " " }
2462
- }),
2587
+ multilineInput(),
2463
2588
  new Promise(
2464
2589
  (_, reject) => setTimeout(() => reject(new Error("INTERVIEW_TIMEOUT")), INTERVIEW_TIMEOUT_MS)
2465
2590
  )
@@ -2650,6 +2775,11 @@ var ProgressRenderer = class {
2650
2775
  spinner.stop();
2651
2776
  console.log("");
2652
2777
  console.log(chalk4.cyan.bold(" \u2500\u2500 Code Analysis \u2500\u2500"));
2778
+ console.log(
2779
+ chalk4.cyan(
2780
+ " \u2615 This takes a few minutes \u2014 grab a coffee, we'll handle the rest!"
2781
+ )
2782
+ );
2653
2783
  console.log(chalk4.dim(" Analyzing your codebase structure, functions, and relationships."));
2654
2784
  spinner.start();
2655
2785
  }
@@ -2669,11 +2799,6 @@ var ProgressRenderer = class {
2669
2799
  this.generationHeaderShown = true;
2670
2800
  spinner.stop();
2671
2801
  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
2802
  console.log("");
2678
2803
  spinner.start();
2679
2804
  }
@@ -2776,7 +2901,7 @@ var ProgressRenderer = class {
2776
2901
  getSpinnerText(syncResult) {
2777
2902
  const overallPct = computeOverallProgress(syncResult);
2778
2903
  const stepLabel = syncResult.stepLabel ?? syncResult.currentStep ?? "Processing";
2779
- if (syncResult.scanProgress && !syncResult.scanProgress.summary) {
2904
+ if (syncResult.scanProgress && !syncResult.scanProgress.summary && syncResult.currentStep === "scan-and-generate") {
2780
2905
  const sp = syncResult.scanProgress;
2781
2906
  const pct = sp.totalBatches > 0 ? Math.round(sp.currentBatch / sp.totalBatches * 100) : 0;
2782
2907
  return `Scanning batch ${sp.currentBatch}/${sp.totalBatches} ${chalk4.dim(`(${pct}%)`)}`;
@@ -2922,26 +3047,51 @@ async function create() {
2922
3047
  try {
2923
3048
  const pricing = await apiRequest(`/v1/repos/${repoId}/rescan-pricing`);
2924
3049
  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 {
3050
+ const { groups } = decodeIdToken(credentials.idToken);
3051
+ const role = resolveRole(groups ?? []);
3052
+ const canRescan = hasPermission(role, "connectRepos");
3053
+ if (!canRescan) {
2929
3054
  spinner.fail(chalk5.red("This repository already has context generated."));
2930
3055
  console.log(
2931
3056
  chalk5.cyan(
2932
3057
  `
2933
- If you're a team member, run: ${chalk5.bold("repowise member")}
3058
+ As a team member, run: ${chalk5.bold("repowise member")}
2934
3059
  This will download context and configure your AI tools.
2935
3060
  `
2936
3061
  )
2937
3062
  );
3063
+ process.exitCode = 1;
3064
+ return;
3065
+ }
3066
+ if (pricing.allowed && pricing.isFree) {
3067
+ spinner.succeed(chalk5.cyan("Free rescan available. Will trigger a full rescan."));
3068
+ useFreeRescan = true;
3069
+ } else if (pricing.allowed) {
3070
+ spinner.stop();
3071
+ const costText = pricing.estimatedCost && pricing.estimatedCost > 0 ? `$${pricing.estimatedCost.toFixed(2)}` : "pay-as-you-go (based on actual processing cost)";
2938
3072
  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
- `
3073
+ chalk5.yellow(
3074
+ `
3075
+ This repository already has context generated.
3076
+ A full rescan will cost: ${chalk5.bold(costText)}`
2943
3077
  )
2944
3078
  );
3079
+ if (pricing.costWarning) {
3080
+ console.log(chalk5.dim(` ${pricing.costWarning}`));
3081
+ }
3082
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
3083
+ const proceed = await confirm2({
3084
+ message: "Proceed with paid full rescan?",
3085
+ default: false
3086
+ });
3087
+ if (!proceed) {
3088
+ console.log(chalk5.dim("\n To sync recent changes, use: repowise sync\n"));
3089
+ process.exitCode = 0;
3090
+ return;
3091
+ }
3092
+ useFreeRescan = true;
3093
+ } else {
3094
+ spinner.fail(chalk5.red(pricing.reason ?? "Cannot rescan at this time."));
2945
3095
  process.exitCode = 1;
2946
3096
  return;
2947
3097
  }
@@ -3913,8 +4063,8 @@ async function config() {
3913
4063
  }
3914
4064
  patch.deliveryMode = newMode;
3915
4065
  } else if (setting === "monitoredBranch") {
3916
- const { input: input2 } = await import("@inquirer/prompts");
3917
- const newBranch = await input2({
4066
+ const { input } = await import("@inquirer/prompts");
4067
+ const newBranch = await input({
3918
4068
  message: "Monitored branch",
3919
4069
  default: currentBranch
3920
4070
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowisestage",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "type": "module",
5
5
  "description": "AI-optimized codebase context generator",
6
6
  "bin": {