repomind 0.4.0 → 0.5.1

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 +413 -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,198 @@ 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
+ const optionsRef = (0, import_react30.useRef)(options);
39431
+ optionsRef.current = options;
39432
+ const triggerKey = options ? `${options.path}:${options.token}` : null;
39433
+ (0, import_react30.useEffect)(() => {
39434
+ const opts = optionsRef.current;
39435
+ if (!opts) return;
39436
+ let cancelled = false;
39437
+ const controller = new AbortController();
39438
+ abortRef.current = controller;
39439
+ setIsStreaming(true);
39440
+ setSteps((prev) => prev.length > 0 ? [] : prev);
39441
+ setResult((prev) => prev !== null ? null : prev);
39442
+ setError((prev) => prev !== null ? null : prev);
39443
+ async function run2() {
39444
+ if (!opts) return;
39445
+ let response;
39446
+ try {
39447
+ response = await opts.postStream(
39448
+ opts.path,
39449
+ opts.body,
39450
+ opts.token,
39451
+ opts.timeoutMs
39452
+ );
39453
+ } catch (err) {
39454
+ if (cancelled) return;
39455
+ setError(err instanceof Error ? err.message : "request failed");
39456
+ setIsStreaming(false);
39457
+ return;
39458
+ }
39459
+ if (cancelled) return;
39460
+ const contentType = response.headers.get("Content-Type") ?? "";
39461
+ if (contentType.includes("application/json")) {
39462
+ try {
39463
+ const data = await response.json();
39464
+ if (!cancelled) {
39465
+ setResult(data);
39466
+ setIsStreaming(false);
39467
+ }
39468
+ } catch (err) {
39469
+ if (!cancelled) {
39470
+ setError(
39471
+ err instanceof Error ? err.message : "failed to parse JSON"
39472
+ );
39473
+ setIsStreaming(false);
39474
+ }
39475
+ }
39476
+ return;
39477
+ }
39478
+ if (!response.body) {
39479
+ if (!cancelled) {
39480
+ setError("response body is null");
39481
+ setIsStreaming(false);
39482
+ }
39483
+ return;
39484
+ }
39485
+ try {
39486
+ for await (const event of parseSSEStream(response.body)) {
39487
+ if (cancelled) break;
39488
+ if (event.type === "step:start") {
39489
+ const newStep = {
39490
+ label: opts.stepLabels[event.step] ?? event.step,
39491
+ status: "active"
39492
+ };
39493
+ setSteps((prev) => {
39494
+ const updated = prev.map(
39495
+ (s) => s.status === "active" ? { ...s, status: "done" } : s
39496
+ );
39497
+ startElapsedTimer(updated.length);
39498
+ return [...updated, newStep];
39499
+ });
39500
+ } else if (event.type === "step:complete") {
39501
+ clearElapsedInterval();
39502
+ activeStepIndexRef.current = null;
39503
+ const detail = `${(event.durationMs / 1e3).toFixed(1)}s`;
39504
+ const idx = event.index - 1;
39505
+ setSteps(
39506
+ (prev) => prev.map(
39507
+ (s, i) => i === idx ? { ...s, status: "done", detail } : s
39508
+ )
39509
+ );
39510
+ } else if (event.type === "result") {
39511
+ clearElapsedInterval();
39512
+ setSteps(
39513
+ (prev) => prev.map(
39514
+ (s) => s.status === "active" ? { ...s, status: "done" } : s
39515
+ )
39516
+ );
39517
+ setResult(event.data);
39518
+ setIsStreaming(false);
39519
+ } else if (event.type === "error") {
39520
+ clearElapsedInterval();
39521
+ setSteps(
39522
+ (prev) => prev.map(
39523
+ (s) => s.status === "active" ? { ...s, status: "error" } : s
39524
+ )
39525
+ );
39526
+ setError(event.message);
39527
+ setIsStreaming(false);
39528
+ }
39529
+ }
39530
+ } catch (err) {
39531
+ if (!cancelled) {
39532
+ clearElapsedInterval();
39533
+ setError(err instanceof Error ? err.message : "stream error");
39534
+ setIsStreaming(false);
39535
+ }
39536
+ }
39537
+ }
39538
+ run2();
39539
+ return () => {
39540
+ cancelled = true;
39541
+ clearElapsedInterval();
39542
+ controller.abort();
39543
+ abortRef.current = null;
39544
+ };
39545
+ }, [triggerKey]);
39546
+ return (0, import_react30.useMemo)(
39547
+ () => ({ steps, result, error, isStreaming }),
39548
+ [steps, result, error, isStreaming]
39549
+ );
39550
+ }
39322
39551
 
