repomind 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +397 -123
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30987,6 +30987,7 @@ async function readStagedFiles(files) {
30987
30987
 
30988
30988
  // src/lib/api-client.ts
30989
30989
  var FETCH_TIMEOUT_MS = 3e4;
30990
+ var STREAM_TIMEOUT_MS = 3e5;
30990
30991
  var DEFAULT_BASE_URL = "https://api.repomind.dev";
30991
30992
  var DEBUG = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
30992
30993
  var ApiError = class extends Error {
@@ -31044,13 +31045,50 @@ function createApiClient(baseUrl = DEFAULT_BASE_URL, fetchFn = fetch) {
31044
31045
  }
31045
31046
  return res.json();
31046
31047
  }
31048
+ async function postStream(path, body, token, timeoutMs) {
31049
+ let res;
31050
+ try {
31051
+ res = await fetchFn(`${baseUrl}${path}`, {
31052
+ method: "POST",
31053
+ headers: {
31054
+ "Content-Type": "application/json",
31055
+ Accept: "text/event-stream",
31056
+ ...token ? { Authorization: `Bearer ${token}` } : {}
31057
+ },
31058
+ body: JSON.stringify(body),
31059
+ signal: AbortSignal.timeout(timeoutMs ?? STREAM_TIMEOUT_MS)
31060
+ });
31061
+ } catch (err) {
31062
+ if (DEBUG) console.error("[api-client] postStream network error:", err);
31063
+ throw new ApiError(
31064
+ 0,
31065
+ err instanceof Error ? err.message : "network error"
31066
+ );
31067
+ }
31068
+ if (!res.ok) {
31069
+ const errBody = await res.json().catch(() => ({}));
31070
+ if (DEBUG)
31071
+ console.error(`[api-client] POST ${path} \u2192 ${res.status}`, errBody);
31072
+ if (res.status === 402 && errBody.error === "plan_limit_reached") {
31073
+ throw new PlanLimitError(
31074
+ String(errBody.plan ?? "free"),
31075
+ Number(errBody.used ?? 0),
31076
+ Number(errBody.limit ?? 0)
31077
+ );
31078
+ }
31079
+ const msg = errBody.detail ? `${errBody.error}: ${errBody.detail}` : String(errBody.error ?? "request failed");
31080
+ throw new ApiError(res.status, msg);
31081
+ }
31082
+ return res;
31083
+ }
31047
31084
  return {
31048
31085
  post(path, body, token, timeoutMs) {
31049
31086
  return request("POST", path, token, body, timeoutMs);
31050
31087
  },
31051
31088
  get(path, token) {
31052
31089
  return request("GET", path, token);
31053
- }
31090
+ },
31091
+ postStream
31054
31092
  };
31055
31093
  }
31056
31094
  var apiClient = createApiClient();
@@ -39212,7 +39250,7 @@ var import_react27 = __toESM(require_react(), 1);
39212
39250
  var import_react28 = __toESM(require_react(), 1);
39213
39251
 
39214
39252
  // src/commands/commit.ts
39215
- var import_react33 = __toESM(require_react(), 1);
39253
+ var import_react34 = __toESM(require_react(), 1);
39216
39254
 
39217
39255
  // src/lib/auth.ts