39323
39552
  // src/ui/colors.ts
39324
39553
  var C = {
@@ -39377,10 +39606,10 @@ function parseCommitType(msg) {
39377
39606
  }
39378
39607
 
39379
39608
  // src/ui/components/diff-viewer.tsx
39380
- var import_react30 = __toESM(require_react(), 1);
39609
+ var import_react31 = __toESM(require_react(), 1);
39381
39610
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
39382
39611
  function DiffViewer({ filePath, patch, onClose }) {
39383
- const [scrollOffset, setScrollOffset] = (0, import_react30.useState)(0);
39612
+ const [scrollOffset, setScrollOffset] = (0, import_react31.useState)(0);
39384
39613
  const termHeight = process.stdout.rows || 24;
39385
39614
  use_input_default((input, key) => {
39386
39615
  if (input === "q" || input === "d" || key.escape) {
@@ -39588,28 +39817,60 @@ function TypeBadge({ type }) {
39588
39817
 
39589
39818
  // src/ui/commit-app.tsx
39590
39819
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
39820
+ var STEP_LABELS = {
39821
+ understand: "Entendendo diff",
39822
+ classify: "Classificando mudan\xE7as",
39823
+ group: "Agrupando commits",
39824
+ generate: "Gerando mensagem"
39825
+ };
39591
39826
  function CommitApp({
39592
39827
  deps,
39593
39828
  onExit
39594
39829
  }) {
39595
39830
  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)(() => {
39831
+ const committedMessageRef = (0, import_react32.useRef)("");
39832
+ const [phase, setPhase] = (0, import_react32.useState)("analyzing");
39833
+ const [message, setMessage] = (0, import_react32.useState)("");
39834
+ const [issueRef, setIssueRef] = (0, import_react32.useState)("");
39835
+ const [errorMsg, setErrorMsg] = (0, import_react32.useState)("");
39836
+ const [fileCount, setFileCount] = (0, import_react32.useState)(0);
39837
+ const [additions, setAdditions] = (0, import_react32.useState)(0);
39838
+ const [deletions, setDeletions] = (0, import_react32.useState)(0);
39839
+ const [rawDiff, setRawDiff] = (0, import_react32.useState)("");
39840
+ const [editBuffer, setEditBuffer] = (0, import_react32.useState)("");
39841
+ const [streamPayload, setStreamPayload] = (0, import_react32.useState)(
39842
+ null
39843
+ );
39844
+ const streamOptions = streamPayload && phase === "streaming" ? {
39845
+ path: streamPayload.path,
39846
+ body: streamPayload.body,
39847
+ token: streamPayload.token,
39848
+ postStream: deps.postStream,
39849
+ stepLabels: STEP_LABELS
39850
+ } : null;
39851
+ const stream = usePipelineStream(streamOptions);
39852
+ (0, import_react32.useEffect)(() => {
39607
39853
  if (!TERMINAL_PHASES.includes(phase)) return;
39608
39854
  const code = phase === "done" || phase === "aborted" ? 0 : 1;
39609
39855
  exit();
39610
39856
  onExit(code, phase === "done" ? committedMessageRef.current : void 0);
39611
39857
  }, [phase]);
39612
- (0, import_react31.useEffect)(() => {
39858
+ (0, import_react32.useEffect)(() => {
39859
+ if (phase !== "streaming") return;
39860
+ if (stream.result) {
39861
+ setMessage(stream.result.message);
39862
+ setPhase("link-issue");
39863
+ return;
39864
+ }
39865
+ if (stream.error) {
39866
+ const verbose2 = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
39867
+ setErrorMsg(
39868
+ verbose2 ? `Falha ao gerar mensagem: ${stream.error}` : "Falha ao gerar mensagem. Tente novamente."
39869
+ );
39870
+ setPhase("error-api");
39871
+ }
39872
+ }, [stream.result, stream.error, phase]);
39873
+ (0, import_react32.useEffect)(() => {
39613
39874
  async function run2() {
39614
39875
  const token = deps.readToken();
39615
39876
  if (!token) {
@@ -39638,7 +39899,6 @@ function CommitApp({
39638
39899
  setAdditions(changes.files.reduce((s, f) => s + f.additions, 0));
39639
39900
  setDeletions(changes.files.reduce((s, f) => s + f.deletions, 0));
39640
39901
  setRawDiff(changes.rawDiff);
39641
- setPhase("generating");
39642
39902
  try {
39643
39903
  const stagedFiles = await deps.readStagedFiles(changes.files);
39644
39904
  const body = {
@@ -39648,13 +39908,8 @@ function CommitApp({
39648
39908
  if (deps.fileConfig && Object.keys(deps.fileConfig).length > 0) {
39649
39909
  body.config = deps.fileConfig;
39650
39910
  }
39651
- const result = await deps.apiPost(
39652
- "/commit/generate",
39653
- body,
39654
- token
39655
- );
39656
- setMessage(result.message);
39657
- setPhase("link-issue");
39911
+ setStreamPayload({ path: "/commit/generate", body, token });
39912
+ setPhase("streaming");
39658
39913
  } catch (e) {
39659
39914
  if (e instanceof PlanLimitError) {
39660
39915
  setErrorMsg(
@@ -39769,7 +40024,7 @@ ${bodyParts.join("\n\n")}` : newMsg
39769
40024
  const steps = [];
39770
40025
  const phaseOrder = [
39771
40026
  "analyzing",
39772
- "generating",
40027
+ "streaming",
39773
40028
  "link-issue",
39774
40029
  "confirm",
39775
40030
  "committing",
@@ -39781,10 +40036,30 @@ ${bodyParts.join("\n\n")}` : newMsg
39781
40036
  status: phase === "analyzing" ? "active" : phaseIdx > 0 || TERMINAL_PHASES.includes(phase) ? "done" : "pending",
39782
40037
  detail: phaseIdx > 0 || TERMINAL_PHASES.includes(phase) ? `${fileCount} arquivos \xB7 +${additions} -${deletions}` : void 0
39783
40038
  });
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
- });
40039
+ if (phase === "streaming") {
40040
+ if (stream.steps.length > 0) {
40041
+ steps.push(...stream.steps);
40042
+ } else {
40043
+ steps.push({
40044
+ label: "Gerando mensagem com IA",
40045
+ status: "active"
40046
+ });
40047
+ }
40048
+ } else if (phaseIdx > 1 || TERMINAL_PHASES.includes(phase)) {
40049
+ if (stream.steps.length > 0) {
40050
+ steps.push(...stream.steps);
40051
+ } else {
40052
+ steps.push({
40053
+ label: "Gerando mensagem com IA",
40054
+ status: message ? "done" : phase.startsWith("error") ? "error" : "pending"
40055
+ });
40056
+ }
40057
+ } else {
40058
+ steps.push({
40059
+ label: "Gerando mensagem com IA",
40060
+ status: "pending"
40061
+ });
40062
+ }
39788
40063
  const parsed = parseCommitType(message);
39789
40064
  const hasMessage = phase === "link-issue" || phase === "confirm" || phase === "editing" || phase === "committing" || phase === "done" || phase === "aborted";
39790
40065
  const commitCard = hasMessage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
@@ -39899,7 +40174,7 @@ ${bodyParts.join("\n\n")}` : newMsg
39899
40174
  }
39900
40175
 
39901
40176
  // src/ui/done-summary.tsx
39902
- var import_react32 = __toESM(require_react(), 1);
40177
+ var import_react33 = __toESM(require_react(), 1);
39903
40178
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
39904
40179
  function parseType(msg) {
39905
40180
  const match = msg.match(/^(\w+)(?:\([^)]+\))?!?:\s*(.+)/);
@@ -39907,7 +40182,7 @@ function parseType(msg) {
39907
40182
  }
39908
40183
  function SplitDoneSummary({ commits }) {
39909
40184
  const { exit } = use_app_default();
39910
- (0, import_react32.useEffect)(() => {
40185
+ (0, import_react33.useEffect)(() => {
39911
40186
  exit();
39912
40187
  }, []);
39913
40188
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
@@ -39936,7 +40211,7 @@ function SplitDoneSummary({ commits }) {
39936
40211
  }
39937
40212
  function CommitDoneSummary({ message }) {
39938
40213
  const { exit } = use_app_default();
39939
- (0, import_react32.useEffect)(() => {
40214
+ (0, import_react33.useEffect)(() => {
39940
40215
  exit();
39941
40216
  }, []);
39942
40217
  const { type, description } = parseType(message);
@@ -39961,12 +40236,13 @@ async function commitCommand() {
39961
40236
  let completedMessage = "";
39962
40237
  enterImmersive();
39963
40238
  const { waitUntilExit } = render_default(
39964
- import_react33.default.createElement(CommitApp, {
40239
+ import_react34.default.createElement(CommitApp, {
39965
40240
  deps: {
39966
40241
  readToken,
39967
40242
  getStagedChanges,
39968
40243
  readStagedFiles,
39969
40244
  apiPost: (path, body, tkn) => apiClient.post(path, body, tkn),
40245
+ postStream: (path, body, tkn, timeoutMs) => apiClient.postStream(path, body, tkn, timeoutMs),
39970
40246
  runGitCommit,
39971
40247
  fileConfig
39972
40248
  },
@@ -39980,7 +40256,7 @@ async function commitCommand() {
39980
40256
  exitImmersive();
39981
40257
  if (completedMessage) {
39982
40258
  const { waitUntilExit: waitUntilExit2 } = render_default(
39983
- import_react33.default.createElement(CommitDoneSummary, { message: completedMessage })
40259
+ import_react34.default.createElement(CommitDoneSummary, { message: completedMessage })
39984
40260
  );
39985
40261
  await waitUntilExit2();
39986
40262
  }
@@ -39988,7 +40264,7 @@ async function commitCommand() {
39988
40264
  }
39989
40265
 
39990
40266
  // src/commands/help.tsx
39991
- var import_react34 = __toESM(require_react(), 1);
40267
+ var import_react35 = __toESM(require_react(), 1);
39992
40268
  var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
39993
40269
  var COMMANDS = [
39994
40270
  {
@@ -40070,7 +40346,7 @@ function HelpApp() {
40070
40346
  ] });
40071
40347
  }
40072
40348
  async function helpCommand() {
40073
- const { waitUntilExit } = render_default(import_react34.default.createElement(HelpApp));
40349
+ const { waitUntilExit } = render_default(import_react35.default.createElement(HelpApp));
40074
40350
  setTimeout(() => process.exit(0), 50);
40075
40351
  await waitUntilExit();
40076
40352
  }
@@ -40161,10 +40437,10 @@ async function openBrowser(url) {
40161
40437
  }
40162
40438
 
40163
40439
  // src/commands/login.ts
40164
- var import_react36 = __toESM(require_react(), 1);
40440
+ var import_react37 = __toESM(require_react(), 1);
40165
40441
 
40166
40442
  // src/ui/login-app.tsx
40167
- var import_react35 = __toESM(require_react(), 1);
40443
+ var import_react36 = __toESM(require_react(), 1);
40168
40444
  var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
40169
40445
  var TERMINAL_PHASES2 = [
40170
40446
  "done",
@@ -40178,17 +40454,17 @@ function LoginApp({
40178
40454
  onExit
40179
40455
  }) {
40180
40456
  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)(() => {
40457
+ const [phase, setPhase] = (0, import_react36.useState)("opening");
40458
+ const [authUrl, setAuthUrl] = (0, import_react36.useState)("");
40459
+ const [email, setEmail] = (0, import_react36.useState)("");
40460
+ const [errorMsg, setErrorMsg] = (0, import_react36.useState)("");
40461
+ (0, import_react36.useEffect)(() => {
40186
40462
  if (!TERMINAL_PHASES2.includes(phase)) return;
40187
40463
  const code = phase === "done" ? 0 : 1;
40188
40464
  exit();
40189
40465
  onExit(code);
40190
40466
  }, [phase]);
40191
- (0, import_react35.useEffect)(() => {
40467
+ (0, import_react36.useEffect)(() => {
40192
40468
  async function run2() {
40193
40469
  const port = await deps.getAvailablePort();
40194
40470
  const baseUrl = "https://repomind.dev";
@@ -40269,7 +40545,7 @@ var apiClient2 = createApiClient();
40269
40545
  async function loginCommand() {
40270
40546
  let exitCode = 0;
40271
40547
  const { waitUntilExit } = render_default(
40272
- import_react36.default.createElement(LoginApp, {
40548
+ import_react37.default.createElement(LoginApp, {
40273
40549
  deps: {
40274
40550
  getAvailablePort,
40275
40551
  startCallbackServer,
@@ -40288,12 +40564,12 @@ async function loginCommand() {
40288
40564
  }
40289
40565
 
40290
40566
  // src/commands/logout.tsx
40291
- var import_react37 = __toESM(require_react(), 1);
40567
+ var import_react38 = __toESM(require_react(), 1);
40292
40568
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
40293
40569
  function LogoutApp({ onExit }) {
40294
40570
  const wasLoggedIn = readToken() !== null;
40295
40571
  const deleted = deleteToken();
40296
- (0, import_react37.useEffect)(() => {
40572
+ (0, import_react38.useEffect)(() => {
40297
40573
  onExit(0);
40298
40574
  }, [onExit]);
40299
40575
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, marginBottom: 1, children: [
@@ -40310,7 +40586,7 @@ function LogoutApp({ onExit }) {
40310
40586
  async function logoutCommand() {
40311
40587
  let exitCode = 0;
40312
40588
  const { waitUntilExit } = render_default(
40313
- import_react37.default.createElement(LogoutApp, {
40589
+ import_react38.default.createElement(LogoutApp, {
40314
40590
  onExit: (code) => {
40315
40591
  exitCode = code;
40316
40592
  }
@@ -40321,10 +40597,10 @@ async function logoutCommand() {
40321
40597
  }
40322
40598
 
40323
40599
  // src/commands/pr.ts
40324
- var import_react39 = __toESM(require_react(), 1);
40600
+ var import_react40 = __toESM(require_react(), 1);
40325
40601
 
40326
40602
  // src/ui/pr-app.tsx
40327
- var import_react38 = __toESM(require_react(), 1);
40603
+ var import_react39 = __toESM(require_react(), 1);
40328
40604
  var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
40329
40605
  var TERMINAL_PHASES3 = [
40330
40606
  "aborted",
@@ -40338,18 +40614,18 @@ function PrApp({
40338
40614
  onExit
40339
40615
  }) {
40340
40616
  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)(() => {
40617
+ const [phase, setPhase] = (0, import_react39.useState)("collecting");
40618
+ const [prTitle, setPrTitle] = (0, import_react39.useState)("");
40619
+ const [prBody, setPrBody] = (0, import_react39.useState)("");
40620
+ const [errorMsg, setErrorMsg] = (0, import_react39.useState)("");
40621
+ (0, import_react39.useEffect)(() => {
40346
40622
  if (!TERMINAL_PHASES3.includes(phase) && phase !== "copied" && phase !== "aborted")
40347
40623
  return;
40348
40624
  const code = phase === "copied" || phase === "aborted" ? 0 : 1;
40349
40625
  exit();
40350
40626
  onExit(code);
40351
40627
  }, [phase]);
40352
- (0, import_react38.useEffect)(() => {
40628
+ (0, import_react39.useEffect)(() => {
40353
40629
  async function run2() {
40354
40630
  const token = deps.readToken();
40355
40631
  if (!token) {
@@ -40536,7 +40812,7 @@ async function prCommand() {
40536
40812
  let exitCode = 0;
40537
40813
  enterImmersive();
40538
40814
  const { waitUntilExit } = render_default(
40539
- import_react39.default.createElement(PrApp, {
40815
+ import_react40.default.createElement(PrApp, {
40540
40816
  deps: {
40541
40817
  readToken,
40542
40818
  getDiffBetweenBranches,
@@ -40554,10 +40830,10 @@ async function prCommand() {
40554
40830
  }
40555
40831
 
40556
40832
  // src/commands/split.ts
40557
- var import_react41 = __toESM(require_react(), 1);
40833
+ var import_react42 = __toESM(require_react(), 1);
40558
40834
 
40559
40835
  // src/ui/split-app.tsx
40560
- var import_react40 = __toESM(require_react(), 1);
40836
+ var import_react41 = __toESM(require_react(), 1);
40561
40837
 
40562
40838
  // src/ui/components/diff-stats.tsx
40563
40839
  var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
@@ -40684,6 +40960,12 @@ function CommitRow(props) {
40684
40960
 
40685
40961
  // src/ui/split-app.tsx
40686
40962
  var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
40963
+ var STEP_LABELS2 = {
40964
+ understand: "Entendendo altera\xE7\xF5es",
40965
+ classify: "Classificando contextos",
40966
+ group: "Agrupando em commits",
40967
+ generate: "Gerando mensagens"
40968
+ };
40687
40969
  var TERMINAL_PHASES4 = [
40688
40970
  "done",
40689
40971
  "aborted",
@@ -40731,24 +41013,57 @@ function SplitApp({
40731
41013
  onExit
40732
41014
  }) {
40733
41015
  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)(() => {
41016
+ const committedMessagesRef = (0, import_react41.useRef)([]);
41017
+ const [phase, setPhase] = (0, import_react41.useState)("collecting");
41018
+ const [groups, setGroups] = (0, import_react41.useState)([]);
41019
+ const [selectedIndex, setSelectedIndex] = (0, import_react41.useState)(0);
41020
+ const [skippedGroups, setSkippedGroups] = (0, import_react41.useState)(/* @__PURE__ */ new Set());
41021
+ const [commitProgress, setCommitProgress] = (0, import_react41.useState)(0);
41022
+ const [errorMsg, setErrorMsg] = (0, import_react41.useState)("");
41023
+ const [originalFiles, setOriginalFiles] = (0, import_react41.useState)([]);
41024
+ const [hunksMap, setHunksMap] = (0, import_react41.useState)(/* @__PURE__ */ new Map());
41025
+ const [patchMap, setPatchMap] = (0, import_react41.useState)(/* @__PURE__ */ new Map());
41026
+ const [authToken, setAuthToken] = (0, import_react41.useState)("");
41027
+ const [streamPayload, setStreamPayload] = (0, import_react41.useState)(
41028
+ null
41029
+ );
41030
+ const streamOptions = (0, import_react41.useMemo)(
41031
+ () => streamPayload && phase === "analyzing" ? {
41032
+ path: streamPayload.path,
41033
+ body: streamPayload.body,
41034
+ token: streamPayload.token,
41035
+ postStream: deps.postStream,
41036
+ stepLabels: STEP_LABELS2,
41037
+ timeoutMs: 3e5
41038
+ } : null,
41039
+ [streamPayload, phase, deps.postStream]
41040
+ );
41041
+ const stream = usePipelineStream(streamOptions);
41042
+ (0, import_react41.useEffect)(() => {
40746
41043
  if (!TERMINAL_PHASES4.includes(phase)) return;
40747
41044
  const code = phase === "done" || phase === "aborted" ? 0 : 1;
40748
41045
  exit();
40749
41046
  onExit(code, phase === "done" ? committedMessagesRef.current : void 0);
40750
41047
  }, [phase]);
40751
- (0, import_react40.useEffect)(() => {
41048
+ (0, import_react41.useEffect)(() => {
41049
+ if (phase !== "analyzing") return;
41050
+ if (stream.result) {
41051
+ const sortedGroups = stream.result.groups.sort(
41052
+ (a, b) => a.order - b.order
41053
+ );
41054
+ setGroups(sortedGroups);
41055
+ setPhase("review");
41056
+ return;
41057
+ }
41058
+ if (stream.error) {
41059
+ const verbose2 = process.argv.includes("--verbose") || process.argv.includes("-v") || !!process.env.REPOMIND_DEBUG;
41060
+ setErrorMsg(
41061
+ verbose2 ? `Falha ao analisar altera\xE7\xF5es: ${stream.error}` : "Falha ao analisar altera\xE7\xF5es. Tente novamente."
41062
+ );
41063
+ setPhase("error-api");
41064
+ }
41065
+ }, [stream.result, stream.error, phase]);
41066
+ (0, import_react41.useEffect)(() => {
40752
41067
  async function run2() {
40753
41068
  const token = deps.readToken();
40754
41069
  if (!token) {
@@ -40813,43 +41128,12 @@ function SplitApp({
40813
41128
  setHunksMap(hMap);
40814
41129
  setPatchMap(pMap);
40815
41130
  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
- }
41131
+ const body = { manifest };
41132
+ if (deps.fileConfig && Object.keys(deps.fileConfig).length > 0) {
41133
+ body.config = deps.fileConfig;
40852
41134
  }
41135
+ setStreamPayload({ path: "/split/analyze", body, token });
41136
+ setPhase("analyzing");
40853
41137
  }
40854
41138
  run2();
40855
41139
  }, []);
@@ -40941,9 +41225,9 @@ function SplitApp({
40941
41225
  });
40942
41226
  }
40943
41227
  }
40944
- }
40945
- if (lineSelections.length > 0) {
40946
- await deps.stageLines(currentDiff, lineSelections);
41228
+ if (lineSelections.length > 0) {
41229
+ await deps.stageLines(currentDiff, lineSelections);
41230
+ }
40947
41231
  }
40948
41232
  }
40949
41233
  }
@@ -41001,10 +41285,15 @@ function SplitApp({
41001
41285
  status: phase === "collecting" ? "active" : "done",
41002
41286
  detail: phase !== "collecting" ? `${originalFiles.length} arquivos` : void 0
41003
41287
  },
41004
- {
41005
- label: "Analisando com IA",
41006
- status: phase === "analyzing" ? "active" : "pending"
41007
- }
41288
+ // Steps dinâmicos do SSE (aparecem quando pipeline anuncia step:start)
41289
+ ...stream.steps,
41290
+ // Fallback: se stream ainda não tem steps e está analisando, exibir step genérico
41291
+ ...phase === "analyzing" && stream.steps.length === 0 ? [
41292
+ {
41293
+ label: "Analisando com IA",
41294
+ status: "active"
41295
+ }
41296
+ ] : []
41008
41297
  ]
41009
41298
  }
41010
41299
  ),
@@ -41154,7 +41443,7 @@ async function splitCommand() {
41154
41443
  let completedCommits = [];
41155
41444
  enterImmersive();
41156
41445
  const { waitUntilExit } = render_default(
41157
- import_react41.default.createElement(SplitApp, {
41446
+ import_react42.default.createElement(SplitApp, {
41158
41447
  deps: {
41159
41448
  readToken,
41160
41449
  getAllChanges,
@@ -41171,6 +41460,7 @@ async function splitCommand() {
41171
41460
  validatePatch,
41172
41461
  getFileDiff,
41173
41462
  apiPost: (path, body, tkn, timeoutMs) => apiClient.post(path, body, tkn, timeoutMs),
41463
+ postStream: (path, body, tkn, timeoutMs) => apiClient.postStream(path, body, tkn, timeoutMs),
41174
41464
  runGitCommit: runGitCommit2,
41175
41465
  fileConfig,
41176
41466
  useStaged
@@ -41185,7 +41475,7 @@ async function splitCommand() {
41185
41475
  exitImmersive();
41186
41476
  if (completedCommits.length > 0) {
41187
41477
  const { waitUntilExit: waitUntilExit2 } = render_default(
41188
- import_react41.default.createElement(SplitDoneSummary, { commits: completedCommits })
41478
+ import_react42.default.createElement(SplitDoneSummary, { commits: completedCommits })
41189
41479
  );
41190
41480
  await waitUntilExit2();
41191
41481
  }
@@ -41193,12 +41483,12 @@ async function splitCommand() {
41193
41483
  }
41194
41484
 
41195
41485
  // src/commands/whoami.tsx
41196
- var import_react42 = __toESM(require_react(), 1);
41486
+ var import_react43 = __toESM(require_react(), 1);
41197
41487
  var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
41198
41488
  function WhoamiApp({ onExit }) {
41199
41489
  const token = readToken();
41200
41490
  const payload = token ? decodeTokenPayload(token) : null;
41201
- (0, import_react42.useEffect)(() => {
41491
+ (0, import_react43.useEffect)(() => {
41202
41492
  onExit(payload ? 0 : 1);
41203
41493
  }, [onExit, payload]);
41204
41494
  const expiresDate = payload ? new Date(payload.exp * 1e3).toLocaleDateString("pt-BR", {
@@ -41234,7 +41524,7 @@ function WhoamiApp({ onExit }) {
41234
41524
  async function whoamiCommand() {
41235
41525
  let exitCode = 0;
41236
41526
  const { waitUntilExit } = render_default(
41237
- import_react42.default.createElement(WhoamiApp, {
41527
+ import_react43.default.createElement(WhoamiApp, {
41238
41528
  onExit: (code) => {
41239
41529
  exitCode = code;
41240
41530
  }
@@ -41246,7 +41536,7 @@ async function whoamiCommand() {
41246
41536
 
41247
41537
  // src/index.ts
41248
41538
  import { createRequire } from "node:module";
41249
- var import_react43 = __toESM(require_react(), 1);
41539
+ var import_react44 = __toESM(require_react(), 1);
41250
41540
 
41251
41541
  // src/ui/components/command-help.tsx
41252
41542
  var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
@@ -41378,7 +41668,7 @@ var COMMAND_HELP = {
41378
41668
  if (wantsHelp && command && COMMAND_HELP[command]) {
41379
41669
  const def = COMMAND_HELP[command];
41380
41670
  const { waitUntilExit } = render_default(
41381
- import_react43.default.createElement(CommandHelp, {
41671
+ import_react44.default.createElement(CommandHelp, {
41382
41672
  command,
41383
41673
  usage: def.usage,
41384
41674
  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.1",
4
4
  "type": "module",
5
5
  "description": "AI-powered git commit messages and repository insights",
6
6
  "keywords": ["git", "ai", "commit", "cli"],