39218
39256
  import { mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
@@ -39318,7 +39356,185 @@ function Spinner({ type = "dots" }) {
39318
39356
  var build_default = Spinner;
39319
39357
 
39320
39358
  // src/ui/commit-app.tsx
39321
- var import_react31 = __toESM(require_react(), 1);
39359
+ var import_react32 = __toESM(require_react(), 1);
39360
+
39361
+ // src/hooks/use-pipeline-stream.ts
39362
+ var import_react30 = __toESM(require_react(), 1);
39363
+
39364
+ // src/lib/sse-parser.ts
39365
+ async function* parseSSEStream(stream) {
39366
+ const reader = stream.getReader();
39367
+ const decoder = new TextDecoder();
39368
+ let buffer = "";
39369
+ try {
39370
+ while (true) {
39371
+ const { done, value } = await reader.read();
39372
+ if (done) break;
39373
+ buffer += decoder.decode(value, { stream: true });
39374
+ const parts = buffer.split("\n\n");
39375
+ buffer = parts.pop() ?? "";
39376
+ for (const part of parts) {
39377
+ const trimmed = part.trim();
39378
+ if (!trimmed || trimmed.startsWith(":")) continue;
39379
+ let data;
39380
+ for (const line of trimmed.split("\n")) {
39381
+ if (line.startsWith("data: ")) {
39382
+ data = line.slice(6);
39383
+ }
39384
+ }
39385
+ if (data) {
39386
+ try {
39387
+ yield JSON.parse(data);
39388
+ } catch {
39389
+ }
39390
+ }
39391
+ }
39392
+ }
39393
+ } finally {
39394
+ reader.releaseLock();
39395
+ }
39396
+ }
39397
+
39398
+ // src/hooks/use-pipeline-stream.ts
39399
+ function usePipelineStream(options) {
39400
+ const [steps, setSteps] = (0, import_react30.useState)([]);
39401
+ const [result, setResult] = (0, import_react30.useState)(null);
39402
+ const [error, setError] = (0, import_react30.useState)(null);
39403
+ const [isStreaming, setIsStreaming] = (0, import_react30.useState)(false);
39404
+ const activeStepIndexRef = (0, import_react30.useRef)(null);
39405
+ const stepStartTimeRef = (0, import_react30.useRef)(0);
39406
+ const elapsedIntervalRef = (0, import_react30.useRef)(
39407
+ null
39408
+ );
39409
+ const abortRef = (0, import_react30.useRef)(null);
39410
+ function clearElapsedInterval() {
39411
+ if (elapsedIntervalRef.current !== null) {
39412
+ clearInterval(elapsedIntervalRef.current);
39413
+ elapsedIntervalRef.current = null;
39414
+ }
39415
+ }
39416
+ function startElapsedTimer(stepIdx) {
39417
+ clearElapsedInterval();
39418
+ activeStepIndexRef.current = stepIdx;
39419
+ stepStartTimeRef.current = Date.now();
39420
+ elapsedIntervalRef.current = setInterval(() => {
39421
+ const elapsed = (Date.now() - stepStartTimeRef.current) / 1e3;
39422
+ const detail = `${elapsed.toFixed(1)}s`;
39423
+ setSteps(
39424
+ (prev) => prev.map(
39425
+ (s, i) => i === activeStepIndexRef.current ? { ...s, detail } : s
39426
+ )
39427
+ );
39428
+ }, 100);
39429
+ }
39430
+ (0, import_react30.useEffect)(() => {
39431
+ if (!options) return;
39432
+ let cancelled = false;
39433
+ const controller = new AbortController();
39434
+ abortRef.current = controller;
39435
+ setIsStreaming(true);
39436
+ setSteps([]);
39437
+ setResult(null);
39438
+ setError(null);
39439
+ async function run2() {
39440
+ if (!options) return;
39441
+ let response;
39442
+ try {
39443
+ response = await options.postStream(
39444
+ options.path,
39445
+ options.body,
39446
+ options.token,
39447
+ options.timeoutMs
39448
+ );
39449
+ } catch (err) {
39450
+ if (cancelled) return;
39451
+ setError(err instanceof Error ? err.message : "request failed");
39452
+ setIsStreaming(false);
39453
+ return;
39454
+ }
39455
+ if (cancelled) return;
39456
+ const contentType = response.headers.get("Content-Type") ?? "";
39457
+ if (contentType.includes("application/json")) {
39458
+ try {
39459
+ const data = await response.json();
39460
+ if (!cancelled) {
39461
+ setResult(data);
39462
+ setIsStreaming(false);
39463
+ }
39464
+ } catch (err) {
39465
+ if (!cancelled) {
39466
+ setError(
39467
+ err instanceof Error ? err.message : "failed to parse JSON"
39468
+ );
39469
+ setIsStreaming(false);
39470
+ }
39471
+ }
39472
+ return;
39473
+ }
39474
+ if (!response.body) {
39475
+ if (!cancelled) {
39476
+ setError("response body is null");
39477
+ setIsStreaming(false);
39478
+ }
39479
+ return;
39480
+ }
39481
+ try {
39482
+ for await (const event of parseSSEStream(response.body)) {
39483
+ if (cancelled) break;
39484
+ if (event.type === "step:start") {
39485
+ const newStep = {
39486
+ label: options.stepLabels[event.step] ?? event.step,
39487
+ status: "active"
39488
+ };
39489
+ setSteps((prev) => {
39490
+ const updated = prev.map(
39491
+ (s) => s.status === "active" ? { ...s, status: "done" } : s
39492
+ );
39493
+ return [...updated, newStep];
39494
+ });
39495
+ startElapsedTimer(event.index);
39496
+ } else if (event.type === "step:complete") {
39497
+ clearElapsedInterval();
39498
+ activeStepIndexRef.current = null;
39499
+ const detail = `${(event.durationMs / 1e3).toFixed(1)}s`;
39500
+ setSteps(
39501
+ (prev) => prev.map(
39502
+ (s, i) => i === event.index ? { ...s, status: "done", detail } : s
39503
+ )
39504
+ );
39505
+ } else if (event.type === "result") {
39506
+ clearElapsedInterval();
39507
+ setResult(event.data);
39508
+ setIsStreaming(false);
39509
+ } else if (event.type === "error") {
39510
+ clearElapsedInterval();
39511
+ setSteps(
39512
+ (prev) => prev.map(
39513
+ (s) => s.status === "active" ? { ...s, status: "error" } : s
39514
+ )
39515
+ );
39516
+ setError(event.message);
39517
+ setIsStreaming(false);
39518
+ }
39519
+ }
39520
+ } catch (err) {
39521
+ if (!cancelled) {
39522
+ clearElapsedInterval();
39523
+ setError(err instanceof Error ? err.message : "stream error");
39524
+ setIsStreaming(false);
39525
+ }
39526
+ }
39527
+ }
39528
+ run2();
39529
+ return () => {
39530
+ cancelled = true;
39531
+ clearElapsedInterval();
39532
+ controller.abort();
39533
+ abortRef.current = null;
39534
+ };
39535
+ }, [options]);
39536
+ return { steps, result, error, isStreaming };
39537
+ }
39322
39538
 
39323
39539
  // src/ui/colors.ts
39324
39540
  var C = {
@@ -39377,10 +39593,10 @@ function parseCommitType(msg) {
39377
39593
  }
39378
39594
 
39379
39595
  // src/ui/components/diff-viewer.tsx
39380
- var import_react30 = __toESM(require_react(), 1);
39596
+ var import_react31 = __toESM(require_react(), 1);
39381
39597
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
39382
39598
  function DiffViewer({ filePath, patch, onClose }) {
39383
- const [scrollOffset, setScrollOffset] = (0, import_react30.useState)(0);
39599
+ const [scrollOffset, setScrollOffset] = (0, import_react31.useState)(0);
39384
39600
  const termHeight = process.stdout.rows || 24;
39385
39601
  use_input_default((input, key) => {
39386
39602
  if (input === "q" || input === "d" || key.escape) {
@@ -39588,28 +39804,60 @@ function TypeBadge({ type }) {
39588
39804
 
39589
39805
  // src/ui/commit-app.tsx
39590
39806
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
39807
+ var STEP_LABELS = {
39808
+ understand: "Entendendo diff",
39809
+ classify: "Classificando mudan\xE7as",
39810
+ group: "Agrupando commits",
39811
+ generate: "Gerando mensagem"
39812
+ };
39591
39813
  function CommitApp({
39592
39814
  deps,
39593
39815
  onExit
39594
39816
  }) {
39595
39817
  const { exit } = use_app_default();
39596
- const committedMessageRef = (0, import_react31.useRef)("");
39597
- const [phase, setPhase] = (0, import_react31.useState)("analyzing");
39598
- const [message, setMessage] = (0, import_react31.useState)("");
39599
- const [issueRef, setIssueRef] = (0, import_react31.useState)("");
39600
- const [errorMsg, setErrorMsg] = (0, import_react31.useState)("");
39601
- const [fileCount, setFileCount] = (0, import_react31.useState)(0);
39602
- const [additions, setAdditions] = (0, import_react31.useState)(0);
39603
- const [deletions, setDeletions] = (0, import_react31.useState)(0);
39604
- const [rawDiff, setRawDiff] = (0, import_react31.useState)("");
39605
- const [editBuffer, setEditBuffer] = (0, import_react31.useState)("");
39606
- (0, import_react31.useEffect)(() => {
39818
+ const committedMessageRef = (0, import_react32.useRef)("");
39819
+ const [phase, setPhase] = (0, import_react32.useState)("analyzing");
39820
+ const [message, setMessage] = (0, import_react32.useState)("");
39821
+ const [issueRef, setIssueRef] = (0, import_react32.useState)("");
39822
+ const [errorMsg, setErrorMsg] = (0, import_react32.useState)("");
39823
+ const [fileCount, setFileCount] = (0, import_react32.useState)(0);
39824
+ const [additions, setAdditions] = (0, import_react32.useState)(0);
39825
+ const [deletions, setDeletions] = (0, import_react32.useState)(0);
39826
+ const [rawDiff, setRawDiff] = (0, import_react32.useState)("");
39827
+ const [editBuffer, setEditBuffer] = (0, import_react32.useState)("");
39828
+ const [streamPayload, setStreamPayload] = (0, import_react32.useState)(
39829
+ null
39830
+ );
39831
+ const streamOptions = streamPayload && phase === "streaming" ? {
39832
+ path: streamPayload.path,
39833
+ body: streamPayload.body,
39834
+ token: streamPayload.token,
39835
+ postStream: deps.postStream,
39836
+ stepLabels: STEP_LABELS
39837
+ } : null;
39838
+ const stream = usePipelineStream(streamOptions);
39839
+ (0, import_react32.useEffect)(() => {
39607
39840
  if (!TERMINAL_PHASES.includes(phase)) return;
39608
39841
  const code = phase === "done" || phase === "aborted" ? 0 : 1;
39609
39842
  exit();
39610
39843
  onExit(code, phase === "done" ? committedMessageRef.current : void 0);
39611
39844
  }, [phase]);
39612
- (0, import_react31.useEffect)(() => {
39845
+ (0, import_react32.useEffect)(() => {
39846
+ if (phase !== "streaming") return;
39847
+ if (stream.result) {
39848
+ setMessage(stream.result.message);
39849
+ setPhase("link-issue");
39850
+ return;
39851
+ }
39852
+ if (stream.error) {
39853
+ const verbose2 = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
39854
+ setErrorMsg(
39855
+ verbose2 ? `Falha ao gerar mensagem: ${stream.error}` : "Falha ao gerar mensagem. Tente novamente."
39856
+ );
39857
+ setPhase("error-api");
39858
+ }
39859
+ }, [stream.result, stream.error, phase]);
39860
+ (0, import_react32.useEffect)(() => {
39613
39861
  async function run2() {
39614
39862
  const token = deps.readToken();
39615
39863
  if (!token) {
@@ -39638,7 +39886,6 @@ function CommitApp({
39638
39886
  setAdditions(changes.files.reduce((s, f) => s + f.additions, 0));
39639
39887
  setDeletions(changes.files.reduce((s, f) => s + f.deletions, 0));
39640
39888
  setRawDiff(changes.rawDiff);
39641
- setPhase("generating");
39642
39889
  try {
39643
39890
  const stagedFiles = await deps.readStagedFiles(changes.files);
39644
39891
  const body = {
@@ -39648,13 +39895,8 @@ function CommitApp({
39648
39895
  if (deps.fileConfig && Object.keys(deps.fileConfig).length > 0) {
39649
39896
  body.config = deps.fileConfig;
39650
39897
  }
39651
- const result = await deps.apiPost(
39652
- "/commit/generate",
39653
- body,
39654
- token
39655
- );
39656
- setMessage(result.message);
39657
- setPhase("link-issue");
39898
+ setStreamPayload({ path: "/commit/generate", body, token });
39899
+ setPhase("streaming");
39658
39900
  } catch (e) {
39659
39901
  if (e instanceof PlanLimitError) {
39660
39902
  setErrorMsg(
@@ -39769,7 +40011,7 @@ ${bodyParts.join("\n\n")}` : newMsg
39769
40011
  const steps = [];
39770
40012
  const phaseOrder = [
39771
40013
  "analyzing",
39772
- "generating",
40014
+ "streaming",
39773
40015
  "link-issue",
39774
40016
  "confirm",
39775
40017
  "committing",
@@ -39781,10 +40023,30 @@ ${bodyParts.join("\n\n")}` : newMsg
39781
40023
  status: phase === "analyzing" ? "active" : phaseIdx > 0 || TERMINAL_PHASES.includes(phase) ? "done" : "pending",
39782
40024
  detail: phaseIdx > 0 || TERMINAL_PHASES.includes(phase) ? `${fileCount} arquivos \xB7 +${additions} -${deletions}` : void 0
39783
40025
  });
39784
- steps.push({
39785
- label: "Gerando mensagem com IA",
39786
- status: phase === "generating" ? "active" : phaseIdx > 1 || TERMINAL_PHASES.includes(phase) ? message ? "done" : phase.startsWith("error") ? "error" : "pending" : "pending"
39787
- });
40026
+ if (phase === "streaming") {
40027
+ if (stream.steps.length > 0) {
40028
+ steps.push(...stream.steps);
40029
+ } else {
40030
+ steps.push({
40031
+ label: "Gerando mensagem com IA",
40032
+ status: "active"
40033
+ });
40034
+ }
40035
+ } else if (phaseIdx > 1 || TERMINAL_PHASES.includes(phase)) {
40036
+ if (stream.steps.length > 0) {
40037
+ steps.push(...stream.steps);
40038
+ } else {
40039
+ steps.push({
40040
+ label: "Gerando mensagem com IA",
40041
+ status: message ? "done" : phase.startsWith("error") ? "error" : "pending"
40042
+ });
40043
+ }
40044
+ } else {
40045
+ steps.push({
40046
+ label: "Gerando mensagem com IA",
40047
+ status: "pending"
40048
+ });
40049
+ }
39788
40050
  const parsed = parseCommitType(message);
39789
40051
  const hasMessage = phase === "link-issue" || phase === "confirm" || phase === "editing" || phase === "committing" || phase === "done" || phase === "aborted";
39790
40052
  const commitCard = hasMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -39899,7 +40161,7 @@ ${bodyParts.join("\n\n")}` : newMsg
39899
40161
  }
39900
40162
 
39901
40163
  // src/ui/done-summary.tsx
39902
- var import_react32 = __toESM(require_react(), 1);
40164
+ var import_react33 = __toESM(require_react(), 1);
39903
40165
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
39904
40166
  function parseType(msg) {
39905
40167
  const match = msg.match(/^(\w+)(?:\([^)]+\))?!?:\s*(.+)/);
@@ -39907,7 +40169,7 @@ function parseType(msg) {
39907
40169
  }
39908
40170
  function SplitDoneSummary({ commits }) {
39909
40171
  const { exit } = use_app_default();
39910
- (0, import_react32.useEffect)(() => {
40172
+ (0, import_react33.useEffect)(() => {
39911
40173
  exit();
39912
40174
  }, []);
39913
40175
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
@@ -39936,7 +40198,7 @@ function SplitDoneSummary({ commits }) {
39936
40198
  }
39937
40199
  function CommitDoneSummary({ message }) {
39938
40200
  const { exit } = use_app_default();
39939
- (0, import_react32.useEffect)(() => {
40201
+ (0, import_react33.useEffect)(() => {
39940
40202
  exit();
39941
40203
  }, []);
39942
40204
  const { type, description } = parseType(message);
@@ -39961,12 +40223,13 @@ async function commitCommand() {
39961
40223
  let completedMessage = "";
39962
40224
  enterImmersive();
39963
40225
  const { waitUntilExit } = render_default(
39964
- import_react33.default.createElement(CommitApp, {
40226
+ import_react34.default.createElement(CommitApp, {
39965
40227
  deps: {
39966
40228
  readToken,
39967
40229
  getStagedChanges,
39968
40230
  readStagedFiles,
39969
40231
  apiPost: (path, body, tkn) => apiClient.post(path, body, tkn),
40232
+ postStream: (path, body, tkn, timeoutMs) => apiClient.postStream(path, body, tkn, timeoutMs),
39970
40233
  runGitCommit,
39971
40234
  fileConfig
39972
40235
  },
@@ -39980,7 +40243,7 @@ async function commitCommand() {
39980
40243
  exitImmersive();
39981
40244
  if (completedMessage) {
39982
40245
  const { waitUntilExit: waitUntilExit2 } = render_default(
39983
- import_react33.default.createElement(CommitDoneSummary, { message: completedMessage })
40246
+ import_react34.default.createElement(CommitDoneSummary, { message: completedMessage })
39984
40247
  );
39985
40248
  await waitUntilExit2();
39986
40249
  }
@@ -39988,7 +40251,7 @@ async function commitCommand() {
39988
40251
  }
39989
40252
 
39990
40253
  // src/commands/help.tsx
39991
- var import_react34 = __toESM(require_react(), 1);
40254
+ var import_react35 = __toESM(require_react(), 1);
39992
40255
  var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
39993
40256
  var COMMANDS = [
39994
40257
  {
@@ -40070,7 +40333,7 @@ function HelpApp() {
40070
40333
  ] });
40071
40334
  }
40072
40335
  async function helpCommand() {
40073
- const { waitUntilExit } = render_default(import_react34.default.createElement(HelpApp));
40336
+ const { waitUntilExit } = render_default(import_react35.default.createElement(HelpApp));
40074
40337
  setTimeout(() => process.exit(0), 50);
40075
40338
  await waitUntilExit();
40076
40339
  }
@@ -40161,10 +40424,10 @@ async function openBrowser(url) {
40161
40424
  }
40162
40425
 
40163
40426
  // src/commands/login.ts
40164
- var import_react36 = __toESM(require_react(), 1);
40427
+ var import_react37 = __toESM(require_react(), 1);
40165
40428
 
40166
40429
  // src/ui/login-app.tsx
40167
- var import_react35 = __toESM(require_react(), 1);
40430
+ var import_react36 = __toESM(require_react(), 1);
40168
40431
  var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
40169
40432
  var TERMINAL_PHASES2 = [
40170
40433
  "done",
@@ -40178,17 +40441,17 @@ function LoginApp({
40178
40441
  onExit
40179
40442
  }) {
40180
40443
  const { exit } = use_app_default();
40181
- const [phase, setPhase] = (0, import_react35.useState)("opening");
40182
- const [authUrl, setAuthUrl] = (0, import_react35.useState)("");
40183
- const [email, setEmail] = (0, import_react35.useState)("");
40184
- const [errorMsg, setErrorMsg] = (0, import_react35.useState)("");
40185
- (0, import_react35.useEffect)(() => {
40444
+ const [phase, setPhase] = (0, import_react36.useState)("opening");
40445
+ const [authUrl, setAuthUrl] = (0, import_react36.useState)("");
40446
+ const [email, setEmail] = (0, import_react36.useState)("");
40447
+ const [errorMsg, setErrorMsg] = (0, import_react36.useState)("");
40448
+ (0, import_react36.useEffect)(() => {
40186
40449
  if (!TERMINAL_PHASES2.includes(phase)) return;
40187
40450
  const code = phase === "done" ? 0 : 1;
40188
40451
  exit();
40189
40452
  onExit(code);
40190
40453
  }, [phase]);
40191
- (0, import_react35.useEffect)(() => {
40454
+ (0, import_react36.useEffect)(() => {
40192
40455
  async function run2() {
40193
40456
  const port = await deps.getAvailablePort();
40194
40457
  const baseUrl = "https://repomind.dev";
@@ -40269,7 +40532,7 @@ var apiClient2 = createApiClient();
40269
40532
  async function loginCommand() {
40270
40533
  let exitCode = 0;
40271
40534
  const { waitUntilExit } = render_default(
40272
- import_react36.default.createElement(LoginApp, {
40535
+ import_react37.default.createElement(LoginApp, {
40273
40536
  deps: {
40274
40537
  getAvailablePort,
40275
40538
  startCallbackServer,
@@ -40288,12 +40551,12 @@ async function loginCommand() {
40288
40551
  }
40289
40552
 
40290
40553
  // src/commands/logout.tsx
40291
- var import_react37 = __toESM(require_react(), 1);
40554
+ var import_react38 = __toESM(require_react(), 1);
40292
40555
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
40293
40556
  function LogoutApp({ onExit }) {
40294
40557
  const wasLoggedIn = readToken() !== null;
40295
40558
  const deleted = deleteToken();
40296
- (0, import_react37.useEffect)(() => {
40559
+ (0, import_react38.useEffect)(() => {
40297
40560
  onExit(0);
40298
40561
  }, [onExit]);
40299
40562
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, marginBottom: 1, children: [
@@ -40310,7 +40573,7 @@ function LogoutApp({ onExit }) {
40310
40573
  async function logoutCommand() {
40311
40574
  let exitCode = 0;
40312
40575
  const { waitUntilExit } = render_default(
40313
- import_react37.default.createElement(LogoutApp, {
40576
+ import_react38.default.createElement(LogoutApp, {
40314
40577
  onExit: (code) => {
40315
40578
  exitCode = code;
40316
40579
  }
@@ -40321,10 +40584,10 @@ async function logoutCommand() {
40321
40584
  }
40322
40585
 
40323
40586
  // src/commands/pr.ts
40324
- var import_react39 = __toESM(require_react(), 1);
40587
+ var import_react40 = __toESM(require_react(), 1);
40325
40588
 
40326
40589
  // src/ui/pr-app.tsx
40327
- var import_react38 = __toESM(require_react(), 1);
40590
+ var import_react39 = __toESM(require_react(), 1);
40328
40591
  var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
40329
40592
  var TERMINAL_PHASES3 = [
40330
40593
  "aborted",
@@ -40338,18 +40601,18 @@ function PrApp({
40338
40601
  onExit
40339
40602
  }) {
40340
40603
  const { exit } = use_app_default();
40341
- const [phase, setPhase] = (0, import_react38.useState)("collecting");
40342
- const [prTitle, setPrTitle] = (0, import_react38.useState)("");
40343
- const [prBody, setPrBody] = (0, import_react38.useState)("");
40344
- const [errorMsg, setErrorMsg] = (0, import_react38.useState)("");
40345
- (0, import_react38.useEffect)(() => {
40604
+ const [phase, setPhase] = (0, import_react39.useState)("collecting");
40605
+ const [prTitle, setPrTitle] = (0, import_react39.useState)("");
40606
+ const [prBody, setPrBody] = (0, import_react39.useState)("");
40607
+ const [errorMsg, setErrorMsg] = (0, import_react39.useState)("");
40608
+ (0, import_react39.useEffect)(() => {
40346
40609
  if (!TERMINAL_PHASES3.includes(phase) && phase !== "copied" && phase !== "aborted")
40347
40610
  return;
40348
40611
  const code = phase === "copied" || phase === "aborted" ? 0 : 1;
40349
40612
  exit();
40350
40613
  onExit(code);
40351
40614
  }, [phase]);
40352
- (0, import_react38.useEffect)(() => {
40615
+ (0, import_react39.useEffect)(() => {
40353
40616
  async function run2() {
40354
40617
  const token = deps.readToken();
40355
40618
  if (!token) {
@@ -40536,7 +40799,7 @@ async function prCommand() {
40536
40799
  let exitCode = 0;
40537
40800
  enterImmersive();
40538
40801
  const { waitUntilExit } = render_default(
40539
- import_react39.default.createElement(PrApp, {
40802
+ import_react40.default.createElement(PrApp, {
40540
40803
  deps: {
40541
40804
  readToken,
40542
40805
  getDiffBetweenBranches,
@@ -40554,10 +40817,10 @@ async function prCommand() {
40554
40817
  }
40555
40818
 
40556
40819
  // src/commands/split.ts
40557
- var import_react41 = __toESM(require_react(), 1);
40820
+ var import_react42 = __toESM(require_react(), 1);
40558
40821
 
40559
40822
  // src/ui/split-app.tsx
40560
- var import_react40 = __toESM(require_react(), 1);
40823
+ var import_react41 = __toESM(require_react(), 1);
40561
40824
 
40562
40825
  // src/ui/components/diff-stats.tsx
40563
40826
  var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
@@ -40684,6 +40947,12 @@ function CommitRow(props) {
40684
40947
 
40685
40948
  // src/ui/split-app.tsx
40686
40949
  var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
40950
+ var STEP_LABELS2 = {
40951
+ understand: "Entendendo altera\xE7\xF5es",
40952
+ classify: "Classificando contextos",
40953
+ group: "Agrupando em commits",
40954
+ generate: "Gerando mensagens"
40955
+ };
40687
40956
  var TERMINAL_PHASES4 = [
40688
40957
  "done",
40689
40958
  "aborted",
@@ -40731,24 +41000,54 @@ function SplitApp({
40731
41000
  onExit
40732
41001
  }) {
40733
41002
  const { exit } = use_app_default();
40734
- const committedMessagesRef = (0, import_react40.useRef)([]);
40735
- const [phase, setPhase] = (0, import_react40.useState)("collecting");
40736
- const [groups, setGroups] = (0, import_react40.useState)([]);
40737
- const [selectedIndex, setSelectedIndex] = (0, import_react40.useState)(0);
40738
- const [skippedGroups, setSkippedGroups] = (0, import_react40.useState)(/* @__PURE__ */ new Set());
40739
- const [commitProgress, setCommitProgress] = (0, import_react40.useState)(0);
40740
- const [errorMsg, setErrorMsg] = (0, import_react40.useState)("");
40741
- const [originalFiles, setOriginalFiles] = (0, import_react40.useState)([]);
40742
- const [hunksMap, setHunksMap] = (0, import_react40.useState)(/* @__PURE__ */ new Map());
40743
- const [patchMap, setPatchMap] = (0, import_react40.useState)(/* @__PURE__ */ new Map());
40744
- const [authToken, setAuthToken] = (0, import_react40.useState)("");
40745
- (0, import_react40.useEffect)(() => {
41003
+ const committedMessagesRef = (0, import_react41.useRef)([]);
41004
+ const [phase, setPhase] = (0, import_react41.useState)("collecting");
41005
+ const [groups, setGroups] = (0, import_react41.useState)([]);
41006
+ const [selectedIndex, setSelectedIndex] = (0, import_react41.useState)(0);
41007
+ const [skippedGroups, setSkippedGroups] = (0, import_react41.useState)(/* @__PURE__ */ new Set());
41008
+ const [commitProgress, setCommitProgress] = (0, import_react41.useState)(0);
41009
+ const [errorMsg, setErrorMsg] = (0, import_react41.useState)("");
41010
+ const [originalFiles, setOriginalFiles] = (0, import_react41.useState)([]);
41011
+ const [hunksMap, setHunksMap] = (0, import_react41.useState)(/* @__PURE__ */ new Map());
41012
+ const [patchMap, setPatchMap] = (0, import_react41.useState)(/* @__PURE__ */ new Map());
41013
+ const [authToken, setAuthToken] = (0, import_react41.useState)("");
41014
+ const [streamPayload, setStreamPayload] = (0, import_react41.useState)(
41015
+ null
41016
+ );
41017
+ const streamOptions = streamPayload && phase === "analyzing" ? {
41018
+ path: streamPayload.path,
41019
+ body: streamPayload.body,
41020
+ token: streamPayload.token,
41021
+ postStream: deps.postStream,
41022
+ stepLabels: STEP_LABELS2,
41023
+ timeoutMs: 3e5
41024
+ } : null;
41025
+ const stream = usePipelineStream(streamOptions);
41026
+ (0, import_react41.useEffect)(() => {
40746
41027
  if (!TERMINAL_PHASES4.includes(phase)) return;
40747
41028
  const code = phase === "done" || phase === "aborted" ? 0 : 1;
40748
41029
  exit();
40749
41030
  onExit(code, phase === "done" ? committedMessagesRef.current : void 0);
40750
41031
  }, [phase]);
40751
- (0, import_react40.useEffect)(() => {
41032
+ (0, import_react41.useEffect)(() => {
41033
+ if (phase !== "analyzing") return;
41034
+ if (stream.result) {
41035
+ const sortedGroups = stream.result.groups.sort(
41036
+ (a, b) => a.order - b.order
41037
+ );
41038
+ setGroups(sortedGroups);
41039
+ setPhase("review");
41040
+ return;
41041
+ }
41042
+ if (stream.error) {
41043
+ const verbose2 = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
41044
+ setErrorMsg(
41045
+ verbose2 ? `Falha ao analisar altera\xE7\xF5es: ${stream.error}` : "Falha ao analisar altera\xE7\xF5es. Tente novamente."
41046
+ );
41047
+ setPhase("error-api");
41048
+ }
41049
+ }, [stream.result, stream.error, phase]);
41050
+ (0, import_react41.useEffect)(() => {
40752
41051
  async function run2() {
40753
41052
  const token = deps.readToken();
40754
41053
  if (!token) {
@@ -40813,43 +41112,12 @@ function SplitApp({
40813
41112
  setHunksMap(hMap);
40814
41113
  setPatchMap(pMap);
40815
41114
  const manifest = deps.buildSplitManifest(files, hMap);
40816
- setPhase("analyzing");
40817
- try {
40818
- const body = { manifest };
40819
- if (deps.fileConfig && Object.keys(deps.fileConfig).length > 0) {
40820
- body.config = deps.fileConfig;
40821
- }
40822
- const result = await deps.apiPost(
40823
- "/split/analyze",
40824
- body,
40825
- token,
40826
- 12e4
40827
- );
40828
- const sortedGroups = result.groups.sort((a, b) => a.order - b.order);
40829
- setGroups(sortedGroups);
40830
- setPhase("review");
40831
- } catch (e) {
40832
- if (e instanceof PlanLimitError) {
40833
- setErrorMsg(
40834
- `Limite do plano ${e.plan === "free" ? "Free" : "Pro"} atingido (${e.used}/${e.limit} commits este m\xEAs)
40835
- \u2192 Fa\xE7a upgrade em https://app.repomind.dev/pricing`
40836
- );
40837
- setPhase("error-plan");
40838
- } else if (e instanceof ApiError && e.status === 401) {
40839
- setErrorMsg("Sess\xE3o expirada. Execute repomind login novamente.");
40840
- setPhase("error-api");
40841
- } else {
40842
- const detail = e instanceof Error ? e.message : "Erro desconhecido";
40843
- const verbose2 = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
40844
- setErrorMsg(
40845
- verbose2 ? `Falha ao analisar altera\xE7\xF5es: ${detail}` : "Falha ao analisar altera\xE7\xF5es. Tente novamente."
40846
- );
40847
- if (verbose2 && e instanceof Error && e.stack) {
40848
- console.error(e.stack);
40849
- }
40850
- setPhase("error-api");
40851
- }
41115
+ const body = { manifest };
41116
+ if (deps.fileConfig && Object.keys(deps.fileConfig).length > 0) {
41117
+ body.config = deps.fileConfig;
40852
41118
  }
41119
+ setStreamPayload({ path: "/split/analyze", body, token });
41120
+ setPhase("analyzing");
40853
41121
  }
40854
41122
  run2();
40855
41123
  }, []);
@@ -40941,9 +41209,9 @@ function SplitApp({
40941
41209
  });
40942
41210
  }
40943
41211
  }
40944
- }
40945
- if (lineSelections.length > 0) {
40946
- await deps.stageLines(currentDiff, lineSelections);
41212
+ if (lineSelections.length > 0) {
41213
+ await deps.stageLines(currentDiff, lineSelections);
41214
+ }
40947
41215
  }
40948
41216
  }
40949
41217
  }
@@ -41001,10 +41269,15 @@ function SplitApp({
41001
41269
  status: phase === "collecting" ? "active" : "done",
41002
41270
  detail: phase !== "collecting" ? `${originalFiles.length} arquivos` : void 0
41003
41271
  },
41004
- {
41005
- label: "Analisando com IA",
41006
- status: phase === "analyzing" ? "active" : "pending"
41007
- }
41272
+ // Steps dinâmicos do SSE (aparecem quando pipeline anuncia step:start)
41273
+ ...stream.steps,
41274
+ // Fallback: se stream ainda não tem steps e está analisando, exibir step genérico
41275
+ ...phase === "analyzing" && stream.steps.length === 0 ? [
41276
+ {
41277
+ label: "Analisando com IA",
41278
+ status: "active"
41279
+ }
41280
+ ] : []
41008
41281
  ]
41009
41282
  }
41010
41283
  ),
@@ -41154,7 +41427,7 @@ async function splitCommand() {
41154
41427
  let completedCommits = [];
41155
41428
  enterImmersive();
41156
41429
  const { waitUntilExit } = render_default(
41157
- import_react41.default.createElement(SplitApp, {
41430
+ import_react42.default.createElement(SplitApp, {
41158
41431
  deps: {
41159
41432
  readToken,
41160
41433
  getAllChanges,
@@ -41171,6 +41444,7 @@ async function splitCommand() {
41171
41444
  validatePatch,
41172
41445
  getFileDiff,
41173
41446
  apiPost: (path, body, tkn, timeoutMs) => apiClient.post(path, body, tkn, timeoutMs),
41447
+ postStream: (path, body, tkn, timeoutMs) => apiClient.postStream(path, body, tkn, timeoutMs),
41174
41448
  runGitCommit: runGitCommit2,
41175
41449
  fileConfig,
41176
41450
  useStaged
@@ -41185,7 +41459,7 @@ async function splitCommand() {
41185
41459
  exitImmersive();
41186
41460
  if (completedCommits.length > 0) {
41187
41461
  const { waitUntilExit: waitUntilExit2 } = render_default(
41188
- import_react41.default.createElement(SplitDoneSummary, { commits: completedCommits })
41462
+ import_react42.default.createElement(SplitDoneSummary, { commits: completedCommits })
41189
41463
  );
41190
41464
  await waitUntilExit2();
41191
41465
  }
@@ -41193,12 +41467,12 @@ async function splitCommand() {
41193
41467
  }
41194
41468
 
41195
41469
  // src/commands/whoami.tsx
41196
- var import_react42 = __toESM(require_react(), 1);
41470
+ var import_react43 = __toESM(require_react(), 1);
41197
41471
  var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
41198
41472
  function WhoamiApp({ onExit }) {
41199
41473
  const token = readToken();
41200
41474
  const payload = token ? decodeTokenPayload(token) : null;
41201
- (0, import_react42.useEffect)(() => {
41475
+ (0, import_react43.useEffect)(() => {
41202
41476
  onExit(payload ? 0 : 1);
41203
41477
  }, [onExit, payload]);
41204
41478
  const expiresDate = payload ? new Date(payload.exp * 1e3).toLocaleDateString("pt-BR", {
@@ -41234,7 +41508,7 @@ function WhoamiApp({ onExit }) {
41234
41508
  async function whoamiCommand() {
41235
41509
  let exitCode = 0;
41236
41510
  const { waitUntilExit } = render_default(
41237
- import_react42.default.createElement(WhoamiApp, {
41511
+ import_react43.default.createElement(WhoamiApp, {
41238
41512
  onExit: (code) => {
41239
41513
  exitCode = code;
41240
41514
  }
@@ -41246,7 +41520,7 @@ async function whoamiCommand() {
41246
41520
 
41247
41521
  // src/index.ts
41248
41522
  import { createRequire } from "node:module";
41249
- var import_react43 = __toESM(require_react(), 1);
41523
+ var import_react44 = __toESM(require_react(), 1);
41250
41524
 
41251
41525
  // src/ui/components/command-help.tsx
41252
41526
  var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
@@ -41378,7 +41652,7 @@ var COMMAND_HELP = {
41378
41652
  if (wantsHelp && command && COMMAND_HELP[command]) {
41379
41653
  const def = COMMAND_HELP[command];
41380
41654
  const { waitUntilExit } = render_default(
41381
- import_react43.default.createElement(CommandHelp, {
41655
+ import_react44.default.createElement(CommandHelp, {
41382
41656
  command,
41383
41657
  usage: def.usage,
41384
41658
  description: def.description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repomind",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "AI-powered git commit messages and repository insights",
6
6
  "keywords": ["git", "ai", "commit", "cli"],