@supatest/cli 0.0.40 → 0.0.41

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 +2375 -496
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -233,7 +233,7 @@ You are the Supatest CLI Help Assistant. You help users understand and use the S
233
233
  - MCP (Model Context Protocol) server management
234
234
  - npm package information and npm link setup
235
235
 
236
- You provide clear, friendly, and accurate answers based on the comprehensive CLI documentation below. You can maintain conversation context across multiple messages, answer follow-up questions, and guide users toward the right features for their needs.
236
+ You provide clear, friendly, and accurate answers based on the comprehensive CLI documentation below. You can maintain session context across multiple messages, answer follow-up questions, and guide users toward the right features for their needs.
237
237
  </role>
238
238
 
239
239
  <capabilities>
@@ -358,7 +358,7 @@ These work in interactive mode:
358
358
  - **ESC or Q** - Close overlays (help, menus, dialogs)
359
359
 
360
360
  ### Navigation
361
- - **Ctrl+M** - Cycle through available models
361
+ - **Ctrl+Shift+M** - Cycle through available models
362
362
  - **Ctrl+P** - Cycle through LLM providers
363
363
 
364
364
  ### Input & Text Editing
@@ -585,7 +585,7 @@ Just ask: "Can you check the npm docs?" or "What's the latest version?"
585
585
  - The agent will handle framework-specific syntax
586
586
 
587
587
  ### Session Persistence
588
- - Your conversations are saved and can be resumed
588
+ - Your sessions are saved and can be resumed
589
589
  - Use /resume to continue work from where you left off
590
590
  - Useful for long-running projects
591
591
 
@@ -596,7 +596,7 @@ Just ask: "Can you check the npm docs?" or "What's the latest version?"
596
596
 
597
597
  </cli-documentation>
598
598
 
599
- <conversation-guidelines>
599
+ <session-guidelines>
600
600
  When answering questions:
601
601
 
602
602
  1. **Be direct**: Answer the question asked first, then offer related information
@@ -614,7 +614,7 @@ When users mix help with actual test building:
614
614
  - Answer their help question
615
615
  - Then ask if they want to start building tests or need more information
616
616
 
617
- </conversation-guidelines>
617
+ </session-guidelines>
618
618
 
619
619
  <action-guide>
620
620
  ## How to Help Users with Actions
@@ -1155,7 +1155,7 @@ function getToolGroupCounts(tools) {
1155
1155
  );
1156
1156
  return Object.entries(groups).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
1157
1157
  }
1158
- var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema;
1158
+ var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema, runSummaryEmailFailureSchema, runSummaryEmailReportSchema, sendRunReportRequestSchema, metricWithTrendSchema, weekOverWeekMetricsSchema, ciComputeTimeSchema, investigationCandidateSchema, failureCategoryBreakdownSchema, stabilityTrendSchema, folderStabilitySchema, managerReportSchema, managerReportQuerySchema, sendManagerReportRequestSchema, reportAttachmentLinkSchema, developerReportRegressionSchema, developerReportFlakyTestSchema, developerReportSlowTestSchema, developerRunSummaryReportSchema, executiveReportStatusSchema, executiveMetricSchema, executiveKeyMetricsSchema, executiveTrendPointSchema, executiveTrendsSchema, executiveFlakyOffenderSchema, executiveSlowestOffenderSchema, executiveTopOffendersSchema, executiveReportSchema, executiveReportQuerySchema, sendExecutiveReportRequestSchema, fixAssignmentStatusSchema, fixAssignmentSchema, testWithAssignmentSchema, listAssignmentsQuerySchema, listTestsWithAssignmentsQuerySchema, assignmentsListResponseSchema, assigneeInfoSchema, assigneesListResponseSchema, assignTestsRequestSchema, assignTestRequestSchema, reassignTestRequestSchema, completeAssignmentsRequestSchema, completeAssignmentRequestSchema, bulkReassignRequestSchema, assignmentResponseSchema, assignTestsResponseSchema, testsWithAssignmentsResponseSchema, bulkReassignResponseSchema, userAssignmentStatsSchema, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR;
1159
1159
  var init_shared_es = __esm({
1160
1160
  "../shared/dist/shared.es.mjs"() {
1161
1161
  "use strict";
@@ -5103,7 +5103,7 @@ var init_shared_es = __esm({
5103
5103
  originMetadata: recordType(unknownType()).nullable().optional(),
5104
5104
  authMethod: enumType(["clerk", "cli-token", "api-key"]).default("clerk"),
5105
5105
  authId: stringType().nullable().optional(),
5106
- // Claude Agent SDK session ID for resuming conversations
5106
+ // Claude Agent SDK session ID for resuming sessions
5107
5107
  // Sessions expire after ~30 days due to Anthropic's data retention policy
5108
5108
  providerSessionId: stringType().nullable().optional(),
5109
5109
  createdAt: dateType(),
@@ -5120,7 +5120,7 @@ var init_shared_es = __esm({
5120
5120
  });
5121
5121
  messageSchema = objectType({
5122
5122
  id: stringType().uuid(),
5123
- conversationId: stringType().uuid(),
5123
+ sessionId: stringType().uuid(),
5124
5124
  role: enumType(["user", "assistant"]),
5125
5125
  content: arrayType(contentBlockSchema),
5126
5126
  createdAt: dateType()
@@ -5414,6 +5414,7 @@ var init_shared_es = __esm({
5414
5414
  limit: coerce.number().min(1).max(100).default(20),
5415
5415
  status: runStatusSchema.optional(),
5416
5416
  branch: stringType().optional(),
5417
+ search: stringType().optional(),
5417
5418
  startDate: stringType().optional(),
5418
5419
  endDate: stringType().optional()
5419
5420
  });
@@ -5503,6 +5504,7 @@ var init_shared_es = __esm({
5503
5504
  "assertion",
5504
5505
  "element_not_found",
5505
5506
  "server_error",
5507
+ "navigation",
5506
5508
  "other"
5507
5509
  ]);
5508
5510
  failureClusterSchema = objectType({
@@ -5524,14 +5526,7 @@ var init_shared_es = __esm({
5524
5526
  newFailures: arrayType(newFailureSchema),
5525
5527
  clusters: arrayType(failureClusterSchema)
5526
5528
  });
5527
- FailureCategoryEnum = enumType([
5528
- "timeout",
5529
- "element_not_found",
5530
- "assertion",
5531
- "network",
5532
- "navigation",
5533
- "other"
5534
- ]);
5529
+ FailureCategoryEnum = errorCategorySchema;
5535
5530
  SelectorTypeEnum = enumType([
5536
5531
  "testid",
5537
5532
  "class",
@@ -5629,12 +5624,1021 @@ var init_shared_es = __esm({
5629
5624
  days: numberType()
5630
5625
  })
5631
5626
  });
5627
+ runSummaryEmailFailureSchema = objectType({
5628
+ testRunId: stringType(),
5629
+ testId: stringType(),
5630
+ title: stringType(),
5631
+ file: stringType(),
5632
+ errorMessage: stringType().nullable(),
5633
+ errorStack: stringType().nullable()
5634
+ });
5635
+ runSummaryEmailReportSchema = objectType({
5636
+ runId: stringType(),
5637
+ readableId: stringType().optional(),
5638
+ runDetailsUrl: stringType(),
5639
+ startedAt: stringType(),
5640
+ endedAt: stringType().optional(),
5641
+ durationMs: numberType(),
5642
+ branch: stringType().optional(),
5643
+ commit: stringType().optional(),
5644
+ commitMessage: stringType().optional(),
5645
+ totalTests: numberType(),
5646
+ passedTests: numberType(),
5647
+ failedTests: numberType(),
5648
+ flakyTests: numberType(),
5649
+ skippedTests: numberType(),
5650
+ passRate: numberType(),
5651
+ topFailures: arrayType(runSummaryEmailFailureSchema)
5652
+ });
5653
+ sendRunReportRequestSchema = objectType({
5654
+ runId: stringType(),
5655
+ emails: arrayType(stringType().email())
5656
+ });
5657
+ metricWithTrendSchema = objectType({
5658
+ current: numberType(),
5659
+ previous: numberType(),
5660
+ change: numberType(),
5661
+ percentChange: numberType().nullable()
5662
+ });
5663
+ weekOverWeekMetricsSchema = objectType({
5664
+ passRate: metricWithTrendSchema,
5665
+ flakyTestCount: metricWithTrendSchema,
5666
+ newFailures: metricWithTrendSchema,
5667
+ totalRuns: metricWithTrendSchema
5668
+ });
5669
+ ciComputeTimeSchema = objectType({
5670
+ failedRunsMs: numberType(),
5671
+ retriedRunsMs: numberType(),
5672
+ totalWastedMs: numberType(),
5673
+ failedRunsHours: numberType(),
5674
+ retriedRunsHours: numberType(),
5675
+ totalWastedHours: numberType()
5676
+ });
5677
+ investigationCandidateSchema = objectType({
5678
+ testId: stringType(),
5679
+ testRunId: stringType(),
5680
+ runId: stringType(),
5681
+ file: stringType(),
5682
+ title: stringType(),
5683
+ flakeCount: numberType(),
5684
+ passRate: numberType(),
5685
+ avgDurationMs: numberType(),
5686
+ ciTimeImpactMs: numberType(),
5687
+ ciTimeImpactHours: numberType(),
5688
+ category: FailureCategoryEnum.nullable(),
5689
+ firstFlakyAt: stringType().nullable()
5690
+ });
5691
+ failureCategoryBreakdownSchema = objectType({
5692
+ category: FailureCategoryEnum,
5693
+ count: numberType(),
5694
+ percentage: numberType()
5695
+ });
5696
+ stabilityTrendSchema = enumType([
5697
+ "improving",
5698
+ "degrading",
5699
+ "stable"
5700
+ ]);
5701
+ folderStabilitySchema = objectType({
5702
+ folder: stringType(),
5703
+ passRate: numberType(),
5704
+ previousPassRate: numberType(),
5705
+ trend: stabilityTrendSchema,
5706
+ testCount: numberType(),
5707
+ failureCount: numberType()
5708
+ });
5709
+ managerReportSchema = objectType({
5710
+ period: objectType({
5711
+ start: stringType(),
5712
+ end: stringType(),
5713
+ days: numberType()
5714
+ }),
5715
+ weekOverWeek: weekOverWeekMetricsSchema,
5716
+ ciComputeTime: ciComputeTimeSchema,
5717
+ investigationCandidates: arrayType(investigationCandidateSchema),
5718
+ failureCategories: arrayType(failureCategoryBreakdownSchema),
5719
+ folderStability: arrayType(folderStabilitySchema)
5720
+ });
5721
+ managerReportQuerySchema = objectType({
5722
+ startDate: stringType().regex(
5723
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/,
5724
+ "startDate must be in format YYYY-MM-DD or ISO datetime"
5725
+ ),
5726
+ endDate: stringType().regex(
5727
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/,
5728
+ "endDate must be in format YYYY-MM-DD or ISO datetime"
5729
+ )
5730
+ });
5731
+ sendManagerReportRequestSchema = objectType({
5732
+ startDate: stringType(),
5733
+ endDate: stringType(),
5734
+ emails: arrayType(stringType().email())
5735
+ });
5736
+ reportAttachmentLinkSchema = objectType({
5737
+ name: stringType(),
5738
+ kind: attachmentKindSchema,
5739
+ url: stringType().optional()
5740
+ });
5741
+ developerReportRegressionSchema = objectType({
5742
+ testRunId: stringType(),
5743
+ testId: stringType(),
5744
+ title: stringType(),
5745
+ file: stringType(),
5746
+ errorMessage: stringType().nullable(),
5747
+ errorStack: stringType().nullable(),
5748
+ aiSummary: stringType().nullable(),
5749
+ attachments: arrayType(reportAttachmentLinkSchema),
5750
+ lastStableRun: objectType({
5751
+ runId: stringType(),
5752
+ date: stringType(),
5753
+ branch: stringType().optional()
5754
+ }).nullable()
5755
+ });
5756
+ developerReportFlakyTestSchema = objectType({
5757
+ testRunId: stringType(),
5758
+ testId: stringType(),
5759
+ title: stringType(),
5760
+ file: stringType(),
5761
+ passRate: numberType(),
5762
+ category: errorCategorySchema.nullable(),
5763
+ lastPassedRun: objectType({
5764
+ runId: stringType(),
5765
+ date: stringType()
5766
+ }).nullable()
5767
+ });
5768
+ developerReportSlowTestSchema = objectType({
5769
+ testRunId: stringType(),
5770
+ testId: stringType(),
5771
+ title: stringType(),
5772
+ file: stringType(),
5773
+ durationMs: numberType(),
5774
+ historicalAvgMs: numberType(),
5775
+ slowdownFactor: numberType()
5776
+ // how many times slower than average
5777
+ });
5778
+ developerRunSummaryReportSchema = objectType({
5779
+ runId: stringType(),
5780
+ readableId: stringType().optional(),
5781
+ runDetailsUrl: stringType(),
5782
+ startedAt: stringType(),
5783
+ endedAt: stringType().optional(),
5784
+ durationMs: numberType(),
5785
+ branch: stringType().optional(),
5786
+ commit: stringType().optional(),
5787
+ commitMessage: stringType().optional(),
5788
+ // Summary counts
5789
+ totalTests: numberType(),
5790
+ passedTests: numberType(),
5791
+ failedTests: numberType(),
5792
+ flakyTests: numberType(),
5793
+ skippedTests: numberType(),
5794
+ passRate: numberType(),
5795
+ // Sections
5796
+ newRegressions: arrayType(developerReportRegressionSchema),
5797
+ knownFlaky: arrayType(developerReportFlakyTestSchema),
5798
+ slowTests: arrayType(developerReportSlowTestSchema)
5799
+ });
5800
+ executiveReportStatusSchema = enumType([
5801
+ "healthy",
5802
+ "needs_attention",
5803
+ "critical"
5804
+ ]);
5805
+ executiveMetricSchema = objectType({
5806
+ /** Current month's value */
5807
+ value: numberType(),
5808
+ /** Previous month's value */
5809
+ previousValue: numberType(),
5810
+ /** Percentage change from previous month (null if previous was 0) */
5811
+ change: numberType().nullable()
5812
+ });
5813
+ executiveKeyMetricsSchema = objectType({
5814
+ /** CI First-Try Pass Rate: COUNT(runs WHERE failed=0 AND flaky=0) / COUNT(runs) */
5815
+ ciFirstTryPassRate: executiveMetricSchema,
5816
+ /** Total Test Pass Rate from health analytics */
5817
+ testPassRate: executiveMetricSchema,
5818
+ /** Count of distinct tests with flaky outcomes */
5819
+ flakyTestCount: executiveMetricSchema,
5820
+ /** CI compute time lost on failed/retried runs in hours */
5821
+ ciComputeLostHours: executiveMetricSchema
5822
+ });
5823
+ executiveTrendPointSchema = objectType({
5824
+ date: stringType(),
5825
+ value: numberType()
5826
+ });
5827
+ executiveTrendsSchema = objectType({
5828
+ /** Daily pass rate values for sparkline */
5829
+ dailyPassRate: arrayType(executiveTrendPointSchema),
5830
+ /** Daily flaky count values for sparkline */
5831
+ dailyFlakyCount: arrayType(executiveTrendPointSchema)
5832
+ });
5833
+ executiveFlakyOffenderSchema = objectType({
5834
+ testId: stringType(),
5835
+ title: stringType(),
5836
+ file: stringType(),
5837
+ /** Number of flaky occurrences */
5838
+ flakyCount: numberType(),
5839
+ /** CI time impact in hours */
5840
+ ciTimeImpactHours: numberType()
5841
+ });
5842
+ executiveSlowestOffenderSchema = objectType({
5843
+ testId: stringType(),
5844
+ title: stringType(),
5845
+ file: stringType(),
5846
+ /** Average duration in milliseconds */
5847
+ avgDurationMs: numberType(),
5848
+ /** Trend percentage change from previous period (null if no previous data) */
5849
+ trend: numberType().nullable()
5850
+ });
5851
+ executiveTopOffendersSchema = objectType({
5852
+ /** Top 3 most flaky tests with CI time impact */
5853
+ mostFlaky: arrayType(executiveFlakyOffenderSchema),
5854
+ /** Top 3 slowest tests with trend */
5855
+ slowest: arrayType(executiveSlowestOffenderSchema)
5856
+ });
5857
+ executiveReportSchema = objectType({
5858
+ /** Month in format YYYY-MM */
5859
+ month: stringType(),
5860
+ /** When the report was generated */
5861
+ generatedAt: stringType(),
5862
+ /** URL to view full report in dashboard */
5863
+ reportUrl: stringType(),
5864
+ /** Overall status: healthy, needs_attention, or critical */
5865
+ status: executiveReportStatusSchema,
5866
+ /** 4 key metrics with month-over-month comparison */
5867
+ keyMetrics: executiveKeyMetricsSchema,
5868
+ /** 30-day trend data for sparklines */
5869
+ trends: executiveTrendsSchema,
5870
+ /** Top offenders (most flaky and slowest tests) */
5871
+ topOffenders: executiveTopOffendersSchema,
5872
+ /** Auto-generated signal messages based on thresholds */
5873
+ signals: arrayType(stringType())
5874
+ });
5875
+ executiveReportQuerySchema = objectType({
5876
+ month: stringType().regex(/^\d{4}-\d{2}$/, "month must be in format YYYY-MM")
5877
+ });
5878
+ sendExecutiveReportRequestSchema = objectType({
5879
+ month: stringType().regex(/^\d{4}-\d{2}$/, "month must be in format YYYY-MM"),
5880
+ emails: arrayType(stringType().email())
5881
+ });
5882
+ fixAssignmentStatusSchema = enumType([
5883
+ "assigned",
5884
+ // Currently being worked on
5885
+ "completed",
5886
+ // Successfully fixed
5887
+ "failed"
5888
+ // Fix attempt failed
5889
+ ]);
5890
+ fixAssignmentSchema = objectType({
5891
+ id: stringType(),
5892
+ testRunId: stringType(),
5893
+ // Reference to test_run table
5894
+ assignedTo: stringType().optional(),
5895
+ // User ID (Clerk user ID)
5896
+ assignedAt: stringType().optional(),
5897
+ // ISO timestamp
5898
+ status: fixAssignmentStatusSchema.optional(),
5899
+ completedAt: stringType().optional(),
5900
+ reason: stringType().optional(),
5901
+ createdAt: stringType(),
5902
+ updatedAt: stringType()
5903
+ });
5904
+ testWithAssignmentSchema = objectType({
5905
+ // Core test fields from Test schema
5906
+ id: stringType(),
5907
+ readableId: stringType().optional(),
5908
+ runId: stringType(),
5909
+ file: stringType(),
5910
+ title: stringType(),
5911
+ location: objectType({
5912
+ file: stringType(),
5913
+ line: numberType(),
5914
+ column: numberType().optional()
5915
+ }).optional(),
5916
+ status: enumType(["passed", "failed", "timedOut", "skipped", "interrupted"]),
5917
+ durationMs: numberType(),
5918
+ retryCount: numberType(),
5919
+ isFlaky: booleanType().optional(),
5920
+ // Assignment fields
5921
+ assignmentId: stringType().optional(),
5922
+ assignedTo: stringType().optional(),
5923
+ assignedAt: stringType().optional(),
5924
+ assignmentStatus: fixAssignmentStatusSchema.optional(),
5925
+ assignmentCompletedAt: stringType().optional(),
5926
+ assignmentReason: stringType().optional()
5927
+ });
5928
+ listAssignmentsQuerySchema = objectType({
5929
+ runId: stringType().optional(),
5930
+ assignedTo: stringType().optional(),
5931
+ status: fixAssignmentStatusSchema.optional(),
5932
+ file: stringType().optional(),
5933
+ search: stringType().optional(),
5934
+ page: coerce.number().min(1).default(1),
5935
+ limit: coerce.number().min(1).max(100).default(50)
5936
+ });
5937
+ listTestsWithAssignmentsQuerySchema = objectType({
5938
+ assignedTo: stringType().optional(),
5939
+ status: fixAssignmentStatusSchema.optional(),
5940
+ testStatus: enumType(["passed", "failed", "timedOut", "skipped", "interrupted"]).optional(),
5941
+ file: stringType().optional(),
5942
+ search: stringType().optional(),
5943
+ page: coerce.number().min(1).default(1),
5944
+ limit: coerce.number().min(1).max(100).default(50)
5945
+ });
5946
+ assignmentsListResponseSchema = objectType({
5947
+ assignments: arrayType(testWithAssignmentSchema),
5948
+ total: numberType(),
5949
+ page: numberType(),
5950
+ limit: numberType(),
5951
+ // Summary stats
5952
+ stats: objectType({
5953
+ total: numberType(),
5954
+ assigned: numberType(),
5955
+ completed: numberType(),
5956
+ failed: numberType(),
5957
+ free: numberType()
5958
+ })
5959
+ });
5960
+ assigneeInfoSchema = objectType({
5961
+ id: stringType(),
5962
+ name: stringType(),
5963
+ email: stringType().optional(),
5964
+ activeTests: numberType().default(0)
5965
+ // Number of tests currently assigned
5966
+ });
5967
+ assigneesListResponseSchema = objectType({
5968
+ assignees: arrayType(assigneeInfoSchema)
5969
+ });
5970
+ assignTestsRequestSchema = objectType({
5971
+ testRunIds: arrayType(stringType()).min(1),
5972
+ assignedTo: stringType().min(1),
5973
+ notes: stringType().optional()
5974
+ });
5975
+ assignTestRequestSchema = objectType({
5976
+ testRunId: stringType(),
5977
+ assignedTo: stringType(),
5978
+ reason: stringType().optional()
5979
+ });
5980
+ reassignTestRequestSchema = objectType({
5981
+ assignedTo: stringType().min(1),
5982
+ notes: stringType().optional()
5983
+ });
5984
+ completeAssignmentsRequestSchema = objectType({
5985
+ assignmentIds: arrayType(stringType()).min(1),
5986
+ status: fixAssignmentStatusSchema,
5987
+ notes: stringType().optional(),
5988
+ fixSessionId: stringType().optional()
5989
+ });
5990
+ completeAssignmentRequestSchema = objectType({
5991
+ assignmentId: stringType(),
5992
+ success: booleanType()
5993
+ });
5994
+ bulkReassignRequestSchema = objectType({
5995
+ fromUser: stringType().min(1),
5996
+ toUser: stringType().min(1),
5997
+ status: fixAssignmentStatusSchema.optional(),
5998
+ notes: stringType().optional()
5999
+ });
6000
+ assignmentResponseSchema = objectType({
6001
+ assignment: fixAssignmentSchema
6002
+ });
6003
+ assignTestsResponseSchema = objectType({
6004
+ assigned: arrayType(
6005
+ objectType({
6006
+ id: stringType(),
6007
+ testRunId: stringType()
6008
+ })
6009
+ ),
6010
+ conflicts: arrayType(
6011
+ objectType({
6012
+ testRunId: stringType(),
6013
+ file: stringType(),
6014
+ title: stringType(),
6015
+ currentAssignee: stringType()
6016
+ })
6017
+ )
6018
+ });
6019
+ testsWithAssignmentsResponseSchema = objectType({
6020
+ tests: arrayType(testWithAssignmentSchema),
6021
+ total: numberType(),
6022
+ page: numberType(),
6023
+ limit: numberType(),
6024
+ stats: objectType({
6025
+ total: numberType(),
6026
+ assigned: numberType(),
6027
+ completed: numberType(),
6028
+ failed: numberType(),
6029
+ free: numberType()
6030
+ })
6031
+ });
6032
+ bulkReassignResponseSchema = objectType({
6033
+ reassigned: numberType(),
6034
+ conflicts: arrayType(
6035
+ objectType({
6036
+ testRunId: stringType(),
6037
+ file: stringType(),
6038
+ title: stringType(),
6039
+ reason: stringType()
6040
+ })
6041
+ )
6042
+ });
6043
+ userAssignmentStatsSchema = objectType({
6044
+ assigned: numberType(),
6045
+ completed: numberType(),
6046
+ failed: numberType()
6047
+ });
6048
+ SECONDS_PER_MINUTE = 60;
6049
+ SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
6050
+ SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
6051
+ SECONDS_PER_WEEK = 7 * SECONDS_PER_DAY;
6052
+ SECONDS_PER_MONTH = 30 * SECONDS_PER_DAY;
6053
+ SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY;
6054
+ }
6055
+ });
6056
+
6057
+ // src/utils/claude-oauth.ts
6058
+ var claude_oauth_exports = {};
6059
+ __export(claude_oauth_exports, {
6060
+ ClaudeOAuthService: () => ClaudeOAuthService
6061
+ });
6062
+ import { spawn } from "child_process";
6063
+ import { createHash, randomBytes } from "crypto";
6064
+ import http from "http";
6065
+ import { platform } from "os";
6066
+ var OAUTH_CONFIG, CALLBACK_PORT, CALLBACK_TIMEOUT_MS, ClaudeOAuthService;
6067
+ var init_claude_oauth = __esm({
6068
+ "src/utils/claude-oauth.ts"() {
6069
+ "use strict";
6070
+ OAUTH_CONFIG = {
6071
+ clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
6072
+ // Claude Code's client ID
6073
+ authorizationEndpoint: "https://claude.ai/oauth/authorize",
6074
+ tokenEndpoint: "https://console.anthropic.com/v1/oauth/token",
6075
+ redirectUri: "http://localhost:8421/callback",
6076
+ // Local callback for CLI
6077
+ scopes: ["user:inference", "user:profile", "org:create_api_key"]
6078
+ };
6079
+ CALLBACK_PORT = 8421;
6080
+ CALLBACK_TIMEOUT_MS = 3e5;
6081
+ ClaudeOAuthService = class _ClaudeOAuthService {
6082
+ secretStorage;
6083
+ static TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
6084
+ // 5 minutes
6085
+ pendingCodeVerifier = null;
6086
+ // Store code verifier for PKCE
6087
+ constructor(secretStorage) {
6088
+ this.secretStorage = secretStorage;
6089
+ }
6090
+ /**
6091
+ * Starts the OAuth authorization flow
6092
+ * Opens the default browser for user authentication
6093
+ * Returns after successful authentication
6094
+ */
6095
+ async authorize() {
6096
+ try {
6097
+ const state = this.generateRandomState();
6098
+ const pkce = this.generatePKCEChallenge();
6099
+ this.pendingCodeVerifier = pkce.codeVerifier;
6100
+ const authUrl = this.buildAuthorizationUrl(state, pkce.codeChallenge);
6101
+ console.log("\nAuthenticating with Claude...\n");
6102
+ console.log(`Opening browser to: ${authUrl}
6103
+ `);
6104
+ const tokenPromise = this.startCallbackServer(CALLBACK_PORT, state);
6105
+ try {
6106
+ this.openBrowser(authUrl);
6107
+ } catch (error) {
6108
+ console.warn("Failed to open browser automatically:", error);
6109
+ console.log(`
6110
+ Please manually open this URL in your browser:
6111
+ ${authUrl}
6112
+ `);
6113
+ }
6114
+ await tokenPromise;
6115
+ console.log("\n\u2705 Successfully authenticated with Claude!\n");
6116
+ return { success: true };
6117
+ } catch (error) {
6118
+ this.pendingCodeVerifier = null;
6119
+ return {
6120
+ success: false,
6121
+ error: error instanceof Error ? error.message : "Authentication failed"
6122
+ };
6123
+ }
6124
+ }
6125
+ /**
6126
+ * Start local HTTP server to receive OAuth callback
6127
+ */
6128
+ startCallbackServer(port, expectedState) {
6129
+ return new Promise((resolve2, reject) => {
6130
+ const server = http.createServer(async (req, res) => {
6131
+ if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
6132
+ res.writeHead(404);
6133
+ res.end("Not Found");
6134
+ return;
6135
+ }
6136
+ const url = new URL(req.url, `http://localhost:${port}`);
6137
+ const code = url.searchParams.get("code");
6138
+ const returnedState = url.searchParams.get("state");
6139
+ const error = url.searchParams.get("error");
6140
+ if (error) {
6141
+ res.writeHead(200, { "Content-Type": "text/html" });
6142
+ res.end(this.buildErrorPage(error));
6143
+ server.close();
6144
+ reject(new Error(`OAuth error: ${error}`));
6145
+ return;
6146
+ }
6147
+ if (returnedState !== expectedState) {
6148
+ const errorMsg = "Security error: state parameter mismatch";
6149
+ res.writeHead(200, { "Content-Type": "text/html" });
6150
+ res.end(this.buildErrorPage(errorMsg));
6151
+ server.close();
6152
+ reject(new Error(errorMsg));
6153
+ return;
6154
+ }
6155
+ if (!code) {
6156
+ const errorMsg = "No authorization code received";
6157
+ res.writeHead(400, { "Content-Type": "text/html" });
6158
+ res.end(this.buildErrorPage(errorMsg));
6159
+ server.close();
6160
+ reject(new Error(errorMsg));
6161
+ return;
6162
+ }
6163
+ try {
6164
+ await this.submitAuthCode(code, returnedState);
6165
+ res.writeHead(200, { "Content-Type": "text/html" });
6166
+ res.end(this.buildSuccessPage());
6167
+ server.close();
6168
+ resolve2();
6169
+ } catch (err) {
6170
+ const errorMsg = err instanceof Error ? err.message : "Token exchange failed";
6171
+ res.writeHead(200, { "Content-Type": "text/html" });
6172
+ res.end(this.buildErrorPage(errorMsg));
6173
+ server.close();
6174
+ reject(err);
6175
+ }
6176
+ });
6177
+ server.on("error", (error) => {
6178
+ if (error.code === "EADDRINUSE") {
6179
+ reject(new Error("Port already in use. Please try again."));
6180
+ } else {
6181
+ reject(error);
6182
+ }
6183
+ });
6184
+ const timeout = setTimeout(() => {
6185
+ server.close();
6186
+ reject(new Error("Authentication timeout - no response received after 5 minutes"));
6187
+ }, CALLBACK_TIMEOUT_MS);
6188
+ server.on("close", () => {
6189
+ clearTimeout(timeout);
6190
+ });
6191
+ server.listen(port, "127.0.0.1", () => {
6192
+ console.log(`Waiting for authentication callback on http://localhost:${port}/callback`);
6193
+ });
6194
+ });
6195
+ }
6196
+ /**
6197
+ * Submits the authorization code and exchanges it for tokens
6198
+ */
6199
+ async submitAuthCode(code, state) {
6200
+ const tokens = await this.exchangeCodeForTokens(code, state);
6201
+ await this.saveTokens(tokens);
6202
+ return tokens;
6203
+ }
6204
+ /**
6205
+ * Exchanges authorization code for access and refresh tokens
6206
+ */
6207
+ async exchangeCodeForTokens(code, state) {
6208
+ if (!this.pendingCodeVerifier) {
6209
+ throw new Error("No PKCE code verifier found. Please start the auth flow first.");
6210
+ }
6211
+ const body = {
6212
+ grant_type: "authorization_code",
6213
+ code,
6214
+ state,
6215
+ // Non-standard: state in body
6216
+ redirect_uri: OAUTH_CONFIG.redirectUri,
6217
+ client_id: OAUTH_CONFIG.clientId,
6218
+ code_verifier: this.pendingCodeVerifier
6219
+ // PKCE verifier
6220
+ };
6221
+ const response = await fetch(OAUTH_CONFIG.tokenEndpoint, {
6222
+ method: "POST",
6223
+ headers: {
6224
+ "Content-Type": "application/json"
6225
+ // Non-standard: JSON instead of form-encoded
6226
+ },
6227
+ body: JSON.stringify(body)
6228
+ });
6229
+ this.pendingCodeVerifier = null;
6230
+ if (!response.ok) {
6231
+ const error = await response.text();
6232
+ throw new Error(`Token exchange failed: ${error}`);
6233
+ }
6234
+ const data = await response.json();
6235
+ return {
6236
+ accessToken: data.access_token,
6237
+ refreshToken: data.refresh_token,
6238
+ expiresAt: Date.now() + data.expires_in * 1e3
6239
+ };
6240
+ }
6241
+ /**
6242
+ * Refreshes the access token using the refresh token
6243
+ */
6244
+ async refreshTokens() {
6245
+ const tokens = await this.getTokens();
6246
+ if (!tokens) {
6247
+ throw new Error("No tokens found to refresh");
6248
+ }
6249
+ const body = {
6250
+ grant_type: "refresh_token",
6251
+ refresh_token: tokens.refreshToken,
6252
+ client_id: OAUTH_CONFIG.clientId
6253
+ };
6254
+ const response = await fetch(OAUTH_CONFIG.tokenEndpoint, {
6255
+ method: "POST",
6256
+ headers: {
6257
+ "Content-Type": "application/json"
6258
+ },
6259
+ body: JSON.stringify(body)
6260
+ });
6261
+ if (!response.ok) {
6262
+ const error = await response.text();
6263
+ throw new Error(`Token refresh failed: ${error}`);
6264
+ }
6265
+ const data = await response.json();
6266
+ const newTokens = {
6267
+ accessToken: data.access_token,
6268
+ refreshToken: data.refresh_token,
6269
+ expiresAt: Date.now() + data.expires_in * 1e3
6270
+ };
6271
+ await this.saveTokens(newTokens);
6272
+ return newTokens;
6273
+ }
6274
+ /**
6275
+ * Gets the current access token, refreshing if necessary
6276
+ */
6277
+ async getAccessToken() {
6278
+ const tokens = await this.getTokens();
6279
+ if (!tokens) {
6280
+ return null;
6281
+ }
6282
+ if (Date.now() > tokens.expiresAt - _ClaudeOAuthService.TOKEN_REFRESH_BUFFER_MS) {
6283
+ try {
6284
+ const refreshedTokens = await this.refreshTokens();
6285
+ return refreshedTokens.accessToken;
6286
+ } catch (error) {
6287
+ console.warn("Token refresh failed:", error);
6288
+ return null;
6289
+ }
6290
+ }
6291
+ return tokens.accessToken;
6292
+ }
6293
+ /**
6294
+ * Gets the stored tokens
6295
+ */
6296
+ async getTokens() {
6297
+ try {
6298
+ const accessToken = await this.secretStorage.getSecret("claude_oauth_access_token");
6299
+ const refreshToken = await this.secretStorage.getSecret("claude_oauth_refresh_token");
6300
+ const expiresAt = await this.secretStorage.getSecret("claude_oauth_expires_at");
6301
+ if (!accessToken || !refreshToken || !expiresAt) {
6302
+ return null;
6303
+ }
6304
+ return {
6305
+ accessToken,
6306
+ refreshToken,
6307
+ expiresAt: parseInt(expiresAt, 10)
6308
+ };
6309
+ } catch (error) {
6310
+ console.error("Failed to get OAuth tokens:", error);
6311
+ return null;
6312
+ }
6313
+ }
6314
+ /**
6315
+ * Saves OAuth tokens to secure storage
6316
+ */
6317
+ async saveTokens(tokens) {
6318
+ await this.secretStorage.setSecret("claude_oauth_access_token", tokens.accessToken);
6319
+ await this.secretStorage.setSecret("claude_oauth_refresh_token", tokens.refreshToken);
6320
+ await this.secretStorage.setSecret("claude_oauth_expires_at", tokens.expiresAt.toString());
6321
+ }
6322
+ /**
6323
+ * Deletes stored OAuth tokens
6324
+ */
6325
+ async deleteTokens() {
6326
+ await this.secretStorage.deleteSecret("claude_oauth_access_token");
6327
+ await this.secretStorage.deleteSecret("claude_oauth_refresh_token");
6328
+ await this.secretStorage.deleteSecret("claude_oauth_expires_at");
6329
+ }
6330
+ /**
6331
+ * Checks if user is authenticated via OAuth
6332
+ */
6333
+ async isAuthenticated() {
6334
+ const tokens = await this.getTokens();
6335
+ return tokens !== null;
6336
+ }
6337
+ /**
6338
+ * Gets the current OAuth authentication status
6339
+ */
6340
+ async getStatus() {
6341
+ try {
6342
+ const tokens = await this.getTokens();
6343
+ if (!tokens) {
6344
+ return { isAuthenticated: false };
6345
+ }
6346
+ return {
6347
+ isAuthenticated: true,
6348
+ expiresAt: tokens.expiresAt
6349
+ };
6350
+ } catch (error) {
6351
+ return {
6352
+ isAuthenticated: false,
6353
+ error: error instanceof Error ? error.message : "Failed to get OAuth status"
6354
+ };
6355
+ }
6356
+ }
6357
+ /**
6358
+ * Builds the authorization URL with all required parameters
6359
+ */
6360
+ buildAuthorizationUrl(state, codeChallenge) {
6361
+ const params = new URLSearchParams({
6362
+ response_type: "code",
6363
+ client_id: OAUTH_CONFIG.clientId,
6364
+ redirect_uri: OAUTH_CONFIG.redirectUri,
6365
+ scope: OAUTH_CONFIG.scopes.join(" "),
6366
+ state
6367
+ });
6368
+ if (codeChallenge) {
6369
+ params.set("code_challenge", codeChallenge);
6370
+ params.set("code_challenge_method", "S256");
6371
+ }
6372
+ return `${OAUTH_CONFIG.authorizationEndpoint}?${params.toString()}`;
6373
+ }
6374
+ /**
6375
+ * Generates a random state string for CSRF protection
6376
+ */
6377
+ generateRandomState() {
6378
+ return Array.from(crypto.getRandomValues(new Uint8Array(32))).map((b) => b.toString(16).padStart(2, "0")).join("");
6379
+ }
6380
+ /**
6381
+ * Generates PKCE code verifier and challenge
6382
+ * PKCE (Proof Key for Code Exchange) adds security to OAuth for public clients
6383
+ */
6384
+ generatePKCEChallenge() {
6385
+ const codeVerifier = randomBytes(32).toString("base64url");
6386
+ const hash = createHash("sha256").update(codeVerifier).digest("base64url");
6387
+ return {
6388
+ codeVerifier,
6389
+ codeChallenge: hash
6390
+ };
6391
+ }
6392
+ /**
6393
+ * Open a URL in the default browser cross-platform
6394
+ */
6395
+ openBrowser(url) {
6396
+ const os3 = platform();
6397
+ let command;
6398
+ let args;
6399
+ switch (os3) {
6400
+ case "darwin":
6401
+ command = "open";
6402
+ args = [url];
6403
+ break;
6404
+ case "win32":
6405
+ command = "start";
6406
+ args = ["", url];
6407
+ break;
6408
+ default:
6409
+ command = "xdg-open";
6410
+ args = [url];
6411
+ break;
6412
+ }
6413
+ const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
6414
+ spawn(command, args, options).unref();
6415
+ }
6416
+ /**
6417
+ * Build success HTML page
6418
+ */
6419
+ buildSuccessPage() {
6420
+ return `
6421
+ <!DOCTYPE html>
6422
+ <html lang="en">
6423
+ <head>
6424
+ <meta charset="UTF-8" />
6425
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6426
+ <title>Authentication Successful - Supatest CLI</title>
6427
+ <style>
6428
+ body {
6429
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
6430
+ display: flex;
6431
+ align-items: center;
6432
+ justify-content: center;
6433
+ height: 100vh;
6434
+ margin: 0;
6435
+ background: #fefefe;
6436
+ }
6437
+ .container {
6438
+ background: white;
6439
+ padding: 3rem 2rem;
6440
+ border-radius: 12px;
6441
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
6442
+ border: 1px solid #e5e7eb;
6443
+ text-align: center;
6444
+ max-width: 400px;
6445
+ }
6446
+ .success-icon {
6447
+ font-size: 48px;
6448
+ margin-bottom: 1rem;
6449
+ }
6450
+ h1 {
6451
+ color: #10b981;
6452
+ margin: 0 0 1rem 0;
6453
+ font-size: 24px;
6454
+ }
6455
+ p {
6456
+ color: #666;
6457
+ margin: 0;
6458
+ line-height: 1.5;
6459
+ }
6460
+ </style>
6461
+ </head>
6462
+ <body>
6463
+ <div class="container">
6464
+ <div class="success-icon">\u2705</div>
6465
+ <h1>Authentication Successful!</h1>
6466
+ <p>You're now authenticated with Claude.</p>
6467
+ <p style="margin-top: 1rem;">You can close this window and return to your terminal.</p>
6468
+ </div>
6469
+ </body>
6470
+ </html>
6471
+ `;
6472
+ }
6473
+ /**
6474
+ * Build error HTML page
6475
+ */
6476
+ buildErrorPage(errorMessage) {
6477
+ const escapedError = errorMessage.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6478
+ return `
6479
+ <!DOCTYPE html>
6480
+ <html lang="en">
6481
+ <head>
6482
+ <meta charset="UTF-8" />
6483
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6484
+ <title>Authentication Failed - Supatest CLI</title>
6485
+ <style>
6486
+ body {
6487
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
6488
+ display: flex;
6489
+ align-items: center;
6490
+ justify-content: center;
6491
+ height: 100vh;
6492
+ margin: 0;
6493
+ background: #fefefe;
6494
+ }
6495
+ .container {
6496
+ background: white;
6497
+ padding: 3rem 2rem;
6498
+ border-radius: 12px;
6499
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
6500
+ border: 1px solid #e5e7eb;
6501
+ text-align: center;
6502
+ max-width: 400px;
6503
+ }
6504
+ .error-icon {
6505
+ font-size: 48px;
6506
+ margin-bottom: 1rem;
6507
+ }
6508
+ h1 {
6509
+ color: #dc2626;
6510
+ margin: 0 0 1rem 0;
6511
+ font-size: 24px;
6512
+ }
6513
+ p {
6514
+ color: #666;
6515
+ margin: 0;
6516
+ line-height: 1.5;
6517
+ }
6518
+ </style>
6519
+ </head>
6520
+ <body>
6521
+ <div class="container">
6522
+ <div class="error-icon">\u274C</div>
6523
+ <h1>Authentication Failed</h1>
6524
+ <p>${escapedError}</p>
6525
+ <p style="margin-top: 1rem;">You can close this window and try again.</p>
6526
+ </div>
6527
+ </body>
6528
+ </html>
6529
+ `;
6530
+ }
6531
+ };
6532
+ }
6533
+ });
6534
+
6535
+ // src/utils/secret-storage.ts
6536
+ var secret_storage_exports = {};
6537
+ __export(secret_storage_exports, {
6538
+ deleteSecret: () => deleteSecret,
6539
+ getSecret: () => getSecret,
6540
+ getSecretStorage: () => getSecretStorage,
6541
+ listSecrets: () => listSecrets,
6542
+ setSecret: () => setSecret
6543
+ });
6544
+ import { promises as fs } from "fs";
6545
+ import { homedir } from "os";
6546
+ import { dirname, join } from "path";
6547
+ async function getSecret(key) {
6548
+ return storage.getSecret(key);
6549
+ }
6550
+ async function setSecret(key, value) {
6551
+ await storage.setSecret(key, value);
6552
+ }
6553
+ async function deleteSecret(key) {
6554
+ return storage.deleteSecret(key);
6555
+ }
6556
+ async function listSecrets() {
6557
+ return storage.listSecrets();
6558
+ }
6559
+ function getSecretStorage() {
6560
+ return storage;
6561
+ }
6562
+ var SECRET_FILE_NAME, FileSecretStorage, storage;
6563
+ var init_secret_storage = __esm({
6564
+ "src/utils/secret-storage.ts"() {
6565
+ "use strict";
6566
+ SECRET_FILE_NAME = "secrets.json";
6567
+ FileSecretStorage = class {
6568
+ secretFilePath;
6569
+ constructor() {
6570
+ const rootDirName = process.env.NODE_ENV === "development" ? ".supatest-dev" : ".supatest";
6571
+ const secretsDir = join(homedir(), rootDirName, "claude-auth");
6572
+ this.secretFilePath = join(secretsDir, SECRET_FILE_NAME);
6573
+ }
6574
+ async ensureDirectoryExists() {
6575
+ const dir = dirname(this.secretFilePath);
6576
+ await fs.mkdir(dir, { recursive: true, mode: 448 });
6577
+ }
6578
+ async loadSecrets() {
6579
+ try {
6580
+ const data = await fs.readFile(this.secretFilePath, "utf-8");
6581
+ const secrets = JSON.parse(data);
6582
+ return new Map(Object.entries(secrets));
6583
+ } catch (error) {
6584
+ const err = error;
6585
+ if (err.code === "ENOENT") {
6586
+ return /* @__PURE__ */ new Map();
6587
+ }
6588
+ try {
6589
+ await fs.unlink(this.secretFilePath);
6590
+ } catch {
6591
+ }
6592
+ return /* @__PURE__ */ new Map();
6593
+ }
6594
+ }
6595
+ async saveSecrets(secrets) {
6596
+ await this.ensureDirectoryExists();
6597
+ const data = Object.fromEntries(secrets);
6598
+ const json = JSON.stringify(data, null, 2);
6599
+ await fs.writeFile(this.secretFilePath, json, { mode: 384 });
6600
+ }
6601
+ async getSecret(key) {
6602
+ const secrets = await this.loadSecrets();
6603
+ return secrets.get(key) ?? null;
6604
+ }
6605
+ async setSecret(key, value) {
6606
+ const secrets = await this.loadSecrets();
6607
+ secrets.set(key, value);
6608
+ await this.saveSecrets(secrets);
6609
+ }
6610
+ async deleteSecret(key) {
6611
+ const secrets = await this.loadSecrets();
6612
+ if (!secrets.has(key)) {
6613
+ return false;
6614
+ }
6615
+ secrets.delete(key);
6616
+ if (secrets.size === 0) {
6617
+ try {
6618
+ await fs.unlink(this.secretFilePath);
6619
+ } catch (error) {
6620
+ const err = error;
6621
+ if (err.code !== "ENOENT") {
6622
+ throw error;
6623
+ }
6624
+ }
6625
+ } else {
6626
+ await this.saveSecrets(secrets);
6627
+ }
6628
+ return true;
6629
+ }
6630
+ async listSecrets() {
6631
+ const secrets = await this.loadSecrets();
6632
+ return Array.from(secrets.keys());
6633
+ }
6634
+ };
6635
+ storage = new FileSecretStorage();
5632
6636
  }
5633
6637
  });
5634
6638
 
5635
6639
  // src/commands/setup.ts
5636
- import { execSync, spawnSync } from "child_process";
5637
- import fs from "fs";
6640
+ import { execSync, spawn as spawn2, spawnSync } from "child_process";
6641
+ import fs2 from "fs";
5638
6642
  import os from "os";
5639
6643
  import path from "path";
5640
6644
  function parseVersion(versionString) {
@@ -5688,7 +6692,7 @@ function getPlaywrightCachePath() {
5688
6692
  // Windows
5689
6693
  ];
5690
6694
  for (const cachePath of cachePaths) {
5691
- if (fs.existsSync(cachePath)) {
6695
+ if (fs2.existsSync(cachePath)) {
5692
6696
  return cachePath;
5693
6697
  }
5694
6698
  }
@@ -5698,7 +6702,7 @@ function getInstalledChromiumVersion() {
5698
6702
  const cachePath = getPlaywrightCachePath();
5699
6703
  if (!cachePath) return null;
5700
6704
  try {
5701
- const entries = fs.readdirSync(cachePath);
6705
+ const entries = fs2.readdirSync(cachePath);
5702
6706
  const chromiumVersions = entries.filter((entry) => entry.startsWith("chromium-") && !entry.includes("headless")).map((entry) => entry.replace("chromium-", "")).sort((a, b) => Number(b) - Number(a));
5703
6707
  return chromiumVersions[0] || null;
5704
6708
  } catch {
@@ -5730,47 +6734,64 @@ function checkNodeVersion() {
5730
6734
  version: nodeVersion.raw
5731
6735
  };
5732
6736
  }
5733
- function installChromium() {
5734
- console.log(" Running: npx playwright install chromium");
5735
- console.log("");
5736
- try {
5737
- const result = spawnSync("npx", ["playwright", "install", "chromium"], {
6737
+ async function installChromium() {
6738
+ return new Promise((resolve2) => {
6739
+ const child = spawn2("npx", ["playwright", "install", "chromium"], {
5738
6740
  stdio: "inherit",
5739
6741
  shell: true
5740
6742
  // Required for Windows where npx is npx.cmd
5741
6743
  });
5742
- if (result.status === 0) {
5743
- return {
5744
- ok: true,
5745
- message: "Chromium browser installed successfully."
5746
- };
5747
- }
5748
- return {
5749
- ok: false,
5750
- message: `Playwright install exited with code ${result.status}`
5751
- };
5752
- } catch (error) {
5753
- return {
5754
- ok: false,
5755
- message: `Failed to install Chromium: ${error instanceof Error ? error.message : String(error)}`
5756
- };
5757
- }
6744
+ child.on("close", (code) => {
6745
+ if (code === 0) {
6746
+ resolve2({
6747
+ ok: true,
6748
+ message: "Chromium browser installed successfully."
6749
+ });
6750
+ } else {
6751
+ resolve2({
6752
+ ok: false,
6753
+ message: `Playwright install exited with code ${code}`
6754
+ });
6755
+ }
6756
+ });
6757
+ child.on("error", (error) => {
6758
+ resolve2({
6759
+ ok: false,
6760
+ message: `Failed to install Chromium: ${error.message}`
6761
+ });
6762
+ });
6763
+ });
5758
6764
  }
5759
6765
  function createSupatestConfig(cwd) {
5760
6766
  const supatestDir = path.join(cwd, ".supatest");
5761
6767
  const mcpJsonPath = path.join(supatestDir, "mcp.json");
5762
6768
  try {
5763
- if (fs.existsSync(mcpJsonPath)) {
6769
+ if (!fs2.existsSync(supatestDir)) {
6770
+ fs2.mkdirSync(supatestDir, { recursive: true });
6771
+ }
6772
+ let config2;
6773
+ let fileExisted = false;
6774
+ if (fs2.existsSync(mcpJsonPath)) {
6775
+ fileExisted = true;
6776
+ const existingContent = fs2.readFileSync(mcpJsonPath, "utf-8");
6777
+ config2 = JSON.parse(existingContent);
6778
+ } else {
6779
+ config2 = {};
6780
+ }
6781
+ if (!config2.mcpServers || typeof config2.mcpServers !== "object") {
6782
+ config2.mcpServers = {};
6783
+ }
6784
+ if (!config2.mcpServers.playwright) {
6785
+ config2.mcpServers.playwright = DEFAULT_MCP_CONFIG.mcpServers.playwright;
6786
+ }
6787
+ fs2.writeFileSync(mcpJsonPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
6788
+ if (fileExisted) {
5764
6789
  return {
5765
6790
  ok: true,
5766
- message: "mcp.json already exists",
6791
+ message: "Updated .supatest/mcp.json with Playwright MCP server configuration",
5767
6792
  created: false
5768
6793
  };
5769
6794
  }
5770
- if (!fs.existsSync(supatestDir)) {
5771
- fs.mkdirSync(supatestDir, { recursive: true });
5772
- }
5773
- fs.writeFileSync(mcpJsonPath, JSON.stringify(DEFAULT_MCP_CONFIG, null, 2) + "\n", "utf-8");
5774
6795
  return {
5775
6796
  ok: true,
5776
6797
  message: "Created .supatest/mcp.json with Playwright MCP server configuration",
@@ -5799,7 +6820,6 @@ async function setupCommand(options) {
5799
6820
  const output = [];
5800
6821
  const log = (msg) => {
5801
6822
  output.push(msg);
5802
- console.log(msg);
5803
6823
  };
5804
6824
  const result = {
5805
6825
  nodeVersionOk: false,
@@ -5833,7 +6853,7 @@ async function setupCommand(options) {
5833
6853
  result.playwrightInstalled = true;
5834
6854
  } else {
5835
6855
  log(" \u{1F4E6} Chromium browser not found. Installing...\n");
5836
- const chromiumResult = installChromium();
6856
+ const chromiumResult = await installChromium();
5837
6857
  result.playwrightInstalled = chromiumResult.ok;
5838
6858
  log("");
5839
6859
  if (chromiumResult.ok) {
@@ -5891,18 +6911,18 @@ var CLI_VERSION;
5891
6911
  var init_version = __esm({
5892
6912
  "src/version.ts"() {
5893
6913
  "use strict";
5894
- CLI_VERSION = "0.0.40";
6914
+ CLI_VERSION = "0.0.41";
5895
6915
  }
5896
6916
  });
5897
6917
 
5898
6918
  // src/utils/error-logger.ts
5899
- import * as fs2 from "fs";
6919
+ import * as fs3 from "fs";
5900
6920
  import * as os2 from "os";
5901
6921
  import * as path2 from "path";
5902
6922
  function ensureLogDir() {
5903
6923
  try {
5904
- if (!fs2.existsSync(LOGS_DIR)) {
5905
- fs2.mkdirSync(LOGS_DIR, { recursive: true });
6924
+ if (!fs3.existsSync(LOGS_DIR)) {
6925
+ fs3.mkdirSync(LOGS_DIR, { recursive: true });
5906
6926
  }
5907
6927
  return true;
5908
6928
  } catch {
@@ -5911,14 +6931,14 @@ function ensureLogDir() {
5911
6931
  }
5912
6932
  function rotateLogIfNeeded() {
5913
6933
  try {
5914
- if (!fs2.existsSync(ERROR_LOG_FILE)) return;
5915
- const stats = fs2.statSync(ERROR_LOG_FILE);
6934
+ if (!fs3.existsSync(ERROR_LOG_FILE)) return;
6935
+ const stats = fs3.statSync(ERROR_LOG_FILE);
5916
6936
  if (stats.size > MAX_LOG_SIZE) {
5917
6937
  const oldLogFile = `${ERROR_LOG_FILE}.old`;
5918
- if (fs2.existsSync(oldLogFile)) {
5919
- fs2.unlinkSync(oldLogFile);
6938
+ if (fs3.existsSync(oldLogFile)) {
6939
+ fs3.unlinkSync(oldLogFile);
5920
6940
  }
5921
- fs2.renameSync(ERROR_LOG_FILE, oldLogFile);
6941
+ fs3.renameSync(ERROR_LOG_FILE, oldLogFile);
5922
6942
  }
5923
6943
  } catch {
5924
6944
  }
@@ -5954,7 +6974,7 @@ function logError(error, context) {
5954
6974
  const logLine = `${JSON.stringify(entry)}
5955
6975
  `;
5956
6976
  try {
5957
- fs2.appendFileSync(ERROR_LOG_FILE, logLine);
6977
+ fs3.appendFileSync(ERROR_LOG_FILE, logLine);
5958
6978
  } catch {
5959
6979
  }
5960
6980
  }
@@ -5971,7 +6991,7 @@ var init_error_logger = __esm({
5971
6991
  });
5972
6992
 
5973
6993
  // src/utils/logger.ts
5974
- import * as fs3 from "fs";
6994
+ import * as fs4 from "fs";
5975
6995
  import * as path3 from "path";
5976
6996
  import chalk from "chalk";
5977
6997
  var Logger, logger;
@@ -6005,7 +7025,7 @@ ${"=".repeat(80)}
6005
7025
  ${"=".repeat(80)}
6006
7026
  `;
6007
7027
  try {
6008
- fs3.appendFileSync(this.logFile, separator);
7028
+ fs4.appendFileSync(this.logFile, separator);
6009
7029
  } catch (error) {
6010
7030
  }
6011
7031
  }
@@ -6019,7 +7039,7 @@ ${"=".repeat(80)}
6019
7039
  ` : `[${timestamp}] [${level}] ${message}
6020
7040
  `;
6021
7041
  try {
6022
- fs3.appendFileSync(this.logFile, logEntry);
7042
+ fs4.appendFileSync(this.logFile, logEntry);
6023
7043
  } catch (error) {
6024
7044
  }
6025
7045
  }
@@ -6626,13 +7646,146 @@ var init_api_client = __esm({
6626
7646
  logger.debug(`Fetched ${data.history.length} history items for test ${testId}`);
6627
7647
  return data;
6628
7648
  }
7649
+ /**
7650
+ * Assign tests to yourself (or someone else)
7651
+ * @param params - Assignment parameters
7652
+ * @returns Assignment result with assigned tests and conflicts
7653
+ */
7654
+ async assignTests(params) {
7655
+ const url = `${this.apiUrl}/v1/fix-assignments/assign`;
7656
+ logger.debug(`Assigning tests`, {
7657
+ runId: params.runId,
7658
+ count: params.testRunIds.length
7659
+ });
7660
+ const response = await fetch(url, {
7661
+ method: "POST",
7662
+ headers: {
7663
+ "Content-Type": "application/json",
7664
+ Authorization: `Bearer ${this.apiKey}`
7665
+ },
7666
+ body: JSON.stringify({
7667
+ testRunIds: params.testRunIds,
7668
+ assignedTo: params.assignedTo
7669
+ })
7670
+ });
7671
+ if (!response.ok) {
7672
+ const errorText = await response.text();
7673
+ throw new ApiError(response.status, response.statusText, errorText);
7674
+ }
7675
+ const data = await response.json();
7676
+ logger.debug(
7677
+ `Assigned ${data.assigned.length} tests, ${data.conflicts.length} conflicts`
7678
+ );
7679
+ return data;
7680
+ }
7681
+ /**
7682
+ * Mark assignment as complete
7683
+ * @param params - Completion parameters
7684
+ * @returns Success status
7685
+ */
7686
+ async completeAssignment(params) {
7687
+ const url = `${this.apiUrl}/v1/fix-assignments/${params.assignmentId}/status`;
7688
+ logger.debug(`Completing assignment`, {
7689
+ assignmentId: params.assignmentId,
7690
+ status: params.status
7691
+ });
7692
+ const response = await fetch(url, {
7693
+ method: "PATCH",
7694
+ headers: {
7695
+ "Content-Type": "application/json",
7696
+ Authorization: `Bearer ${this.apiKey}`
7697
+ },
7698
+ body: JSON.stringify({
7699
+ status: params.status,
7700
+ fixSessionId: params.fixSessionId
7701
+ })
7702
+ });
7703
+ if (!response.ok) {
7704
+ const errorText = await response.text();
7705
+ throw new ApiError(response.status, response.statusText, errorText);
7706
+ }
7707
+ const data = await response.json();
7708
+ logger.debug(`Assignment completed: ${params.assignmentId}`);
7709
+ return data;
7710
+ }
7711
+ /**
7712
+ * Release claim (delete assignment)
7713
+ * @param assignmentId - The assignment ID to release
7714
+ * @returns Success status
7715
+ */
7716
+ async releaseAssignment(assignmentId) {
7717
+ const url = `${this.apiUrl}/v1/fix-assignments/${assignmentId}`;
7718
+ logger.debug(`Releasing assignment: ${assignmentId}`);
7719
+ const response = await fetch(url, {
7720
+ method: "DELETE",
7721
+ headers: {
7722
+ Authorization: `Bearer ${this.apiKey}`
7723
+ }
7724
+ });
7725
+ if (!response.ok) {
7726
+ const errorText = await response.text();
7727
+ throw new ApiError(response.status, response.statusText, errorText);
7728
+ }
7729
+ const data = await response.json();
7730
+ logger.debug(`Assignment released: ${assignmentId}`);
7731
+ return data;
7732
+ }
7733
+ /**
7734
+ * Get my current assignments
7735
+ * @returns List of current assignments
7736
+ */
7737
+ async getMyAssignments() {
7738
+ const url = `${this.apiUrl}/v1/fix-assignments?status=assigned`;
7739
+ logger.debug(`Fetching my assignments`);
7740
+ const response = await fetch(url, {
7741
+ method: "GET",
7742
+ headers: {
7743
+ Authorization: `Bearer ${this.apiKey}`
7744
+ }
7745
+ });
7746
+ if (!response.ok) {
7747
+ const errorText = await response.text();
7748
+ throw new ApiError(response.status, response.statusText, errorText);
7749
+ }
7750
+ const data = await response.json();
7751
+ logger.debug(`Fetched ${data.assignments.length} assignments`);
7752
+ return { assignments: data.assignments };
7753
+ }
7754
+ /**
7755
+ * Get assignments for a specific run (to show what others are working on)
7756
+ * @param runId - The run ID
7757
+ * @returns List of assignments for the run
7758
+ */
7759
+ async getRunAssignments(runId) {
7760
+ const url = `${this.apiUrl}/v1/fix-assignments?runId=${runId}&status=assigned`;
7761
+ logger.debug(`Fetching assignments for run: ${runId}`);
7762
+ const response = await fetch(url, {
7763
+ method: "GET",
7764
+ headers: {
7765
+ Authorization: `Bearer ${this.apiKey}`
7766
+ }
7767
+ });
7768
+ if (!response.ok) {
7769
+ const errorText = await response.text();
7770
+ throw new ApiError(response.status, response.statusText, errorText);
7771
+ }
7772
+ const data = await response.json();
7773
+ logger.debug(`Fetched ${data.assignments.length} assignments for run ${runId}`);
7774
+ return {
7775
+ assignments: data.assignments.map((a) => ({
7776
+ testRunId: a.testRunId,
7777
+ assignedTo: a.assignedTo,
7778
+ assignedAt: a.assignedAt
7779
+ }))
7780
+ };
7781
+ }
6629
7782
  };
6630
7783
  }
6631
7784
  });
6632
7785
 
6633
7786
  // src/utils/command-discovery.ts
6634
7787
  import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "fs";
6635
- import { join as join3, relative } from "path";
7788
+ import { join as join4, relative } from "path";
6636
7789
  function parseMarkdownFrontmatter(content) {
6637
7790
  const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
6638
7791
  const match = content.match(frontmatterRegex);
@@ -6657,7 +7810,7 @@ function discoverMarkdownFiles(dir, baseDir, files = []) {
6657
7810
  }
6658
7811
  const entries = readdirSync(dir);
6659
7812
  for (const entry of entries) {
6660
- const fullPath = join3(dir, entry);
7813
+ const fullPath = join4(dir, entry);
6661
7814
  const stat = statSync2(fullPath);
6662
7815
  if (stat.isDirectory()) {
6663
7816
  discoverMarkdownFiles(fullPath, baseDir, files);
@@ -6668,7 +7821,7 @@ function discoverMarkdownFiles(dir, baseDir, files = []) {
6668
7821
  return files;
6669
7822
  }
6670
7823
  function discoverCommands(cwd) {
6671
- const commandsDir = join3(cwd, ".supatest", "commands");
7824
+ const commandsDir = join4(cwd, ".supatest", "commands");
6672
7825
  if (!existsSync2(commandsDir)) {
6673
7826
  return [];
6674
7827
  }
@@ -6691,9 +7844,9 @@ function discoverCommands(cwd) {
6691
7844
  return commands.sort((a, b) => a.name.localeCompare(b.name));
6692
7845
  }
6693
7846
  function expandCommand(cwd, commandName, args) {
6694
- const commandsDir = join3(cwd, ".supatest", "commands");
7847
+ const commandsDir = join4(cwd, ".supatest", "commands");
6695
7848
  const relativePath = commandName.replace(/\./g, "/") + ".md";
6696
- const filePath = join3(commandsDir, relativePath);
7849
+ const filePath = join4(commandsDir, relativePath);
6697
7850
  if (!existsSync2(filePath)) {
6698
7851
  return null;
6699
7852
  }
@@ -6716,7 +7869,7 @@ function expandCommand(cwd, commandName, args) {
6716
7869
  }
6717
7870
  }
6718
7871
  function discoverAgents(cwd) {
6719
- const agentsDir = join3(cwd, ".supatest", "agents");
7872
+ const agentsDir = join4(cwd, ".supatest", "agents");
6720
7873
  if (!existsSync2(agentsDir)) {
6721
7874
  return [];
6722
7875
  }
@@ -6747,8 +7900,8 @@ var init_command_discovery = __esm({
6747
7900
 
6748
7901
  // src/utils/mcp-loader.ts
6749
7902
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
6750
- import { homedir as homedir2 } from "os";
6751
- import { join as join4 } from "path";
7903
+ import { homedir as homedir3 } from "os";
7904
+ import { join as join5 } from "path";
6752
7905
  function expandEnvVar(value) {
6753
7906
  return value.replace(/\$\{([^}]+)\}/g, (_, expr) => {
6754
7907
  const [varName, defaultValue] = expr.split(":-");
@@ -6797,9 +7950,9 @@ function loadMcpServersFromFile(mcpPath) {
6797
7950
  }
6798
7951
  }
6799
7952
  function loadMcpServers(cwd) {
6800
- const globalMcpPath = join4(homedir2(), ".supatest", "mcp.json");
7953
+ const globalMcpPath = join5(homedir3(), ".supatest", "mcp.json");
6801
7954
  const globalServers = loadMcpServersFromFile(globalMcpPath);
6802
- const projectMcpPath = join4(cwd, ".supatest", "mcp.json");
7955
+ const projectMcpPath = join5(cwd, ".supatest", "mcp.json");
6803
7956
  const projectServers = loadMcpServersFromFile(projectMcpPath);
6804
7957
  return { ...globalServers, ...projectServers };
6805
7958
  }
@@ -6811,11 +7964,11 @@ var init_mcp_loader = __esm({
6811
7964
 
6812
7965
  // src/utils/project-instructions.ts
6813
7966
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
6814
- import { join as join5 } from "path";
7967
+ import { join as join6 } from "path";
6815
7968
  function loadProjectInstructions(cwd) {
6816
7969
  const paths = [
6817
- join5(cwd, "SUPATEST.md"),
6818
- join5(cwd, ".supatest", "SUPATEST.md")
7970
+ join6(cwd, "SUPATEST.md"),
7971
+ join6(cwd, ".supatest", "SUPATEST.md")
6819
7972
  ];
6820
7973
  for (const path6 of paths) {
6821
7974
  if (existsSync4(path6)) {
@@ -6835,8 +7988,8 @@ var init_project_instructions = __esm({
6835
7988
 
6836
7989
  // src/core/agent.ts
6837
7990
  import { createRequire } from "module";
6838
- import { homedir as homedir3 } from "os";
6839
- import { dirname, join as join6 } from "path";
7991
+ import { homedir as homedir4 } from "os";
7992
+ import { dirname as dirname2, join as join7 } from "path";
6840
7993
  import { query } from "@anthropic-ai/claude-agent-sdk";
6841
7994
  var CoreAgent;
6842
7995
  var init_agent = __esm({
@@ -6853,6 +8006,8 @@ var init_agent = __esm({
6853
8006
  presenter;
6854
8007
  abortController = null;
6855
8008
  messageBridge = null;
8009
+ // Map of pending questions waiting for user answers
8010
+ pendingQuestionResolvers = /* @__PURE__ */ new Map();
6856
8011
  constructor(presenter, messageBridge) {
6857
8012
  this.presenter = presenter;
6858
8013
  this.messageBridge = messageBridge ?? null;
@@ -6866,9 +8021,37 @@ var init_agent = __esm({
6866
8021
  this.abortController.abort();
6867
8022
  }
6868
8023
  }
8024
+ /**
8025
+ * Create a promise that waits for user to answer a question.
8026
+ * Used by canUseTool callback to pause agent execution until user responds.
8027
+ */
8028
+ createQuestionPromise(questionId) {
8029
+ return new Promise((resolve2) => {
8030
+ this.pendingQuestionResolvers.set(questionId, resolve2);
8031
+ });
8032
+ }
8033
+ /**
8034
+ * Submit an answer for a pending question.
8035
+ * Called from the UI when user responds to AskUserQuestion.
8036
+ * @param questionId - The unique ID of the question
8037
+ * @param answers - The user's answers, or null if dismissed
8038
+ * @returns true if question was found and resolved, false otherwise
8039
+ */
8040
+ submitAnswer(questionId, answers) {
8041
+ const resolver = this.pendingQuestionResolvers.get(questionId);
8042
+ if (resolver) {
8043
+ resolver(answers);
8044
+ this.pendingQuestionResolvers.delete(questionId);
8045
+ return true;
8046
+ }
8047
+ return false;
8048
+ }
6869
8049
  async run(config2) {
6870
8050
  this.abortController = new AbortController();
6871
8051
  this.presenter.onStart(config2);
8052
+ if (config2.providerSessionId) {
8053
+ this.presenter.onLog(`Resuming with providerSessionId: ${config2.providerSessionId}`);
8054
+ }
6872
8055
  const claudeCodePath = await this.resolveClaudeCodePath();
6873
8056
  let prompt = config2.task;
6874
8057
  if (config2.logs) {
@@ -6936,7 +8119,7 @@ ${projectInstructions}`,
6936
8119
  this.presenter.onLog(`Auth: Using Claude Max (default Claude Code credentials)`);
6937
8120
  logger.debug("[agent] Claude Max mode: Using default ~/.claude/ config, cleared provider credentials");
6938
8121
  } else {
6939
- const internalConfigDir = join6(homedir3(), ".supatest", "claude-internal");
8122
+ const internalConfigDir = join7(homedir4(), ".supatest", "claude-internal");
6940
8123
  cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
6941
8124
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
6942
8125
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
@@ -6951,6 +8134,9 @@ ${projectInstructions}`,
6951
8134
  CLAUDE_CONFIG_DIR: cleanEnv.CLAUDE_CONFIG_DIR || "(using default ~/.claude/)"
6952
8135
  });
6953
8136
  cleanEnv.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
8137
+ cleanEnv.CLAUDE_CODE_ENABLE_TELEMETRY = "0";
8138
+ cleanEnv.DISABLE_TELEMETRY = "1";
8139
+ cleanEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
6954
8140
  const selectedModel = config2.selectedModel || "premium";
6955
8141
  const defaultModel = resolveToAnthropicModel(selectedModel);
6956
8142
  const queryOptions = {
@@ -6982,7 +8168,7 @@ ${projectInstructions}`,
6982
8168
  return servers;
6983
8169
  })(),
6984
8170
  // Resume from previous session if providerSessionId is provided
6985
- // This allows the agent to continue conversations with full context
8171
+ // This allows the agent to continue sessions with full context
6986
8172
  // Note: Sessions expire after ~30 days due to Anthropic's data retention policy
6987
8173
  ...config2.providerSessionId && {
6988
8174
  resume: config2.providerSessionId
@@ -6999,6 +8185,29 @@ ${projectInstructions}`,
6999
8185
  env: cleanEnv,
7000
8186
  stderr: (msg) => {
7001
8187
  this.presenter.onLog(`[Agent Failure] ${msg}`);
8188
+ },
8189
+ // Intercept AskUserQuestion tool to wait for user response
8190
+ canUseTool: async (toolName, input) => {
8191
+ if (toolName !== "AskUserQuestion") {
8192
+ return { behavior: "allow", updatedInput: input };
8193
+ }
8194
+ const questions = input.questions || [];
8195
+ if (questions.length === 0) {
8196
+ logger.warn("[AskUserQuestion] No questions provided");
8197
+ return { behavior: "deny", message: "No questions provided" };
8198
+ }
8199
+ const questionId = `ask-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
8200
+ this.presenter.onToolUse?.(toolName, input, questionId);
8201
+ const answers = await this.createQuestionPromise(questionId);
8202
+ if (answers === null) {
8203
+ this.presenter.onToolResult?.(questionId, JSON.stringify({ dismissed: true }));
8204
+ return { behavior: "deny", message: "User dismissed the question" };
8205
+ }
8206
+ this.presenter.onToolResult?.(questionId, JSON.stringify({ answers }));
8207
+ return {
8208
+ behavior: "allow",
8209
+ updatedInput: { questions, answers }
8210
+ };
7002
8211
  }
7003
8212
  };
7004
8213
  let resultText = "";
@@ -7023,7 +8232,7 @@ ${projectInstructions}`,
7023
8232
  };
7024
8233
  const isSessionExpiredError = (errorMsg) => {
7025
8234
  const expiredPatterns = [
7026
- "no conversation found",
8235
+ "no session found",
7027
8236
  "session not found",
7028
8237
  "session expired",
7029
8238
  "invalid session"
@@ -7031,6 +8240,20 @@ ${projectInstructions}`,
7031
8240
  const lowerError = errorMsg.toLowerCase();
7032
8241
  return expiredPatterns.some((pattern) => lowerError.includes(pattern));
7033
8242
  };
8243
+ const isContextWindowExceededError = (errorMsg) => {
8244
+ const contextPatterns = [
8245
+ "exceed context limit",
8246
+ "exceeds context limit",
8247
+ "context length",
8248
+ "maximum context",
8249
+ "prompt is too long",
8250
+ "input_length and max_tokens",
8251
+ "requested token count exceeds",
8252
+ "context window"
8253
+ ];
8254
+ const lowerError = errorMsg.toLowerCase();
8255
+ return contextPatterns.some((pattern) => lowerError.includes(pattern));
8256
+ };
7034
8257
  const runQuery = async (options) => {
7035
8258
  logger.debug("[agent] Starting query", {
7036
8259
  model: options.model,
@@ -7181,11 +8404,17 @@ ${projectInstructions}`,
7181
8404
  wasInterrupted = true;
7182
8405
  logger.debug("[agent] Query was aborted by user");
7183
8406
  } else if (config2.providerSessionId && isSessionExpiredError(errorMessage)) {
7184
- const expiredMessage = "Can't continue conversation older than 30 days. Please start a new session.";
8407
+ const expiredMessage = "Can't continue session older than 30 days. Please start a new session.";
7185
8408
  this.presenter.onError(expiredMessage, true);
7186
8409
  hasError = true;
7187
8410
  errors.push(expiredMessage);
7188
8411
  logger.debug("[agent] Session expired error");
8412
+ } else if (isContextWindowExceededError(errorMessage)) {
8413
+ const contextMessage = "Context window exceeded. The session has grown too large.\n\nSuggestions:\n\u2022 Start a new session to begin fresh\n\u2022 Use /clear to reset the current session\n\u2022 Reduce file sizes or number of files in your request\n\u2022 Be more specific with your prompts to reduce context";
8414
+ this.presenter.onError(contextMessage, true);
8415
+ hasError = true;
8416
+ errors.push(contextMessage);
8417
+ logger.debug("[agent] Context window exceeded error");
7189
8418
  } else {
7190
8419
  logError(error, {
7191
8420
  source: "CoreAgent.run",
@@ -7220,7 +8449,7 @@ ${projectInstructions}`,
7220
8449
  let claudeCodePath;
7221
8450
  const require2 = createRequire(import.meta.url);
7222
8451
  const sdkPath = require2.resolve("@anthropic-ai/claude-agent-sdk/sdk.mjs");
7223
- claudeCodePath = join6(dirname(sdkPath), "cli.js");
8452
+ claudeCodePath = join7(dirname2(sdkPath), "cli.js");
7224
8453
  this.presenter.onLog(`Using SDK CLI: ${claudeCodePath}`);
7225
8454
  if (config.claudeCodeExecutablePath) {
7226
8455
  claudeCodePath = config.claudeCodeExecutablePath;
@@ -7270,6 +8499,10 @@ function getToolDescription(toolName, input) {
7270
8499
  case "BashOutput":
7271
8500
  case "Command Output":
7272
8501
  return input?.bash_id || "shell output";
8502
+ case "AskUserQuestion": {
8503
+ const question = input?.questions?.[0]?.question || input?.question || "Question";
8504
+ return question.length > 60 ? `${question.substring(0, 60)}...` : question;
8505
+ }
7273
8506
  default:
7274
8507
  return toolName;
7275
8508
  }
@@ -7402,6 +8635,15 @@ var init_react = __esm({
7402
8635
  this.callbacks.onExitPlanMode?.();
7403
8636
  } else if (tool === "EnterPlanMode") {
7404
8637
  this.callbacks.onEnterPlanMode?.();
8638
+ } else if (tool === "AskUserQuestion") {
8639
+ const questions = input?.questions || [];
8640
+ if (questions.length > 0) {
8641
+ const firstQuestion = questions[0];
8642
+ const question = firstQuestion.question || "";
8643
+ const options = firstQuestion.options || [];
8644
+ const multiSelect = firstQuestion.multiSelect || false;
8645
+ this.callbacks.onAskUserQuestion?.(toolId, question, options, multiSelect);
8646
+ }
7405
8647
  }
7406
8648
  streamEventAsync(this.apiClient, this.sessionId, {
7407
8649
  type: "tool_use",
@@ -7559,6 +8801,7 @@ var init_SessionContext = __esm({
7559
8801
  const [allToolsExpanded, setAllToolsExpanded] = useState(true);
7560
8802
  const [toolGroupsExpanded, setToolGroupsExpanded] = useState(false);
7561
8803
  const [staticRemountKey, setStaticRemountKey] = useState(0);
8804
+ const [pendingQuestion, setPendingQuestion] = useState(null);
7562
8805
  const addMessage = useCallback(
7563
8806
  (message) => {
7564
8807
  const expandableTools = ["Bash", "BashOutput", "Command Output"];
@@ -7689,7 +8932,9 @@ var init_SessionContext = __esm({
7689
8932
  llmProvider,
7690
8933
  setLlmProvider,
7691
8934
  staticRemountKey,
7692
- refreshStatic
8935
+ refreshStatic,
8936
+ pendingQuestion,
8937
+ setPendingQuestion
7693
8938
  }), [
7694
8939
  messages,
7695
8940
  addMessage,
@@ -7714,7 +8959,8 @@ var init_SessionContext = __esm({
7714
8959
  selectedModel,
7715
8960
  llmProvider,
7716
8961
  staticRemountKey,
7717
- refreshStatic
8962
+ refreshStatic,
8963
+ pendingQuestion
7718
8964
  ]);
7719
8965
  const usageStatsValue = useMemo(() => ({
7720
8966
  usageStats,
@@ -8445,6 +9691,14 @@ function getCommandDisplay(toolName, input) {
8445
9691
  return input.pattern ? [`"${input.pattern}"${input.path ? ` in ${input.path}` : ""}`] : null;
8446
9692
  case "Glob":
8447
9693
  return input.pattern ? [input.pattern] : null;
9694
+ case "AskUserQuestion": {
9695
+ const questions = input.questions || [];
9696
+ if (questions.length > 0) {
9697
+ const question = questions[0].question || "Question";
9698
+ return [question.length > 80 ? `${question.substring(0, 80)}...` : question];
9699
+ }
9700
+ return null;
9701
+ }
8448
9702
  default:
8449
9703
  return null;
8450
9704
  }
@@ -8460,7 +9714,8 @@ function getToolStyle(toolName) {
8460
9714
  Glob: { icon: "\u{1F50D}", color: theme.tool.search },
8461
9715
  Grep: { icon: "\u{1F50D}", color: theme.tool.search },
8462
9716
  Task: { icon: "\u{1F916}", color: theme.tool.agent },
8463
- TodoWrite: { icon: "\u{1F4DD}", color: theme.text.info }
9717
+ TodoWrite: { icon: "\u{1F4DD}", color: theme.text.info },
9718
+ AskUserQuestion: { icon: "\u2753", color: theme.text.info }
8464
9719
  };
8465
9720
  return styles[toolName] || { icon: "\u{1F527}", color: theme.text.secondary };
8466
9721
  }
@@ -8494,6 +9749,9 @@ function getResultSummary(toolName, result) {
8494
9749
  const lines = result.split("\n").filter((line) => line.trim());
8495
9750
  return lines.length > 0 ? `${lines.length} lines of output` : "No output";
8496
9751
  }
9752
+ case "AskUserQuestion": {
9753
+ return result ? `Answered: ${result.substring(0, 50)}${result.length > 50 ? "..." : ""}` : "Waiting for response...";
9754
+ }
8497
9755
  default:
8498
9756
  return null;
8499
9757
  }
@@ -8578,16 +9836,7 @@ var init_UserMessage = __esm({
8578
9836
  "use strict";
8579
9837
  init_theme();
8580
9838
  UserMessage = ({ text }) => {
8581
- return /* @__PURE__ */ React11.createElement(Box10, { alignItems: "center", flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.info }, "\u{1F464} "), /* @__PURE__ */ React11.createElement(
8582
- Box10,
8583
- {
8584
- borderColor: theme.text.dim,
8585
- borderStyle: "round",
8586
- paddingLeft: 1,
8587
- paddingRight: 1
8588
- },
8589
- /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.secondary }, text)
8590
- ));
9839
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.info }, "\u{1F464} "), /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.secondary }, text));
8591
9840
  };
8592
9841
  }
8593
9842
  });
@@ -8928,7 +10177,7 @@ var init_stdio = __esm({
8928
10177
  });
8929
10178
 
8930
10179
  // src/utils/encryption.ts
8931
- import crypto from "crypto";
10180
+ import crypto2 from "crypto";
8932
10181
  import { hostname, userInfo } from "os";
8933
10182
  function deriveEncryptionKey() {
8934
10183
  const host = hostname();
@@ -8937,7 +10186,7 @@ function deriveEncryptionKey() {
8937
10186
  if (process.env.DEBUG_ENCRYPTION) {
8938
10187
  console.error(`[encryption] hostname=${host}, username=${user}, salt=${salt}`);
8939
10188
  }
8940
- return crypto.scryptSync("supatest-cli-token", salt, KEY_LENGTH);
10189
+ return crypto2.scryptSync("supatest-cli-token", salt, KEY_LENGTH);
8941
10190
  }
8942
10191
  function getEncryptionKey() {
8943
10192
  if (!cachedKey) {
@@ -8947,8 +10196,8 @@ function getEncryptionKey() {
8947
10196
  }
8948
10197
  function encrypt(plaintext) {
8949
10198
  const key = getEncryptionKey();
8950
- const iv = crypto.randomBytes(IV_LENGTH);
8951
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
10199
+ const iv = crypto2.randomBytes(IV_LENGTH);
10200
+ const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
8952
10201
  let encrypted = cipher.update(plaintext, "utf8", "hex");
8953
10202
  encrypted += cipher.final("hex");
8954
10203
  const authTag = cipher.getAuthTag();
@@ -8963,7 +10212,7 @@ function decrypt(encryptedData) {
8963
10212
  const iv = Buffer.from(ivHex, "hex");
8964
10213
  const authTag = Buffer.from(authTagHex, "hex");
8965
10214
  const key = getEncryptionKey();
8966
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
10215
+ const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
8967
10216
  decipher.setAuthTag(authTag);
8968
10217
  let decrypted = decipher.update(encrypted, "hex", "utf8");
8969
10218
  decrypted += decipher.final("utf8");
@@ -8982,14 +10231,14 @@ var init_encryption = __esm({
8982
10231
 
8983
10232
  // src/utils/token-storage.ts
8984
10233
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
8985
- import { homedir as homedir5 } from "os";
8986
- import { join as join7 } from "path";
10234
+ import { homedir as homedir6 } from "os";
10235
+ import { join as join8 } from "path";
8987
10236
  function getTokenFilePath() {
8988
10237
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
8989
10238
  if (apiUrl === PRODUCTION_API_URL) {
8990
- return join7(CONFIG_DIR, "token.json");
10239
+ return join8(CONFIG_DIR, "token.json");
8991
10240
  }
8992
- return join7(CONFIG_DIR, "token.local.json");
10241
+ return join8(CONFIG_DIR, "token.local.json");
8993
10242
  }
8994
10243
  function isV2Format(stored) {
8995
10244
  return "version" in stored && stored.version === 2;
@@ -9057,10 +10306,10 @@ var init_token_storage = __esm({
9057
10306
  "src/utils/token-storage.ts"() {
9058
10307
  "use strict";
9059
10308
  init_encryption();
9060
- CONFIG_DIR = join7(homedir5(), ".supatest");
10309
+ CONFIG_DIR = join8(homedir6(), ".supatest");
9061
10310
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
9062
10311
  STORAGE_VERSION = 2;
9063
- TOKEN_FILE = join7(CONFIG_DIR, "token.json");
10312
+ TOKEN_FILE = join8(CONFIG_DIR, "token.json");
9064
10313
  }
9065
10314
  });
9066
10315
 
@@ -9084,7 +10333,7 @@ var init_message_bridge = __esm({
9084
10333
  this.sessionId = sessionId;
9085
10334
  }
9086
10335
  /**
9087
- * Push a user message to be injected into the conversation.
10336
+ * Push a user message to be injected into the session.
9088
10337
  * Call this from the UI when user submits a message during agent execution.
9089
10338
  */
9090
10339
  push(text) {
@@ -9156,15 +10405,15 @@ var init_message_bridge = __esm({
9156
10405
  });
9157
10406
 
9158
10407
  // src/commands/login.ts
9159
- import { spawn as spawn2 } from "child_process";
9160
- import crypto2 from "crypto";
9161
- import http from "http";
9162
- import { platform } from "os";
10408
+ import { spawn as spawn4 } from "child_process";
10409
+ import crypto3 from "crypto";
10410
+ import http2 from "http";
10411
+ import { platform as platform2 } from "os";
9163
10412
  function escapeHtml(text) {
9164
10413
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9165
10414
  }
9166
10415
  function generateState() {
9167
- return crypto2.randomBytes(STATE_LENGTH).toString("hex");
10416
+ return crypto3.randomBytes(STATE_LENGTH).toString("hex");
9168
10417
  }
9169
10418
  async function exchangeCodeForToken(code, state) {
9170
10419
  const response = await fetch(`${API_URL}/web/auth/cli-token-exchange`, {
@@ -9180,7 +10429,7 @@ async function exchangeCodeForToken(code, state) {
9180
10429
  return { token: data.token, expiresAt: data.expiresAt };
9181
10430
  }
9182
10431
  function openBrowser(url) {
9183
- const os3 = platform();
10432
+ const os3 = platform2();
9184
10433
  let command;
9185
10434
  let args;
9186
10435
  switch (os3) {
@@ -9197,11 +10446,11 @@ function openBrowser(url) {
9197
10446
  args = [url];
9198
10447
  }
9199
10448
  const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
9200
- spawn2(command, args, options).unref();
10449
+ spawn4(command, args, options).unref();
9201
10450
  }
9202
10451
  function startCallbackServer(port, expectedState) {
9203
10452
  return new Promise((resolve2, reject) => {
9204
- const server = http.createServer(async (req, res) => {
10453
+ const server = http2.createServer(async (req, res) => {
9205
10454
  if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
9206
10455
  res.writeHead(404);
9207
10456
  res.end("Not Found");
@@ -9262,7 +10511,7 @@ function startCallbackServer(port, expectedState) {
9262
10511
  const timeout = setTimeout(() => {
9263
10512
  server.close();
9264
10513
  reject(new Error("Login timeout - no response received after 5 minutes"));
9265
- }, CALLBACK_TIMEOUT_MS);
10514
+ }, CALLBACK_TIMEOUT_MS2);
9266
10515
  server.on("close", () => {
9267
10516
  clearTimeout(timeout);
9268
10517
  });
@@ -9521,74 +10770,33 @@ ${loginUrl}
9521
10770
  throw error;
9522
10771
  }
9523
10772
  }
9524
- var CLI_LOGIN_PORT, FRONTEND_URL, API_URL, CALLBACK_TIMEOUT_MS, STATE_LENGTH;
10773
+ var CLI_LOGIN_PORT, FRONTEND_URL, API_URL, CALLBACK_TIMEOUT_MS2, STATE_LENGTH;
9525
10774
  var init_login = __esm({
9526
10775
  "src/commands/login.ts"() {
9527
10776
  "use strict";
9528
10777
  CLI_LOGIN_PORT = 8420;
9529
10778
  FRONTEND_URL = process.env.SUPATEST_FRONTEND_URL || "https://code.supatest.ai";
9530
10779
  API_URL = process.env.SUPATEST_API_URL || "https://code-api.supatest.ai";
9531
- CALLBACK_TIMEOUT_MS = 3e5;
10780
+ CALLBACK_TIMEOUT_MS2 = 3e5;
9532
10781
  STATE_LENGTH = 32;
9533
10782
  }
9534
10783
  });
9535
10784
 
9536
10785
  // src/utils/claude-max.ts
9537
- import { execSync as execSync5 } from "child_process";
9538
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
9539
- import { homedir as homedir6 } from "os";
9540
- import { join as join8 } from "path";
9541
- function isClaudeMaxAvailable() {
9542
- const platform2 = process.platform;
9543
- logger.debug("[claude-max] Checking Claude Code credentials", { platform: platform2 });
9544
- if (platform2 === "darwin") {
9545
- return checkMacOSKeychain();
9546
- } else {
9547
- return checkCredentialsFile();
9548
- }
9549
- }
9550
- function checkMacOSKeychain() {
10786
+ async function isClaudeMaxAvailable() {
10787
+ logger.debug("[claude-max] Checking Supatest OAuth credentials");
9551
10788
  try {
9552
- const credentialsJson = execSync5(
9553
- 'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
9554
- { encoding: "utf-8" }
9555
- ).trim();
9556
- if (!credentialsJson) {
9557
- logger.debug("[claude-max] No credentials found in macOS keychain");
9558
- return false;
10789
+ const secretStorage = getSecretStorage();
10790
+ const oauthService = new ClaudeOAuthService(secretStorage);
10791
+ const hasSupatestOAuth = await oauthService.isAuthenticated();
10792
+ if (hasSupatestOAuth) {
10793
+ logger.debug("[claude-max] Found Supatest OAuth credentials");
10794
+ return true;
9559
10795
  }
9560
- const credentials = JSON.parse(credentialsJson);
9561
- const hasOauth = !!credentials.claudeAiOauth?.accessToken;
9562
- logger.debug("[claude-max] macOS keychain credentials check", {
9563
- hasOauth,
9564
- hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
9565
- });
9566
- return hasOauth;
9567
- } catch (error) {
9568
- logger.debug("[claude-max] Error checking macOS keychain", {
9569
- error: error instanceof Error ? error.message : String(error)
9570
- });
10796
+ logger.debug("[claude-max] No Supatest OAuth credentials found");
9571
10797
  return false;
9572
- }
9573
- }
9574
- function checkCredentialsFile() {
9575
- try {
9576
- const credentialsPath = join8(homedir6(), ".claude", ".credentials.json");
9577
- if (!existsSync6(credentialsPath)) {
9578
- logger.debug("[claude-max] Credentials file not found", { path: credentialsPath });
9579
- return false;
9580
- }
9581
- const credentialsJson = readFileSync5(credentialsPath, "utf-8");
9582
- const credentials = JSON.parse(credentialsJson);
9583
- const hasOauth = !!credentials.claudeAiOauth?.accessToken;
9584
- logger.debug("[claude-max] Credentials file check", {
9585
- path: credentialsPath,
9586
- hasOauth,
9587
- hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
9588
- });
9589
- return hasOauth;
9590
10798
  } catch (error) {
9591
- logger.debug("[claude-max] Error checking credentials file", {
10799
+ logger.debug("[claude-max] Error checking Supatest OAuth storage", {
9592
10800
  error: error instanceof Error ? error.message : String(error)
9593
10801
  });
9594
10802
  return false;
@@ -9597,12 +10805,14 @@ function checkCredentialsFile() {
9597
10805
  var init_claude_max = __esm({
9598
10806
  "src/utils/claude-max.ts"() {
9599
10807
  "use strict";
10808
+ init_claude_oauth();
9600
10809
  init_logger();
10810
+ init_secret_storage();
9601
10811
  }
9602
10812
  });
9603
10813
 
9604
10814
  // src/utils/mcp-manager.ts
9605
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
10815
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
9606
10816
  import { homedir as homedir7 } from "os";
9607
10817
  import { join as join9 } from "path";
9608
10818
  function getGlobalMcpPath() {
@@ -9612,11 +10822,11 @@ function getProjectMcpPath(cwd) {
9612
10822
  return join9(cwd, ".supatest", "mcp.json");
9613
10823
  }
9614
10824
  function loadMcpConfigFromFile(mcpPath, scope) {
9615
- if (!existsSync7(mcpPath)) {
10825
+ if (!existsSync6(mcpPath)) {
9616
10826
  return {};
9617
10827
  }
9618
10828
  try {
9619
- const content = readFileSync6(mcpPath, "utf-8");
10829
+ const content = readFileSync5(mcpPath, "utf-8");
9620
10830
  const config2 = JSON.parse(content);
9621
10831
  if (!config2.mcpServers) {
9622
10832
  return {};
@@ -9650,7 +10860,7 @@ function loadMcpConfig(cwd) {
9650
10860
  }
9651
10861
  function saveMcpConfigToFile(mcpPath, servers) {
9652
10862
  const mcpDir = join9(mcpPath, "..");
9653
- if (!existsSync7(mcpDir)) {
10863
+ if (!existsSync6(mcpDir)) {
9654
10864
  mkdirSync3(mcpDir, { recursive: true });
9655
10865
  }
9656
10866
  const config2 = {
@@ -9834,15 +11044,15 @@ var init_mcp_manager = __esm({
9834
11044
  });
9835
11045
 
9836
11046
  // src/utils/settings-loader.ts
9837
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
11047
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
9838
11048
  import { join as join10 } from "path";
9839
11049
  function loadSupatestSettings(cwd) {
9840
11050
  const settingsPath = join10(cwd, ".supatest", "settings.json");
9841
- if (!existsSync8(settingsPath)) {
11051
+ if (!existsSync7(settingsPath)) {
9842
11052
  return {};
9843
11053
  }
9844
11054
  try {
9845
- const content = readFileSync7(settingsPath, "utf-8");
11055
+ const content = readFileSync6(settingsPath, "utf-8");
9846
11056
  return JSON.parse(content);
9847
11057
  } catch (error) {
9848
11058
  console.warn(
@@ -9856,7 +11066,7 @@ function saveSupatestSettings(cwd, settings) {
9856
11066
  const settingsDir = join10(cwd, ".supatest");
9857
11067
  const settingsPath = join10(settingsDir, "settings.json");
9858
11068
  try {
9859
- if (!existsSync8(settingsDir)) {
11069
+ if (!existsSync7(settingsDir)) {
9860
11070
  mkdirSync4(settingsDir, { recursive: true });
9861
11071
  }
9862
11072
  const existingSettings = loadSupatestSettings(cwd);
@@ -11035,18 +12245,37 @@ var init_TestSelector = __esm({
11035
12245
  apiClient,
11036
12246
  run,
11037
12247
  onSelect,
11038
- onCancel
12248
+ onCancel,
12249
+ assignments = []
11039
12250
  }) => {
11040
12251
  const [allTests, setAllTests] = useState7([]);
11041
12252
  const [selectedTests, setSelectedTests] = useState7(/* @__PURE__ */ new Set());
11042
12253
  const [cursorIndex, setCursorIndex] = useState7(0);
11043
12254
  const [isLoading, setIsLoading] = useState7(false);
12255
+ const [isLoadingAssignments, setIsLoadingAssignments] = useState7(true);
11044
12256
  const [hasMore, setHasMore] = useState7(true);
11045
12257
  const [totalTests, setTotalTests] = useState7(0);
11046
12258
  const [error, setError] = useState7(null);
12259
+ const [showAvailableOnly, setShowAvailableOnly] = useState7(true);
12260
+ const [groupByFile, setGroupByFile] = useState7(false);
12261
+ const assignedTestMap = new Map(assignments.map((a) => [a.testRunId, a]));
12262
+ const assignedTestIds = new Set(assignments.map((a) => a.testRunId));
11047
12263
  useEffect7(() => {
11048
12264
  loadMoreTests();
11049
12265
  }, []);
12266
+ useEffect7(() => {
12267
+ fetchAssignments();
12268
+ }, [run.id]);
12269
+ const fetchAssignments = async () => {
12270
+ setIsLoadingAssignments(true);
12271
+ try {
12272
+ const result = await apiClient.getRunAssignments(run.id);
12273
+ } catch (err) {
12274
+ console.error("Failed to load assignments:", err);
12275
+ } finally {
12276
+ setIsLoadingAssignments(false);
12277
+ }
12278
+ };
11050
12279
  const loadMoreTests = async () => {
11051
12280
  if (isLoading || !hasMore) {
11052
12281
  return;
@@ -11072,8 +12301,34 @@ var init_TestSelector = __esm({
11072
12301
  setIsLoading(false);
11073
12302
  }
11074
12303
  };
11075
- const totalItems = allTests.length + 1;
11076
- const isOnFixAll = cursorIndex === 0;
12304
+ const getFilteredTests = () => {
12305
+ if (showAvailableOnly) {
12306
+ return allTests.filter((t) => !assignedTestIds.has(t.id));
12307
+ }
12308
+ return allTests;
12309
+ };
12310
+ const filteredTests = getFilteredTests();
12311
+ const availableCount = allTests.filter((t) => !assignedTestIds.has(t.id)).length;
12312
+ const assignedCount = assignedTestIds.size;
12313
+ const getTestsByFile = () => {
12314
+ const groups = /* @__PURE__ */ new Map();
12315
+ for (const test of filteredTests) {
12316
+ const file = test.file;
12317
+ if (!groups.has(file)) {
12318
+ groups.set(file, []);
12319
+ }
12320
+ groups.get(file).push(test);
12321
+ }
12322
+ return Array.from(groups.entries()).map(([file, tests]) => ({
12323
+ file,
12324
+ tests,
12325
+ availableCount: tests.filter((t) => !assignedTestIds.has(t.id)).length,
12326
+ assignedCount: tests.filter((t) => assignedTestIds.has(t.id)).length
12327
+ }));
12328
+ };
12329
+ const fileGroups = getTestsByFile();
12330
+ const totalItems = groupByFile ? fileGroups.length + 1 : filteredTests.length + 1;
12331
+ const isOnFixNext10 = cursorIndex === 0;
11077
12332
  const toggleTest = (testId) => {
11078
12333
  setSelectedTests((prev) => {
11079
12334
  const next = new Set(prev);
@@ -11085,50 +12340,92 @@ var init_TestSelector = __esm({
11085
12340
  return next;
11086
12341
  });
11087
12342
  };
11088
- const selectAll = () => {
11089
- setSelectedTests(new Set(allTests.map((t) => t.id)));
11090
- };
11091
12343
  const selectNone = () => {
11092
12344
  setSelectedTests(/* @__PURE__ */ new Set());
11093
12345
  };
12346
+ const selectAvailable = (tests) => {
12347
+ const availableTests = tests.filter((t) => !assignedTestIds.has(t.id));
12348
+ setSelectedTests(new Set(availableTests.map((t) => t.id)));
12349
+ };
12350
+ const selectFileTests = (fileTests) => {
12351
+ const available = fileTests.filter((t) => !assignedTestIds.has(t.id));
12352
+ const newSelected = new Set(selectedTests);
12353
+ const allSelected = available.every((t) => newSelected.has(t.id));
12354
+ if (allSelected) {
12355
+ for (const test of available) {
12356
+ newSelected.delete(test.id);
12357
+ }
12358
+ } else {
12359
+ for (const test of available) {
12360
+ newSelected.add(test.id);
12361
+ }
12362
+ }
12363
+ setSelectedTests(newSelected);
12364
+ };
11094
12365
  useInput3((input, key) => {
11095
- if (allTests.length === 0 && !isLoading) {
12366
+ if (allTests.length === 0 && !isLoading && !isLoadingAssignments) {
11096
12367
  if (key.escape || input === "q") {
11097
12368
  onCancel();
11098
12369
  }
11099
12370
  return;
11100
12371
  }
12372
+ if (input === "f") {
12373
+ setGroupByFile((prev) => !prev);
12374
+ setCursorIndex(0);
12375
+ return;
12376
+ }
12377
+ if (input === "t") {
12378
+ setShowAvailableOnly((prev) => !prev);
12379
+ setCursorIndex(0);
12380
+ return;
12381
+ }
11101
12382
  if (key.upArrow) {
11102
12383
  setCursorIndex((prev) => prev > 0 ? prev - 1 : totalItems - 1);
11103
12384
  } else if (key.downArrow) {
11104
12385
  const newIndex = cursorIndex < totalItems - 1 ? cursorIndex + 1 : 0;
11105
12386
  setCursorIndex(newIndex);
11106
- if (newIndex >= allTests.length - 2 && hasMore && !isLoading) {
12387
+ if (!groupByFile && newIndex >= allTests.length - 2 && hasMore && !isLoading) {
11107
12388
  loadMoreTests();
11108
12389
  }
11109
12390
  } else if (input === " ") {
11110
- if (isOnFixAll) {
11111
- selectAll();
12391
+ if (isOnFixNext10) {
12392
+ const availableTests = getFilteredTests();
12393
+ const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
12394
+ selectAvailable(next10);
12395
+ } else if (groupByFile) {
12396
+ const fileGroups2 = getTestsByFile();
12397
+ const fileIndex = cursorIndex - 1;
12398
+ if (fileGroups2[fileIndex]) {
12399
+ selectFileTests(fileGroups2[fileIndex].tests);
12400
+ }
11112
12401
  } else {
11113
12402
  const testIndex = cursorIndex - 1;
11114
- if (allTests[testIndex]) {
11115
- toggleTest(allTests[testIndex].id);
12403
+ const filteredTests2 = getFilteredTests();
12404
+ if (filteredTests2[testIndex]) {
12405
+ const test = filteredTests2[testIndex];
12406
+ if (!assignedTestIds.has(test.id)) {
12407
+ toggleTest(test.id);
12408
+ }
11116
12409
  }
11117
12410
  }
11118
- } else if (input === "a") {
11119
- selectAll();
11120
12411
  } else if (input === "n") {
11121
12412
  selectNone();
12413
+ } else if (input === "s") {
12414
+ const availableTests = getFilteredTests();
12415
+ const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
12416
+ selectAvailable(next10);
11122
12417
  } else if (key.return) {
11123
- if (isOnFixAll) {
11124
- onSelect(allTests);
12418
+ const availableTests = getFilteredTests();
12419
+ if (isOnFixNext10) {
12420
+ const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
12421
+ onSelect(next10);
11125
12422
  } else if (selectedTests.size > 0) {
11126
- const testsToFix = allTests.filter((t) => selectedTests.has(t.id));
12423
+ const testsToFix = availableTests.filter((t) => selectedTests.has(t.id));
11127
12424
  onSelect(testsToFix);
11128
- } else {
12425
+ } else if (!groupByFile) {
11129
12426
  const testIndex = cursorIndex - 1;
11130
- if (allTests[testIndex]) {
11131
- onSelect([allTests[testIndex]]);
12427
+ if (availableTests[testIndex] && !assignedTestIds.has(availableTests[testIndex].id)) {
12428
+ onSelect([availableTests[testIndex]]);
11132
12429
  }
11133
12430
  }
11134
12431
  } else if (key.escape || input === "q") {
@@ -11145,44 +12442,66 @@ var init_TestSelector = __esm({
11145
12442
  return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "green" }, "No Failed Tests"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "All tests passed in this run. Nothing to fix!"), /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "ESC"), " to go back")));
11146
12443
  }
11147
12444
  const testStartIndex = Math.max(0, cursorIndex - 1);
11148
- const adjustedStart = isOnFixAll ? 0 : Math.max(0, testStartIndex - Math.floor(VISIBLE_ITEMS2 / 2));
11149
- const adjustedEnd = Math.min(allTests.length, adjustedStart + VISIBLE_ITEMS2);
11150
- const visibleTests = allTests.slice(adjustedStart, adjustedEnd);
12445
+ const adjustedStart = isOnFixNext10 ? 0 : Math.max(0, testStartIndex - Math.floor(VISIBLE_ITEMS2 / 2));
12446
+ const adjustedEnd = Math.min(filteredTests.length, adjustedStart + VISIBLE_ITEMS2);
12447
+ const visibleTests = filteredTests.slice(adjustedStart, adjustedEnd);
11151
12448
  const branch = run.git?.branch || "unknown";
11152
12449
  const commit = run.git?.commit?.slice(0, 7) || "";
11153
- return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "cyan" }, "Run: ", branch, commit && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " @ ", commit), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "red" }, allTests.length, " failed test", allTests.length !== 1 ? "s" : ""))), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
12450
+ return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "cyan" }, "Run: ", branch, commit && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " @ ", commit), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "red" }, allTests.length, " failed"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, availableCount, " avail"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, assignedCount, " working"))), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "[", showAvailableOnly ? "x" : " ", "] ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " avail only", " ", "[", groupByFile ? "x" : " ", "] ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files")), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
11154
12451
  Text16,
11155
12452
  {
11156
- backgroundColor: isOnFixAll ? theme.text.accent : void 0,
11157
- bold: isOnFixAll,
11158
- color: isOnFixAll ? "black" : theme.text.primary
12453
+ backgroundColor: isOnFixNext10 ? theme.text.accent : void 0,
12454
+ bold: isOnFixNext10,
12455
+ color: isOnFixNext10 ? "black" : theme.text.primary
11159
12456
  },
11160
- isOnFixAll ? "\u25B6 " : " ",
11161
- "[Fix All ",
11162
- allTests.length,
11163
- " Failed Test",
11164
- allTests.length !== 1 ? "s" : "",
12457
+ isOnFixNext10 ? "\u25B6 " : " ",
12458
+ "[Fix Next 10 Available Test",
12459
+ Math.min(10, filteredTests.length) !== 1 ? "s" : "",
11165
12460
  "]"
11166
- )), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), visibleTests.map((test, index) => {
11167
- const actualIndex = adjustedStart + index;
11168
- const itemIndex = actualIndex + 1;
11169
- const isSelected = itemIndex === cursorIndex;
11170
- const isChecked = selectedTests.has(test.id);
11171
- const checkbox = isChecked ? "[x]" : "[ ]";
11172
- const file = test.file.split("/").pop() || test.file;
11173
- const line = test.location?.line || "";
11174
- const title = test.title;
11175
- const indicator = isSelected ? "\u25B6 " : " ";
11176
- const bgColor = isSelected ? theme.text.accent : void 0;
11177
- return /* @__PURE__ */ React21.createElement(Box18, { key: test.id, marginBottom: 0 }, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: isChecked ? "green" : isSelected ? "black" : theme.text.dim }, checkbox), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, file, line && /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, ":", line), /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, " - "), title));
11178
- })), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, allTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", allTests.length, " failed tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "a"), " all \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), isLoading && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, "Loading more tests..."))));
12461
+ )), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), groupByFile ? (
12462
+ // File Grouping Mode
12463
+ fileGroups.map((group, index) => {
12464
+ const itemIndex = index + 1;
12465
+ const isSelected = itemIndex === cursorIndex;
12466
+ const bgColor = isSelected ? theme.text.accent : void 0;
12467
+ const indicator = isSelected ? "\u25B6 " : " ";
12468
+ const fileName = group.file.split("/").pop() || group.file;
12469
+ const allSelected = group.availableCount > 0 && group.tests.filter((t) => !assignedTestIds.has(t.id)).every((t) => selectedTests.has(t.id));
12470
+ const someSelected = group.tests.filter((t) => !assignedTestIds.has(t.id)).some((t) => selectedTests.has(t.id));
12471
+ return /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", key: group.file, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: allSelected ? "green" : someSelected ? "yellow" : isSelected ? "black" : theme.text.dim }, "[", allSelected ? "\u2713" : someSelected ? "\u2297" : " ", "]"), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, fileName), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, group.availableCount, " available"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, group.assignedCount, " working"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, group.tests.length, " total")), isSelected && group.tests.length <= 5 && /* @__PURE__ */ React21.createElement(Box18, { marginLeft: 2 }, group.tests.map((test) => {
12472
+ const line = test.location?.line || "";
12473
+ const title = test.title;
12474
+ const isAssigned = assignedTestIds.has(test.id);
12475
+ return /* @__PURE__ */ React21.createElement(Box18, { key: test.id }, /* @__PURE__ */ React21.createElement(Text16, { color: isAssigned ? theme.text.dim : theme.text.primary }, isAssigned ? "\u{1F504} " : " ", line && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, line, ": "), title));
12476
+ })));
12477
+ })
12478
+ ) : (
12479
+ // Individual Tests Mode
12480
+ visibleTests.map((test, index) => {
12481
+ const actualIndex = adjustedStart + index;
12482
+ const itemIndex = actualIndex + 1;
12483
+ const isSelected = itemIndex === cursorIndex;
12484
+ const isChecked = selectedTests.has(test.id);
12485
+ const isAssigned = assignedTestIds.has(test.id);
12486
+ const assignment = assignedTestMap.get(test.id);
12487
+ const checkbox = isChecked ? "[x]" : "[ ]";
12488
+ const file = test.file.split("/").pop() || test.file;
12489
+ const line = test.location?.line || "";
12490
+ const title = test.title;
12491
+ const indicator = isSelected ? "\u25B6 " : " ";
12492
+ const bgColor = isSelected ? theme.text.accent : void 0;
12493
+ const assignee = assignment?.assignedTo || "";
12494
+ const displayAssignee = assignee.startsWith("cli:") ? assignee.slice(4) : assignee;
12495
+ return /* @__PURE__ */ React21.createElement(Box18, { key: test.id, marginBottom: 0 }, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: isAssigned ? theme.text.dim : isChecked ? "green" : isSelected ? "black" : theme.text.dim }, isAssigned ? "\u{1F504}" : checkbox), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, file, line && /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, ":", line), isAssigned && /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, displayAssignee)), /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, " - "), title));
12496
+ })
12497
+ )), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, !groupByFile && filteredTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", filteredTests.length, " ", showAvailableOnly ? "available" : "", " tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), groupByFile && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", fileGroups.length, " file", fileGroups.length !== 1 ? "s" : "", " \u2022 ", filteredTests.length, " total test", filteredTests.length !== 1 ? "s" : "")), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "s"), " next 10 \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " toggle filter \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), (isLoading || isLoadingAssignments) && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, isLoading ? "Loading more tests..." : "Loading assignments..."))));
11179
12498
  };
11180
12499
  }
11181
12500
  });
11182
12501
 
11183
12502
  // src/ui/components/FixFlow.tsx
11184
12503
  import { Box as Box19, Text as Text17, useInput as useInput4 } from "ink";
11185
- import React22, { useState as useState8 } from "react";
12504
+ import React22, { useEffect as useEffect8, useState as useState8 } from "react";
11186
12505
  var FixFlow;
11187
12506
  var init_FixFlow = __esm({
11188
12507
  "src/ui/components/FixFlow.tsx"() {
@@ -11201,6 +12520,8 @@ var init_FixFlow = __esm({
11201
12520
  const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
11202
12521
  const [selectedRun, setSelectedRun] = useState8(null);
11203
12522
  const [selectedTests, setSelectedTests] = useState8([]);
12523
+ const [assignments, setAssignments] = useState8([]);
12524
+ const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
11204
12525
  const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
11205
12526
  const [loadError, setLoadError] = useState8(null);
11206
12527
  useInput4((input, key) => {
@@ -11210,7 +12531,7 @@ var init_FixFlow = __esm({
11210
12531
  setStep("select-run");
11211
12532
  } else if (step === "select-run") {
11212
12533
  onCancel();
11213
- } else if (step === "loading-details" && loadError) {
12534
+ } else if ((step === "loading-details" || step === "claiming-tests" || step === "error") && loadError) {
11214
12535
  setLoadError(null);
11215
12536
  setStep("select-tests");
11216
12537
  }
@@ -11219,13 +12540,47 @@ var init_FixFlow = __esm({
11219
12540
  const handleRunSelect = (run) => {
11220
12541
  setSelectedRun(run);
11221
12542
  setStep("select-tests");
12543
+ fetchAssignments(run.id);
12544
+ };
12545
+ const fetchAssignments = async (runId) => {
12546
+ try {
12547
+ const result = await apiClient.getRunAssignments(runId);
12548
+ setAssignments(result.assignments);
12549
+ } catch (err) {
12550
+ console.error("Failed to load assignments:", err);
12551
+ }
11222
12552
  };
11223
12553
  const handleTestsSelect = async (tests) => {
11224
12554
  setSelectedTests(tests);
11225
- setStep("loading-details");
11226
- setLoadingProgress({ current: 0, total: tests.length });
12555
+ setStep("claiming-tests");
11227
12556
  setLoadError(null);
11228
12557
  try {
12558
+ if (selectedRun) {
12559
+ const claimResult = await apiClient.assignTests({
12560
+ runId: selectedRun.id,
12561
+ testRunIds: tests.map((t) => t.id)
12562
+ });
12563
+ if (claimResult.conflicts.length > 0) {
12564
+ const conflictList = claimResult.conflicts.slice(0, 5).map((c) => `${c.file}: ${c.title} (claimed by ${c.currentAssignee})`).join("\n\u2022 ");
12565
+ const moreCount = claimResult.conflicts.length - 5;
12566
+ setLoadError(
12567
+ `${claimResult.conflicts.length} test${claimResult.conflicts.length > 1 ? "s were" : " was"} already claimed by others:
12568
+ \u2022 ${conflictList}${moreCount > 0 ? `
12569
+ \u2022 ... and ${moreCount} more` : ""}
12570
+
12571
+ Please select different tests.`
12572
+ );
12573
+ setStep("error");
12574
+ return;
12575
+ }
12576
+ const assignmentMap = /* @__PURE__ */ new Map();
12577
+ claimResult.assigned.forEach((assignment) => {
12578
+ assignmentMap.set(assignment.testRunId, assignment.id);
12579
+ });
12580
+ setAssignmentIds(assignmentMap);
12581
+ }
12582
+ setStep("loading-details");
12583
+ setLoadingProgress({ current: 0, total: tests.length });
11229
12584
  const testDetails = [];
11230
12585
  const batchSize = 5;
11231
12586
  for (let i = 0; i < tests.length; i += batchSize) {
@@ -11241,7 +12596,25 @@ var init_FixFlow = __esm({
11241
12596
  setStep("fixing");
11242
12597
  onStartFix(prompt, tests);
11243
12598
  } catch (err) {
11244
- setLoadError(err instanceof Error ? err.message : String(err));
12599
+ const errorMessage = err instanceof Error ? err.message : String(err);
12600
+ if (errorMessage.includes("network") || errorMessage.includes("fetch")) {
12601
+ setLoadError(`Network error: Unable to connect to the server.
12602
+
12603
+ Please check your internet connection and try again.
12604
+
12605
+ Error: ${errorMessage}`);
12606
+ } else if (errorMessage.includes("auth") || errorMessage.includes("unauthorized")) {
12607
+ setLoadError(`Authentication error: Please run 'supatest auth' to log in.
12608
+
12609
+ Error: ${errorMessage}`);
12610
+ } else {
12611
+ setLoadError(`Failed to load test details:
12612
+
12613
+ ${errorMessage}
12614
+
12615
+ Press ESC to go back and try again.`);
12616
+ }
12617
+ setStep("error");
11245
12618
  }
11246
12619
  };
11247
12620
  const handleRunCancel = () => {
@@ -11249,8 +12622,47 @@ var init_FixFlow = __esm({
11249
12622
  };
11250
12623
  const handleTestCancel = () => {
11251
12624
  setSelectedRun(null);
12625
+ setAssignments([]);
11252
12626
  setStep("select-run");
11253
12627
  };
12628
+ const markAssignmentsComplete = async (fixSessionId) => {
12629
+ if (assignmentIds.size === 0) {
12630
+ return;
12631
+ }
12632
+ try {
12633
+ await Promise.all(
12634
+ Array.from(assignmentIds.values()).map(
12635
+ (assignmentId) => apiClient.completeAssignment({
12636
+ assignmentId,
12637
+ status: "completed",
12638
+ fixSessionId
12639
+ })
12640
+ )
12641
+ );
12642
+ } catch (err) {
12643
+ console.error("Failed to mark assignments as complete:", err);
12644
+ }
12645
+ };
12646
+ const releaseAssignments = async () => {
12647
+ if (assignmentIds.size === 0) {
12648
+ return;
12649
+ }
12650
+ try {
12651
+ await Promise.all(
12652
+ Array.from(assignmentIds.values()).map(
12653
+ (assignmentId) => apiClient.releaseAssignment(assignmentId)
12654
+ )
12655
+ );
12656
+ setAssignmentIds(/* @__PURE__ */ new Map());
12657
+ } catch (err) {
12658
+ console.error("Failed to release assignments:", err);
12659
+ }
12660
+ };
12661
+ useEffect8(() => {
12662
+ if (step === "complete") {
12663
+ markAssignmentsComplete();
12664
+ }
12665
+ }, [step]);
11254
12666
  switch (step) {
11255
12667
  case "select-run":
11256
12668
  return /* @__PURE__ */ React22.createElement(
@@ -11270,20 +12682,25 @@ var init_FixFlow = __esm({
11270
12682
  TestSelector,
11271
12683
  {
11272
12684
  apiClient,
12685
+ assignments,
11273
12686
  onCancel: handleTestCancel,
11274
12687
  onSelect: handleTestsSelect,
11275
12688
  run: selectedRun
11276
12689
  }
11277
12690
  );
12691
+ case "claiming-tests":
12692
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Claiming Tests..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Assigning ", selectedTests.length, " test", selectedTests.length !== 1 ? "s" : "", " to you...")));
11278
12693
  case "loading-details":
11279
12694
  if (loadError) {
11280
12695
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "red" }, "Error Loading Test Details"), /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, loadError), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
11281
12696
  }
11282
12697
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Loading Test Details..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Fetching error messages, stack traces, and execution steps...")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: "yellow" }, loadingProgress.current, " / ", loadingProgress.total, " tests loaded")));
12698
+ case "error":
12699
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "red" }, "Error"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, loadError)), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
11283
12700
  case "fixing":
11284
12701
  return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Fixing ", selectedTests.length, " Test", selectedTests.length !== 1 ? "s" : "", "..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, selectedTests.map((test, index) => /* @__PURE__ */ React22.createElement(Box19, { key: test.id }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, index + 1, ". ", test.file.split("/").pop(), ":", test.location?.line || "", " - ", test.title)))), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "The agent will now analyze and fix each test...")));
11285
12702
  case "complete":
11286
- return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "Fix Complete"), /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press any key to continue..."));
12703
+ return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "Tests Marked as Complete"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "\u2713 ", selectedTests.length, " test", selectedTests.length !== 1 ? "s have" : " has", " been marked as fixed")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press any key to continue...")));
11287
12704
  default:
11288
12705
  return null;
11289
12706
  }
@@ -11291,91 +12708,144 @@ var init_FixFlow = __esm({
11291
12708
  }
11292
12709
  });
11293
12710
 
11294
- // src/ui/utils/file-completion.ts
11295
- import fs4 from "fs";
12711
+ // src/ui/utils/file-search.ts
11296
12712
  import path4 from "path";
11297
- function shouldIgnore(name, isDir) {
11298
- if (name.startsWith(".")) return true;
11299
- if (isDir) return IGNORED_DIRS.has(name);
11300
- const ext = path4.extname(name).toLowerCase();
11301
- return IGNORED_EXTENSIONS.has(ext);
11302
- }
11303
- function getFiles(rootDir = process.cwd(), maxDepth = 3) {
11304
- const files = [];
11305
- function scan(dir, depth) {
11306
- if (depth > maxDepth) return;
11307
- try {
11308
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
11309
- for (const entry of entries) {
11310
- if (shouldIgnore(entry.name, entry.isDirectory())) continue;
11311
- const fullPath = path4.join(dir, entry.name);
11312
- const relativePath = path4.relative(rootDir, fullPath);
11313
- if (entry.isDirectory()) {
11314
- scan(fullPath, depth + 1);
11315
- } else {
11316
- files.push(relativePath);
11317
- }
11318
- }
11319
- } catch (error) {
12713
+ import { glob } from "glob";
12714
+ function fuzzyMatch(text, query2) {
12715
+ const textLower = text.toLowerCase();
12716
+ const queryLower = query2.toLowerCase();
12717
+ if (!queryLower) return 1;
12718
+ let queryIdx = 0;
12719
+ let score = 0;
12720
+ let consecutiveMatches = 0;
12721
+ for (let i = 0; i < textLower.length && queryIdx < queryLower.length; i++) {
12722
+ if (textLower[i] === queryLower[queryIdx]) {
12723
+ queryIdx++;
12724
+ score += 1 + consecutiveMatches * 0.5;
12725
+ consecutiveMatches++;
12726
+ } else {
12727
+ consecutiveMatches = 0;
11320
12728
  }
11321
12729
  }
11322
- scan(rootDir, 0);
11323
- return files;
12730
+ if (queryIdx < queryLower.length) {
12731
+ return 0;
12732
+ }
12733
+ const segments = textLower.split(path4.sep);
12734
+ for (const segment of segments) {
12735
+ if (segment.startsWith(queryLower[0])) {
12736
+ score += 0.5;
12737
+ }
12738
+ }
12739
+ if (textLower === queryLower) {
12740
+ score += 2;
12741
+ }
12742
+ return score;
12743
+ }
12744
+ async function searchFiles(query2, options = {}) {
12745
+ const { signal, maxResults = 20, maxDepth = 5, cwd = process.cwd() } = options;
12746
+ const pattern = "**/*";
12747
+ const files = await glob(pattern, {
12748
+ cwd,
12749
+ dot: false,
12750
+ // Don't include dotfiles by default
12751
+ signal,
12752
+ nodir: true,
12753
+ maxDepth,
12754
+ absolute: false
12755
+ });
12756
+ const safeFiles = files || [];
12757
+ if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
12758
+ if (!query2) {
12759
+ return safeFiles.slice(0, maxResults);
12760
+ }
12761
+ const results = [];
12762
+ for (const file of safeFiles) {
12763
+ const score = fuzzyMatch(file, query2);
12764
+ if (score > 0) {
12765
+ results.push({ file, score });
12766
+ }
12767
+ }
12768
+ results.sort((a, b) => b.score - a.score);
12769
+ return results.slice(0, maxResults).map((r) => r.file);
11324
12770
  }
11325
- var IGNORED_DIRS, IGNORED_EXTENSIONS;
11326
- var init_file_completion = __esm({
11327
- "src/ui/utils/file-completion.ts"() {
12771
+ var init_file_search = __esm({
12772
+ "src/ui/utils/file-search.ts"() {
11328
12773
  "use strict";
11329
- IGNORED_DIRS = /* @__PURE__ */ new Set([
11330
- "node_modules",
11331
- ".git",
11332
- ".turbo",
11333
- "dist",
11334
- "build",
11335
- "coverage",
11336
- ".next",
11337
- ".cache"
11338
- ]);
11339
- IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
11340
- ".png",
11341
- ".jpg",
11342
- ".jpeg",
11343
- ".gif",
11344
- ".ico",
11345
- ".svg",
11346
- ".woff",
11347
- ".woff2",
11348
- ".ttf",
11349
- ".eot",
11350
- ".mp4",
11351
- ".webm",
11352
- ".mp3",
11353
- ".wav",
11354
- ".zip",
11355
- ".tar",
11356
- ".gz",
11357
- ".7z",
11358
- ".rar",
11359
- ".pdf",
11360
- ".doc",
11361
- ".docx",
11362
- ".xls",
11363
- ".xlsx",
11364
- ".exe",
11365
- ".dll",
11366
- ".so",
11367
- ".dylib",
11368
- ".bin",
11369
- ".map",
11370
- ".lock",
11371
- ".tsbuildinfo"
11372
- ]);
12774
+ }
12775
+ });
12776
+
12777
+ // src/ui/hooks/useFileCompletion.ts
12778
+ import { useEffect as useEffect9, useRef as useRef6, useState as useState9 } from "react";
12779
+ function useFileCompletion(enabled, query2, cwd, maxResults = 20) {
12780
+ const [suggestions, setSuggestions] = useState9([]);
12781
+ const [isLoading, setIsLoading] = useState9(false);
12782
+ const abortControllerRef = useRef6(null);
12783
+ const debounceTimerRef = useRef6(null);
12784
+ const loadingTimerRef = useRef6(null);
12785
+ useEffect9(() => {
12786
+ if (abortControllerRef.current) {
12787
+ abortControllerRef.current.abort();
12788
+ }
12789
+ if (debounceTimerRef.current) {
12790
+ clearTimeout(debounceTimerRef.current);
12791
+ }
12792
+ if (loadingTimerRef.current) {
12793
+ clearTimeout(loadingTimerRef.current);
12794
+ }
12795
+ if (!enabled || !query2 || query2.length === 0) {
12796
+ setSuggestions([]);
12797
+ setIsLoading(false);
12798
+ return;
12799
+ }
12800
+ loadingTimerRef.current = setTimeout(() => {
12801
+ setIsLoading(true);
12802
+ }, 200);
12803
+ debounceTimerRef.current = setTimeout(async () => {
12804
+ const controller = new AbortController();
12805
+ abortControllerRef.current = controller;
12806
+ try {
12807
+ const results = await searchFiles(query2, {
12808
+ signal: controller.signal,
12809
+ maxResults,
12810
+ cwd
12811
+ });
12812
+ if (!controller.signal.aborted) {
12813
+ setSuggestions(Array.isArray(results) ? results : []);
12814
+ }
12815
+ } catch (error) {
12816
+ if (error instanceof Error && error.name !== "AbortError") {
12817
+ console.error("File search failed:", error);
12818
+ }
12819
+ setSuggestions([]);
12820
+ } finally {
12821
+ if (loadingTimerRef.current) {
12822
+ clearTimeout(loadingTimerRef.current);
12823
+ }
12824
+ setIsLoading(false);
12825
+ }
12826
+ }, 150);
12827
+ return () => {
12828
+ abortControllerRef.current?.abort();
12829
+ if (debounceTimerRef.current) {
12830
+ clearTimeout(debounceTimerRef.current);
12831
+ }
12832
+ if (loadingTimerRef.current) {
12833
+ clearTimeout(loadingTimerRef.current);
12834
+ }
12835
+ };
12836
+ }, [enabled, query2, cwd, maxResults]);
12837
+ return { suggestions, isLoading };
12838
+ }
12839
+ var init_useFileCompletion = __esm({
12840
+ "src/ui/hooks/useFileCompletion.ts"() {
12841
+ "use strict";
12842
+ init_file_search();
11373
12843
  }
11374
12844
  });
11375
12845
 
11376
12846
  // src/ui/components/ModelSelector.tsx
11377
12847
  import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
11378
- import React23, { useState as useState9 } from "react";
12848
+ import React23, { useState as useState10 } from "react";
11379
12849
  function getAvailableModels(isClaudeMax) {
11380
12850
  if (isClaudeMax) {
11381
12851
  return AVAILABLE_MODELS.map((m) => ({
@@ -11410,7 +12880,7 @@ var init_ModelSelector = __esm({
11410
12880
  }) => {
11411
12881
  const models = getAvailableModels(isClaudeMax);
11412
12882
  const currentIndex = models.findIndex((m) => m.id === currentModel);
11413
- const [selectedIndex, setSelectedIndex] = useState9(currentIndex >= 0 ? currentIndex : 0);
12883
+ const [selectedIndex, setSelectedIndex] = useState10(currentIndex >= 0 ? currentIndex : 0);
11414
12884
  useInput5((input, key) => {
11415
12885
  if (key.upArrow) {
11416
12886
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
@@ -11452,7 +12922,7 @@ var init_ModelSelector = __esm({
11452
12922
  import path5 from "path";
11453
12923
  import chalk4 from "chalk";
11454
12924
  import { Box as Box21, Text as Text19 } from "ink";
11455
- import React24, { forwardRef, memo as memo3, useEffect as useEffect9, useImperativeHandle, useState as useState10 } from "react";
12925
+ import React24, { forwardRef, memo as memo3, useEffect as useEffect10, useImperativeHandle, useState as useState11 } from "react";
11456
12926
  var InputPromptInner, InputPrompt;
11457
12927
  var init_InputPrompt = __esm({
11458
12928
  "src/ui/components/InputPrompt.tsx"() {
@@ -11460,13 +12930,13 @@ var init_InputPrompt = __esm({
11460
12930
  init_shared_es();
11461
12931
  init_command_discovery();
11462
12932
  init_SessionContext();
12933
+ init_useFileCompletion();
11463
12934
  init_useKeypress();
11464
- init_file_completion();
11465
12935
  init_theme();
11466
12936
  init_ModelSelector();
11467
12937
  InputPromptInner = forwardRef(({
11468
12938
  onSubmit,
11469
- placeholder = "Enter your task (press Enter to submit, Shift+Enter for new line)...",
12939
+ placeholder = "Enter your task... (Ctrl+W=del word, Alt+\u2190/\u2192=word nav, Ctrl+U/K=clear)",
11470
12940
  disabled = false,
11471
12941
  onHelpToggle,
11472
12942
  cwd,
@@ -11477,13 +12947,38 @@ var init_InputPrompt = __esm({
11477
12947
  }, ref) => {
11478
12948
  const { agentMode, selectedModel, setSelectedModel, isAgentRunning } = useSession();
11479
12949
  const { usageStats } = useUsageStats();
11480
- const [value, setValue] = useState10("");
11481
- const [cursorOffset, setCursorOffset] = useState10(0);
11482
- const [allFiles, setAllFiles] = useState10([]);
11483
- const [suggestions, setSuggestions] = useState10([]);
11484
- const [activeSuggestion, setActiveSuggestion] = useState10(0);
11485
- const [showSuggestions, setShowSuggestions] = useState10(false);
11486
- const [mentionStartIndex, setMentionStartIndex] = useState10(-1);
12950
+ const [value, setValue] = useState11("");
12951
+ const [cursorPos, setCursorPos] = useState11([0, 0]);
12952
+ const getCursorOffset = () => {
12953
+ const lines2 = value.split("\n");
12954
+ let offset = 0;
12955
+ for (let i = 0; i < cursorPos[0]; i++) {
12956
+ offset += lines2[i]?.length ?? 0;
12957
+ offset += 1;
12958
+ }
12959
+ offset += cursorPos[1];
12960
+ return offset;
12961
+ };
12962
+ const offsetToCursorPos = (offset, text) => {
12963
+ const lines2 = text.split("\n");
12964
+ let currentOffset = 0;
12965
+ for (let i = 0; i < lines2.length; i++) {
12966
+ const lineLength = lines2[i]?.length ?? 0;
12967
+ if (currentOffset + lineLength >= offset) {
12968
+ return [i, offset - currentOffset];
12969
+ }
12970
+ currentOffset += lineLength + 1;
12971
+ }
12972
+ return [lines2.length - 1, lines2[lines2.length - 1]?.length ?? 0];
12973
+ };
12974
+ const setCursorFromOffset = (offset, text) => {
12975
+ setCursorPos(offsetToCursorPos(offset, text ?? value));
12976
+ };
12977
+ const [suggestions, setSuggestions] = useState11([]);
12978
+ const [activeSuggestion, setActiveSuggestion] = useState11(0);
12979
+ const [showSuggestions, setShowSuggestions] = useState11(false);
12980
+ const [mentionStartIndex, setMentionStartIndex] = useState11(null);
12981
+ const [mentionQuery, setMentionQuery] = useState11("");
11487
12982
  const BUILTIN_SLASH_COMMANDS = [
11488
12983
  { name: "/help", desc: "Show help" },
11489
12984
  { name: "/resume", desc: "Resume session" },
@@ -11499,9 +12994,9 @@ var init_InputPrompt = __esm({
11499
12994
  { name: "/logout", desc: "Log out" },
11500
12995
  { name: "/exit", desc: "Exit CLI" }
11501
12996
  ];
11502
- const [customCommands, setCustomCommands] = useState10([]);
11503
- const [isSlashCommand, setIsSlashCommand] = useState10(false);
11504
- useEffect9(() => {
12997
+ const [customCommands, setCustomCommands] = useState11([]);
12998
+ const [isSlashCommand, setIsSlashCommand] = useState11(false);
12999
+ useEffect10(() => {
11505
13000
  try {
11506
13001
  const projectDir = cwd || process.cwd();
11507
13002
  const discovered = discoverCommands(projectDir);
@@ -11517,29 +13012,55 @@ var init_InputPrompt = __esm({
11517
13012
  useImperativeHandle(ref, () => ({
11518
13013
  clear: () => {
11519
13014
  setValue("");
11520
- setCursorOffset(0);
13015
+ setCursorPos([0, 0]);
11521
13016
  setShowSuggestions(false);
11522
13017
  onInputChange?.("");
11523
13018
  }
11524
13019
  }));
11525
- useEffect9(() => {
11526
- setTimeout(() => {
11527
- try {
11528
- const files = getFiles();
11529
- setAllFiles(files);
11530
- } catch (e) {
11531
- }
11532
- }, 100);
11533
- }, []);
13020
+ const { suggestions: fileSuggestions, isLoading: isLoadingFiles } = useFileCompletion(
13021
+ showSuggestions && !isSlashCommand && mentionQuery.length > 0,
13022
+ mentionQuery,
13023
+ cwd || process.cwd(),
13024
+ 20
13025
+ // maxResults
13026
+ );
13027
+ useEffect10(() => {
13028
+ if (!isSlashCommand && showSuggestions) {
13029
+ setSuggestions(fileSuggestions);
13030
+ }
13031
+ }, [fileSuggestions, isSlashCommand, showSuggestions]);
11534
13032
  const updateValue = (newValue, newCursor) => {
11535
13033
  setValue(newValue);
11536
- setCursorOffset(newCursor);
13034
+ if (typeof newCursor === "number") {
13035
+ setCursorFromOffset(newCursor, newValue);
13036
+ } else {
13037
+ setCursorPos(newCursor);
13038
+ }
13039
+ const newOffset = typeof newCursor === "number" ? newCursor : (() => {
13040
+ const lines2 = newValue.split("\n");
13041
+ let offset = 0;
13042
+ for (let i = 0; i < newCursor[0]; i++) {
13043
+ offset += lines2[i]?.length ?? 0;
13044
+ offset += 1;
13045
+ }
13046
+ offset += newCursor[1];
13047
+ return offset;
13048
+ })();
11537
13049
  checkSuggestions(newValue, newCursor);
11538
13050
  onInputChange?.(newValue);
11539
13051
  };
11540
13052
  const checkSuggestions = (text, cursor) => {
11541
- if (text.startsWith("/") && cursor <= text.length && !text.includes(" ", 1)) {
11542
- const query2 = text.slice(1);
13053
+ let cursorRow;
13054
+ let cursorCol2;
13055
+ if (typeof cursor === "number") {
13056
+ [cursorRow, cursorCol2] = offsetToCursorPos(cursor, text);
13057
+ } else {
13058
+ [cursorRow, cursorCol2] = cursor;
13059
+ }
13060
+ const lines2 = text.split("\n");
13061
+ const currentLine = lines2[cursorRow] || "";
13062
+ if (cursorRow === 0 && text.startsWith("/") && cursorCol2 <= currentLine.length && !currentLine.includes(" ", 1)) {
13063
+ const query2 = currentLine.slice(1);
11543
13064
  const builtinMatches = BUILTIN_SLASH_COMMANDS.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
11544
13065
  const customMatches = customCommands.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
11545
13066
  const matches = [];
@@ -11561,25 +13082,26 @@ var init_InputPrompt = __esm({
11561
13082
  }
11562
13083
  }
11563
13084
  setIsSlashCommand(false);
11564
- const textBeforeCursor = text.slice(0, cursor);
11565
- const lastAt = textBeforeCursor.lastIndexOf("@");
11566
- if (lastAt !== -1) {
11567
- const isValidStart = lastAt === 0 || /\s/.test(textBeforeCursor[lastAt - 1]);
11568
- if (isValidStart) {
11569
- const query2 = textBeforeCursor.slice(lastAt + 1);
11570
- if (!/\s/.test(query2)) {
11571
- const matches = allFiles.filter((f) => f.toLowerCase().includes(query2.toLowerCase())).slice(0, 5);
11572
- if (matches.length > 0) {
11573
- setSuggestions(matches);
11574
- setShowSuggestions(true);
11575
- setActiveSuggestion(0);
11576
- setMentionStartIndex(lastAt);
11577
- return;
11578
- }
11579
- }
13085
+ let foundMention = false;
13086
+ for (let i = cursorCol2 - 1; i >= 0; i--) {
13087
+ const char = currentLine[i];
13088
+ if (char === " ") {
13089
+ break;
13090
+ }
13091
+ if (char === "@") {
13092
+ const query2 = currentLine.slice(i + 1, cursorCol2);
13093
+ setMentionQuery(query2);
13094
+ setShowSuggestions(true);
13095
+ setActiveSuggestion(0);
13096
+ setMentionStartIndex({ row: cursorRow, col: i });
13097
+ foundMention = true;
13098
+ break;
11580
13099
  }
11581
13100
  }
11582
- setShowSuggestions(false);
13101
+ if (!foundMention) {
13102
+ setMentionQuery("");
13103
+ setShowSuggestions(false);
13104
+ }
11583
13105
  };
11584
13106
  const completeSuggestion = (submit = false) => {
11585
13107
  if (!showSuggestions || suggestions.length === 0) return;
@@ -11588,27 +13110,35 @@ var init_InputPrompt = __esm({
11588
13110
  if (submit) {
11589
13111
  onSubmit(selectedCmd);
11590
13112
  setValue("");
11591
- setCursorOffset(0);
13113
+ setCursorPos([0, 0]);
11592
13114
  setShowSuggestions(false);
11593
13115
  onInputChange?.("");
11594
13116
  return;
11595
13117
  }
11596
- updateValue(selectedCmd, selectedCmd.length);
13118
+ updateValue(selectedCmd, [0, selectedCmd.length]);
11597
13119
  setShowSuggestions(false);
11598
13120
  return;
11599
13121
  }
11600
13122
  const selectedFile = suggestions[activeSuggestion];
11601
- const textBeforeMention = value.slice(0, mentionStartIndex);
11602
- const textAfterCursor = value.slice(cursorOffset);
11603
- const newValue = textBeforeMention + "@" + selectedFile + " " + textAfterCursor;
11604
- const newCursor = mentionStartIndex + 1 + selectedFile.length + 1;
11605
- updateValue(newValue, newCursor);
13123
+ const mentionStart = mentionStartIndex;
13124
+ const [currentRow, currentCol] = cursorPos;
13125
+ if (!mentionStart) return;
13126
+ const lines2 = value.split("\n");
13127
+ const beforeMention = lines2[mentionStart.row].slice(0, mentionStart.col);
13128
+ const afterCursor = lines2[mentionStart.row].slice(currentCol);
13129
+ const newLine = beforeMention + "@" + selectedFile + " " + afterCursor;
13130
+ const newLines = [...lines2];
13131
+ newLines[mentionStart.row] = newLine;
13132
+ const newCursorCol = mentionStart.col + 1 + selectedFile.length + 1;
13133
+ const newCursorPos = [mentionStart.row, newCursorCol];
13134
+ updateValue(newLines.join("\n"), newCursorPos);
11606
13135
  setShowSuggestions(false);
11607
13136
  };
11608
13137
  useKeypress(
11609
13138
  (key) => {
11610
13139
  if (disabled) return;
11611
13140
  const input = key.sequence;
13141
+ const cursorOffset = getCursorOffset();
11612
13142
  if (input.length > 1 && (input.includes("/") || input.includes("\\"))) {
11613
13143
  let cleanPath = input.trim();
11614
13144
  if (cleanPath.startsWith('"') && cleanPath.endsWith('"') || cleanPath.startsWith("'") && cleanPath.endsWith("'")) {
@@ -11632,7 +13162,7 @@ var init_InputPrompt = __esm({
11632
13162
  return;
11633
13163
  }
11634
13164
  }
11635
- if (key.shift && key.name === "m" && !isAgentRunning) {
13165
+ if ((key.ctrl || key.meta) && key.shift && key.name === "m" && !isAgentRunning) {
11636
13166
  setSelectedModel(getNextModel(selectedModel, isClaudeMax));
11637
13167
  return;
11638
13168
  }
@@ -11681,7 +13211,7 @@ var init_InputPrompt = __esm({
11681
13211
  if (value.trim()) {
11682
13212
  onSubmit(value.trim());
11683
13213
  setValue("");
11684
- setCursorOffset(0);
13214
+ setCursorPos([0, 0]);
11685
13215
  setShowSuggestions(false);
11686
13216
  onInputChange?.("");
11687
13217
  }
@@ -11692,11 +13222,55 @@ var init_InputPrompt = __esm({
11692
13222
  updateValue(newValue, cursorOffset - 1);
11693
13223
  }
11694
13224
  } else if (key.name === "left") {
11695
- setCursorOffset(Math.max(0, cursorOffset - 1));
13225
+ if (key.meta && !key.shift && !key.ctrl) {
13226
+ const textBefore = value.slice(0, cursorOffset);
13227
+ const match = textBefore.match(/\S+\s*$/);
13228
+ const wordStart = match ? textBefore.length - match[0].length : 0;
13229
+ setCursorFromOffset(wordStart);
13230
+ } else {
13231
+ setCursorFromOffset(Math.max(0, cursorOffset - 1));
13232
+ }
11696
13233
  } else if (key.name === "right") {
11697
- setCursorOffset(Math.min(value.length, cursorOffset + 1));
11698
- } else if (key.ctrl && input === "u") {
11699
- updateValue("", 0);
13234
+ if (key.meta && !key.shift && !key.ctrl) {
13235
+ const textAfter = value.slice(cursorOffset);
13236
+ const match = textAfter.match(/^\s*\S+/);
13237
+ const wordEnd = match ? cursorOffset + match[0].length : value.length;
13238
+ setCursorFromOffset(wordEnd);
13239
+ } else {
13240
+ setCursorFromOffset(Math.min(value.length, cursorOffset + 1));
13241
+ }
13242
+ } else if (key.ctrl && key.name === "u") {
13243
+ const newValue = value.slice(cursorOffset);
13244
+ updateValue(newValue, 0);
13245
+ } else if (key.ctrl && key.name === "k") {
13246
+ const newValue = value.slice(0, cursorOffset);
13247
+ updateValue(newValue, cursorOffset);
13248
+ } else if (key.ctrl && key.name === "w") {
13249
+ const textBefore = value.slice(0, cursorOffset);
13250
+ const match = textBefore.match(/\S+\s*$/);
13251
+ const wordStart = match ? textBefore.length - match[0].length : 0;
13252
+ const newValue = value.slice(0, wordStart) + value.slice(cursorOffset);
13253
+ updateValue(newValue, wordStart);
13254
+ } else if (key.meta && key.name === "backspace") {
13255
+ const textBefore = value.slice(0, cursorOffset);
13256
+ const match = textBefore.match(/\S+\s*$/);
13257
+ const wordStart = match ? textBefore.length - match[0].length : 0;
13258
+ const newValue = value.slice(0, wordStart) + value.slice(cursorOffset);
13259
+ updateValue(newValue, wordStart);
13260
+ } else if (key.meta && key.name === "d") {
13261
+ const textAfter = value.slice(cursorOffset);
13262
+ const match = textAfter.match(/^\s*\S+/);
13263
+ const wordEnd = match ? cursorOffset + match[0].length : value.length;
13264
+ const newValue = value.slice(0, cursorOffset) + value.slice(wordEnd);
13265
+ updateValue(newValue, cursorOffset);
13266
+ } else if (key.ctrl && key.name === "a") {
13267
+ setCursorFromOffset(0);
13268
+ } else if (key.ctrl && key.name === "e") {
13269
+ setCursorFromOffset(value.length);
13270
+ } else if (key.name === "home") {
13271
+ setCursorFromOffset(0);
13272
+ } else if (key.name === "end") {
13273
+ setCursorFromOffset(value.length);
11700
13274
  } else if (key.name === "tab" && !showSuggestions) {
11701
13275
  } else if (key.paste) {
11702
13276
  const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
@@ -11710,18 +13284,7 @@ var init_InputPrompt = __esm({
11710
13284
  );
11711
13285
  const lines = value ? value.split("\n") : [];
11712
13286
  const hasContent = value.trim().length > 0;
11713
- let cursorLine = 0;
11714
- let cursorCol = cursorOffset;
11715
- let charCount = 0;
11716
- for (let i = 0; i < lines.length; i++) {
11717
- const lineLength = lines[i].length;
11718
- if (charCount + lineLength >= cursorOffset) {
11719
- cursorLine = i;
11720
- cursorCol = cursorOffset - charCount;
11721
- break;
11722
- }
11723
- charCount += lineLength + 1;
11724
- }
13287
+ const [cursorLine, cursorCol] = cursorPos;
11725
13288
  return /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React24.createElement(
11726
13289
  Box21,
11727
13290
  {
@@ -11731,13 +13294,13 @@ var init_InputPrompt = __esm({
11731
13294
  marginBottom: 0,
11732
13295
  paddingX: 1
11733
13296
  },
11734
- suggestions.map((item, idx) => {
13297
+ isLoadingFiles && !isSlashCommand ? /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "Searching files...") : suggestions.length > 0 ? suggestions.map((item, idx) => {
11735
13298
  const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
11736
13299
  if (isSeparator) {
11737
13300
  return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, key: item }, " ", item);
11738
13301
  }
11739
13302
  return /* @__PURE__ */ React24.createElement(Text19, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
11740
- })
13303
+ }) : /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "No matches")
11741
13304
  ), /* @__PURE__ */ React24.createElement(
11742
13305
  Box21,
11743
13306
  {
@@ -11758,7 +13321,7 @@ var init_InputPrompt = __esm({
11758
13321
  }
11759
13322
  return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.primary, key: idx }, line);
11760
13323
  })), !hasContent && disabled && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
11761
- ), /* @__PURE__ */ React24.createElement(Box21, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box21, { gap: 2 }, /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, ") (shift+m)"))), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
13324
+ ), /* @__PURE__ */ React24.createElement(Box21, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box21, { gap: 2 }, /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, ") (ctrl+shift+m)"))), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
11762
13325
  });
11763
13326
  InputPromptInner.displayName = "InputPromptInner";
11764
13327
  InputPrompt = memo3(InputPromptInner);
@@ -11768,19 +13331,19 @@ var init_InputPrompt = __esm({
11768
13331
 
11769
13332
  // src/ui/components/McpAddDialog.tsx
11770
13333
  import { Box as Box22, Text as Text20, useInput as useInput6 } from "ink";
11771
- import React25, { useState as useState11 } from "react";
13334
+ import React25, { useState as useState12 } from "react";
11772
13335
  var McpAddDialog;
11773
13336
  var init_McpAddDialog = __esm({
11774
13337
  "src/ui/components/McpAddDialog.tsx"() {
11775
13338
  "use strict";
11776
13339
  init_theme();
11777
13340
  McpAddDialog = ({ onConfirm, onCancel }) => {
11778
- const [mode, setMode] = useState11("name" /* Name */);
11779
- const [serverName, setServerName] = useState11("");
11780
- const [command, setCommand] = useState11("");
11781
- const [args, setArgs] = useState11("");
11782
- const [description, setDescription] = useState11("");
11783
- const [scope, setScope] = useState11("project");
13341
+ const [mode, setMode] = useState12("name" /* Name */);
13342
+ const [serverName, setServerName] = useState12("");
13343
+ const [command, setCommand] = useState12("");
13344
+ const [args, setArgs] = useState12("");
13345
+ const [description, setDescription] = useState12("");
13346
+ const [scope, setScope] = useState12("project");
11784
13347
  useInput6((input, key) => {
11785
13348
  if (key.escape) {
11786
13349
  onCancel();
@@ -11875,7 +13438,7 @@ var init_McpAddDialog = __esm({
11875
13438
 
11876
13439
  // src/ui/components/McpServerSelector.tsx
11877
13440
  import { Box as Box23, Text as Text21, useInput as useInput7 } from "ink";
11878
- import React26, { useState as useState12 } from "react";
13441
+ import React26, { useState as useState13 } from "react";
11879
13442
  var McpServerSelector;
11880
13443
  var init_McpServerSelector = __esm({
11881
13444
  "src/ui/components/McpServerSelector.tsx"() {
@@ -11887,7 +13450,7 @@ var init_McpServerSelector = __esm({
11887
13450
  onSelect,
11888
13451
  onCancel
11889
13452
  }) => {
11890
- const [selectedIndex, setSelectedIndex] = useState12(0);
13453
+ const [selectedIndex, setSelectedIndex] = useState13(0);
11891
13454
  useInput7((input, key) => {
11892
13455
  if (key.escape) {
11893
13456
  onCancel();
@@ -11946,7 +13509,7 @@ var init_McpServerSelector = __esm({
11946
13509
  // src/ui/components/McpServersDisplay.tsx
11947
13510
  import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
11948
13511
  import Spinner3 from "ink-spinner";
11949
- import React27, { useEffect as useEffect10, useState as useState13 } from "react";
13512
+ import React27, { useEffect as useEffect11, useState as useState14 } from "react";
11950
13513
  var McpServersDisplay;
11951
13514
  var init_McpServersDisplay = __esm({
11952
13515
  "src/ui/components/McpServersDisplay.tsx"() {
@@ -11960,9 +13523,9 @@ var init_McpServersDisplay = __esm({
11960
13523
  onTest,
11961
13524
  cwd
11962
13525
  }) => {
11963
- const [servers, setServers] = useState13([]);
11964
- const [isTestingAll, setIsTestingAll] = useState13(false);
11965
- useEffect10(() => {
13526
+ const [servers, setServers] = useState14([]);
13527
+ const [isTestingAll, setIsTestingAll] = useState14(false);
13528
+ useEffect11(() => {
11966
13529
  const projectDir = cwd || process.cwd();
11967
13530
  const loadedServers = loadMcpConfig(projectDir);
11968
13531
  const serversArray = Object.values(loadedServers).map((server) => ({
@@ -12032,7 +13595,7 @@ var init_McpServersDisplay = __esm({
12032
13595
 
12033
13596
  // src/ui/components/ProviderSelector.tsx
12034
13597
  import { Box as Box25, Text as Text23, useInput as useInput9 } from "ink";
12035
- import React28, { useState as useState14 } from "react";
13598
+ import React28, { useState as useState15 } from "react";
12036
13599
  var PROVIDERS, ProviderSelector;
12037
13600
  var init_ProviderSelector = __esm({
12038
13601
  "src/ui/components/ProviderSelector.tsx"() {
@@ -12047,7 +13610,7 @@ var init_ProviderSelector = __esm({
12047
13610
  {
12048
13611
  id: "claude-max",
12049
13612
  name: "Claude Max",
12050
- description: "Direct Claude Max subscription (requires Claude Code login)"
13613
+ description: "Direct Claude Max subscription (requires OAuth login)"
12051
13614
  }
12052
13615
  ];
12053
13616
  ProviderSelector = ({
@@ -12062,7 +13625,7 @@ var init_ProviderSelector = __esm({
12062
13625
  const currentIndex = availableProviders.findIndex(
12063
13626
  (p) => p.id === currentProvider
12064
13627
  );
12065
- const [selectedIndex, setSelectedIndex] = useState14(
13628
+ const [selectedIndex, setSelectedIndex] = useState15(
12066
13629
  currentIndex >= 0 ? currentIndex : 0
12067
13630
  );
12068
13631
  useInput9((input, key) => {
@@ -12102,9 +13665,125 @@ var init_ProviderSelector = __esm({
12102
13665
  }
12103
13666
  });
12104
13667
 
12105
- // src/ui/components/SessionSelector.tsx
13668
+ // src/ui/components/QuestionSelector.tsx
12106
13669
  import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
12107
- import React29, { useEffect as useEffect11, useState as useState15 } from "react";
13670
+ import React29, { useState as useState16 } from "react";
13671
+ var QuestionSelector;
13672
+ var init_QuestionSelector = __esm({
13673
+ "src/ui/components/QuestionSelector.tsx"() {
13674
+ "use strict";
13675
+ init_theme();
13676
+ QuestionSelector = ({
13677
+ question,
13678
+ options,
13679
+ multiSelect = false,
13680
+ allowCustomAnswer = true,
13681
+ // Default to true - always show "Other" option
13682
+ onSubmit,
13683
+ onCancel
13684
+ }) => {
13685
+ const hasOptions = options && options.length > 0;
13686
+ const allOptions = hasOptions && allowCustomAnswer ? [...options, { label: "Other", description: "Provide a custom response" }] : options;
13687
+ const [selectedIndex, setSelectedIndex] = useState16(0);
13688
+ const [selectedItems, setSelectedItems] = useState16(/* @__PURE__ */ new Set());
13689
+ const [customInputMode, setCustomInputMode] = useState16(!hasOptions);
13690
+ const [customAnswer, setCustomAnswer] = useState16("");
13691
+ useInput10((input, key) => {
13692
+ if (customInputMode) {
13693
+ if (key.return) {
13694
+ if (customAnswer.trim()) {
13695
+ onSubmit([customAnswer.trim()]);
13696
+ }
13697
+ } else if (key.escape) {
13698
+ if (!hasOptions) {
13699
+ onCancel();
13700
+ } else {
13701
+ setCustomInputMode(false);
13702
+ setCustomAnswer("");
13703
+ }
13704
+ } else if (key.backspace || key.delete) {
13705
+ setCustomAnswer((prev) => prev.slice(0, -1));
13706
+ } else if (input && !key.ctrl && !key.meta) {
13707
+ setCustomAnswer((prev) => prev + input);
13708
+ }
13709
+ return;
13710
+ }
13711
+ if (key.upArrow) {
13712
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : allOptions.length - 1);
13713
+ } else if (key.downArrow) {
13714
+ setSelectedIndex((prev) => prev < allOptions.length - 1 ? prev + 1 : 0);
13715
+ } else if (key.return) {
13716
+ const selectedOption = allOptions[selectedIndex];
13717
+ if (selectedOption.label === "Other" && allowCustomAnswer) {
13718
+ setCustomInputMode(true);
13719
+ return;
13720
+ }
13721
+ if (multiSelect) {
13722
+ setSelectedItems((prev) => {
13723
+ const newSet = new Set(prev);
13724
+ if (newSet.has(selectedIndex)) {
13725
+ newSet.delete(selectedIndex);
13726
+ } else {
13727
+ newSet.add(selectedIndex);
13728
+ }
13729
+ return newSet;
13730
+ });
13731
+ } else {
13732
+ onSubmit([selectedOption.label]);
13733
+ }
13734
+ } else if (input === " " && multiSelect) {
13735
+ setSelectedItems((prev) => {
13736
+ const newSet = new Set(prev);
13737
+ if (newSet.has(selectedIndex)) {
13738
+ newSet.delete(selectedIndex);
13739
+ } else {
13740
+ newSet.add(selectedIndex);
13741
+ }
13742
+ return newSet;
13743
+ });
13744
+ } else if (key.escape || input === "q") {
13745
+ onCancel();
13746
+ } else if (input === "s" && multiSelect && selectedItems.size > 0) {
13747
+ const answers = Array.from(selectedItems).sort((a, b) => a - b).map((idx) => allOptions[idx].label);
13748
+ onSubmit(answers);
13749
+ } else if ((input === "o" || input === "O") && allowCustomAnswer) {
13750
+ setCustomInputMode(true);
13751
+ }
13752
+ });
13753
+ if (customInputMode) {
13754
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, question)), /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, hasOptions ? "Enter your custom answer:" : "Type your answer:")), /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.primary }, ">", " ", customAnswer, /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: theme.text.accent }, " "))), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " submit \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " ", hasOptions ? "back to options" : "cancel")));
13755
+ }
13756
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: theme.text.accent }, question)), multiSelect && /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "(Select multiple options with ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Space"), ", submit with ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "S"), ")")), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginBottom: 1 }, allOptions.map((option, index) => {
13757
+ const isSelected = index === selectedIndex;
13758
+ const isChosen = selectedItems.has(index);
13759
+ const indicator = isSelected ? "\u25B6 " : " ";
13760
+ const checkbox = multiSelect ? isChosen ? "[\u2713] " : "[ ] " : "";
13761
+ return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", gap: 0, key: `${option.label}-${index}` }, /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(
13762
+ Text24,
13763
+ {
13764
+ backgroundColor: isSelected ? theme.text.accent : void 0,
13765
+ bold: isSelected,
13766
+ color: isSelected ? "black" : theme.text.primary
13767
+ },
13768
+ indicator,
13769
+ checkbox,
13770
+ option.label
13771
+ )), /* @__PURE__ */ React29.createElement(Box26, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React29.createElement(
13772
+ Text24,
13773
+ {
13774
+ color: isSelected ? theme.text.primary : theme.text.dim,
13775
+ dimColor: !isSelected
13776
+ },
13777
+ option.description
13778
+ )));
13779
+ })), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", multiSelect ? /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "S"), " submit \u2022", " ") : /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " select \u2022", " "), /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " cancel")));
13780
+ };
13781
+ }
13782
+ });
13783
+
13784
+ // src/ui/components/SessionSelector.tsx
13785
+ import { Box as Box27, Text as Text25, useInput as useInput11 } from "ink";
13786
+ import React30, { useEffect as useEffect12, useState as useState17 } from "react";
12108
13787
  function getSessionPrefix(authMethod) {
12109
13788
  return authMethod === "api-key" ? "[Team]" : "[Me]";
12110
13789
  }
@@ -12119,13 +13798,13 @@ var init_SessionSelector = __esm({
12119
13798
  onSelect,
12120
13799
  onCancel
12121
13800
  }) => {
12122
- const [allSessions, setAllSessions] = useState15([]);
12123
- const [selectedIndex, setSelectedIndex] = useState15(0);
12124
- const [isLoading, setIsLoading] = useState15(false);
12125
- const [hasMore, setHasMore] = useState15(true);
12126
- const [totalSessions, setTotalSessions] = useState15(0);
12127
- const [error, setError] = useState15(null);
12128
- useEffect11(() => {
13801
+ const [allSessions, setAllSessions] = useState17([]);
13802
+ const [selectedIndex, setSelectedIndex] = useState17(0);
13803
+ const [isLoading, setIsLoading] = useState17(false);
13804
+ const [hasMore, setHasMore] = useState17(true);
13805
+ const [totalSessions, setTotalSessions] = useState17(0);
13806
+ const [error, setError] = useState17(null);
13807
+ useEffect12(() => {
12129
13808
  loadMoreSessions();
12130
13809
  }, []);
12131
13810
  const loadMoreSessions = async () => {
@@ -12151,7 +13830,7 @@ var init_SessionSelector = __esm({
12151
13830
  setIsLoading(false);
12152
13831
  }
12153
13832
  };
12154
- useInput10((input, key) => {
13833
+ useInput11((input, key) => {
12155
13834
  if (allSessions.length === 0) {
12156
13835
  if (key.escape || input === "q") {
12157
13836
  onCancel();
@@ -12175,13 +13854,13 @@ var init_SessionSelector = __esm({
12175
13854
  }
12176
13855
  });
12177
13856
  if (error) {
12178
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, error), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")));
13857
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, error), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
12179
13858
  }
12180
13859
  if (allSessions.length === 0 && isLoading) {
12181
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Fetching your sessions from the server"));
13860
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Fetching your sessions from the server"));
12182
13861
  }
12183
13862
  if (allSessions.length === 0 && !isLoading) {
12184
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "No previous sessions available. Start a new conversation!"), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")));
13863
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "No previous sessions available. Start a new session!"), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
12185
13864
  }
12186
13865
  const VISIBLE_ITEMS3 = 10;
12187
13866
  let startIndex;
@@ -12201,7 +13880,7 @@ var init_SessionSelector = __esm({
12201
13880
  const visibleSessions = allSessions.slice(startIndex, endIndex);
12202
13881
  const MAX_TITLE_WIDTH = 50;
12203
13882
  const PREFIX_WIDTH = 6;
12204
- return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column" }, visibleSessions.map((item, index) => {
13883
+ return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column" }, visibleSessions.map((item, index) => {
12205
13884
  const actualIndex = startIndex + index;
12206
13885
  const isSelected = actualIndex === selectedIndex;
12207
13886
  const title = item.session.title || "Untitled session";
@@ -12225,18 +13904,18 @@ var init_SessionSelector = __esm({
12225
13904
  const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
12226
13905
  const indicator = isSelected ? "\u25B6 " : " ";
12227
13906
  const bgColor = isSelected ? theme.text.accent : void 0;
12228
- return /* @__PURE__ */ React29.createElement(Box26, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
12229
- })), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan" }, "Loading more sessions..."))));
13907
+ return /* @__PURE__ */ React30.createElement(Box27, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React30.createElement(Text25, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
13908
+ })), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React30.createElement(Box27, null, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "cyan" }, "Loading more sessions..."))));
12230
13909
  };
12231
13910
  }
12232
13911
  });
12233
13912
 
12234
13913
  // src/ui/hooks/useModeToggle.ts
12235
- import { useEffect as useEffect12 } from "react";
13914
+ import { useEffect as useEffect13 } from "react";
12236
13915
  function useModeToggle() {
12237
13916
  const { subscribe, unsubscribe } = useKeypressContext();
12238
13917
  const { agentMode, setAgentMode, isAgentRunning } = useSession();
12239
- useEffect12(() => {
13918
+ useEffect13(() => {
12240
13919
  const handleKeypress = (key) => {
12241
13920
  if (key.name === "tab" && key.shift && !isAgentRunning) {
12242
13921
  const newMode = agentMode === "plan" ? "build" : "plan";
@@ -12257,13 +13936,13 @@ var init_useModeToggle = __esm({
12257
13936
  });
12258
13937
 
12259
13938
  // src/ui/hooks/useOverlayEscapeGuard.ts
12260
- import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef6 } from "react";
13939
+ import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef7 } from "react";
12261
13940
  var useOverlayEscapeGuard;
12262
13941
  var init_useOverlayEscapeGuard = __esm({
12263
13942
  "src/ui/hooks/useOverlayEscapeGuard.ts"() {
12264
13943
  "use strict";
12265
13944
  useOverlayEscapeGuard = ({ overlays, suppressionMs = 250 }) => {
12266
- const suppressUntilRef = useRef6(0);
13945
+ const suppressUntilRef = useRef7(0);
12267
13946
  const markOverlayClosed = useCallback4(() => {
12268
13947
  suppressUntilRef.current = Date.now() + suppressionMs;
12269
13948
  }, [suppressionMs]);
@@ -12275,11 +13954,11 @@ var init_useOverlayEscapeGuard = __esm({
12275
13954
  });
12276
13955
 
12277
13956
  // src/ui/App.tsx
12278
- import { execSync as execSync6 } from "child_process";
13957
+ import { execSync as execSync5 } from "child_process";
12279
13958
  import { homedir as homedir8 } from "os";
12280
- import { Box as Box27, Text as Text25, useApp as useApp2, useStdout as useStdout2 } from "ink";
13959
+ import { Box as Box28, Text as Text26, useApp as useApp2, useStdout as useStdout2 } from "ink";
12281
13960
  import Spinner4 from "ink-spinner";
12282
- import React30, { useCallback as useCallback5, useEffect as useEffect13, useRef as useRef7, useState as useState16 } from "react";
13961
+ import React31, { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef8, useState as useState18 } from "react";
12283
13962
  var getGitBranch2, getCurrentFolder2, AppContent, App;
12284
13963
  var init_App = __esm({
12285
13964
  "src/ui/App.tsx"() {
@@ -12306,6 +13985,7 @@ var init_App = __esm({
12306
13985
  init_MessageList();
12307
13986
  init_ModelSelector();
12308
13987
  init_ProviderSelector();
13988
+ init_QuestionSelector();
12309
13989
  init_SessionSelector();
12310
13990
  init_SessionContext();
12311
13991
  init_useKeypress();
@@ -12315,7 +13995,7 @@ var init_App = __esm({
12315
13995
  init_theme();
12316
13996
  getGitBranch2 = () => {
12317
13997
  try {
12318
- return execSync6("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
13998
+ return execSync5("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
12319
13999
  } catch {
12320
14000
  return "";
12321
14001
  }
@@ -12328,36 +14008,37 @@ var init_App = __esm({
12328
14008
  }
12329
14009
  return cwd;
12330
14010
  };
12331
- AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
14011
+ AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession, onQuestionAnswer }) => {
12332
14012
  const { exit } = useApp2();
12333
14013
  const { stdout } = useStdout2();
12334
- const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
14014
+ const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider, pendingQuestion, setPendingQuestion } = useSession();
12335
14015
  useModeToggle();
12336
- const [terminalWidth, setTerminalWidth] = useState16(process.stdout.columns || 80);
12337
- const [showInput, setShowInput] = useState16(true);
12338
- const [gitBranch] = useState16(() => getGitBranch2());
12339
- const [currentFolder] = useState16(() => getCurrentFolder2(config2.cwd));
12340
- const hasInputContentRef = useRef7(false);
12341
- const [exitWarning, setExitWarning] = useState16(null);
12342
- const inputPromptRef = useRef7(null);
12343
- const [showSessionSelector, setShowSessionSelector] = useState16(false);
12344
- const [showModelSelector, setShowModelSelector] = useState16(false);
12345
- const [showProviderSelector, setShowProviderSelector] = useState16(false);
12346
- const [showFeedbackDialog, setShowFeedbackDialog] = useState16(false);
12347
- const [isLoadingSession, setIsLoadingSession] = useState16(false);
12348
- const [showFixFlow, setShowFixFlow] = useState16(false);
12349
- const [fixRunId, setFixRunId] = useState16(void 0);
12350
- const [showMcpServers, setShowMcpServers] = useState16(false);
12351
- const [showMcpAdd, setShowMcpAdd] = useState16(false);
12352
- const [showMcpSelector, setShowMcpSelector] = useState16(false);
12353
- const [mcpSelectorAction, setMcpSelectorAction] = useState16(
14016
+ const [terminalWidth, setTerminalWidth] = useState18(process.stdout.columns || 80);
14017
+ const [showInput, setShowInput] = useState18(true);
14018
+ const [gitBranch] = useState18(() => getGitBranch2());
14019
+ const [currentFolder] = useState18(() => getCurrentFolder2(config2.cwd));
14020
+ const hasInputContentRef = useRef8(false);
14021
+ const [exitWarning, setExitWarning] = useState18(null);
14022
+ const inputPromptRef = useRef8(null);
14023
+ const [showSessionSelector, setShowSessionSelector] = useState18(false);
14024
+ const [showModelSelector, setShowModelSelector] = useState18(false);
14025
+ const [showProviderSelector, setShowProviderSelector] = useState18(false);
14026
+ const [claudeMaxAvailable, setClaudeMaxAvailable] = useState18(false);
14027
+ const [showFeedbackDialog, setShowFeedbackDialog] = useState18(false);
14028
+ const [isLoadingSession, setIsLoadingSession] = useState18(false);
14029
+ const [showFixFlow, setShowFixFlow] = useState18(false);
14030
+ const [fixRunId, setFixRunId] = useState18(void 0);
14031
+ const [showMcpServers, setShowMcpServers] = useState18(false);
14032
+ const [showMcpAdd, setShowMcpAdd] = useState18(false);
14033
+ const [showMcpSelector, setShowMcpSelector] = useState18(false);
14034
+ const [mcpSelectorAction, setMcpSelectorAction] = useState18(
12354
14035
  "remove"
12355
14036
  );
12356
- const [mcpServers, setMcpServers] = useState16([]);
12357
- const [authState, setAuthState] = useState16(
14037
+ const [mcpServers, setMcpServers] = useState18([]);
14038
+ const [authState, setAuthState] = useState18(
12358
14039
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
12359
14040
  );
12360
- const [showAuthDialog, setShowAuthDialog] = useState16(false);
14041
+ const [showAuthDialog, setShowAuthDialog] = useState18(false);
12361
14042
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
12362
14043
  overlays: [
12363
14044
  showSessionSelector,
@@ -12368,15 +14049,16 @@ var init_App = __esm({
12368
14049
  showFixFlow,
12369
14050
  showMcpServers,
12370
14051
  showMcpAdd,
12371
- showMcpSelector
14052
+ showMcpSelector,
14053
+ !!pendingQuestion
12372
14054
  ]
12373
14055
  });
12374
- useEffect13(() => {
14056
+ useEffect14(() => {
12375
14057
  if (!config2.supatestApiKey) {
12376
14058
  setShowAuthDialog(true);
12377
14059
  }
12378
14060
  }, [config2.supatestApiKey]);
12379
- useEffect13(() => {
14061
+ useEffect14(() => {
12380
14062
  if (sessionId) {
12381
14063
  setSessionId(sessionId);
12382
14064
  }
@@ -12391,6 +14073,7 @@ var init_App = __esm({
12391
14073
  type: "assistant",
12392
14074
  content: "Opening browser for authentication..."
12393
14075
  });
14076
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
12394
14077
  try {
12395
14078
  const result = await loginCommand();
12396
14079
  saveToken(result.token, result.expiresAt);
@@ -12485,6 +14168,7 @@ var init_App = __esm({
12485
14168
  return;
12486
14169
  }
12487
14170
  if (command === "/provider") {
14171
+ isClaudeMaxAvailable().then(setClaudeMaxAvailable);
12488
14172
  setShowProviderSelector(true);
12489
14173
  return;
12490
14174
  }
@@ -12537,6 +14221,7 @@ var init_App = __esm({
12537
14221
  type: "assistant",
12538
14222
  content: "Running setup..."
12539
14223
  });
14224
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
12540
14225
  try {
12541
14226
  const result = await setupCommand({ cwd: config2.cwd || process.cwd() });
12542
14227
  addMessage({
@@ -12630,9 +14315,45 @@ var init_App = __esm({
12630
14315
  markOverlayClosed();
12631
14316
  setShowModelSelector(false);
12632
14317
  };
12633
- const handleProviderSelect = (provider) => {
14318
+ const handleProviderSelect = async (provider) => {
12634
14319
  setShowProviderSelector(false);
12635
14320
  markOverlayClosed();
14321
+ if (provider === "claude-max") {
14322
+ const hasAuth = await isClaudeMaxAvailable();
14323
+ if (!hasAuth) {
14324
+ addMessage({
14325
+ type: "assistant",
14326
+ content: "Claude Max requires OAuth authentication. Starting authentication flow..."
14327
+ });
14328
+ try {
14329
+ const { ClaudeOAuthService: ClaudeOAuthService2 } = await Promise.resolve().then(() => (init_claude_oauth(), claude_oauth_exports));
14330
+ const { getSecretStorage: getSecretStorage2 } = await Promise.resolve().then(() => (init_secret_storage(), secret_storage_exports));
14331
+ const secretStorage = getSecretStorage2();
14332
+ const oauthService = new ClaudeOAuthService2(secretStorage);
14333
+ const result = await oauthService.authorize();
14334
+ if (!result.success) {
14335
+ addMessage({
14336
+ type: "error",
14337
+ content: `Authentication failed: ${result.error || "Unknown error"}`,
14338
+ errorType: "error"
14339
+ });
14340
+ return;
14341
+ }
14342
+ addMessage({
14343
+ type: "assistant",
14344
+ content: "\u2705 Successfully authenticated with Claude! Switching to Claude Max provider..."
14345
+ });
14346
+ setClaudeMaxAvailable(true);
14347
+ } catch (error) {
14348
+ addMessage({
14349
+ type: "error",
14350
+ content: `Authentication failed: ${error instanceof Error ? error.message : String(error)}`,
14351
+ errorType: "error"
14352
+ });
14353
+ return;
14354
+ }
14355
+ }
14356
+ }
12636
14357
  setLlmProvider(provider);
12637
14358
  saveSupatestSettings(config2.cwd || process.cwd(), { llmProvider: provider });
12638
14359
  addMessage({
@@ -12683,6 +14404,38 @@ var init_App = __esm({
12683
14404
  markOverlayClosed();
12684
14405
  setShowFeedbackDialog(false);
12685
14406
  };
14407
+ const handleQuestionSubmit = (answers) => {
14408
+ if (!pendingQuestion) return;
14409
+ const formattedAnswer = answers.length === 1 ? answers[0] : answers.join(", ");
14410
+ addMessage({
14411
+ type: "user",
14412
+ content: formattedAnswer
14413
+ });
14414
+ const answerRecord = {
14415
+ "answer": formattedAnswer
14416
+ };
14417
+ if (onQuestionAnswer) {
14418
+ const success = onQuestionAnswer(pendingQuestion.toolUseId, answerRecord);
14419
+ if (!success) {
14420
+ console.warn("[App] Failed to submit answer - no pending question found");
14421
+ }
14422
+ }
14423
+ setPendingQuestion(null);
14424
+ markOverlayClosed();
14425
+ };
14426
+ const handleQuestionCancel = () => {
14427
+ if (!pendingQuestion) return;
14428
+ if (onQuestionAnswer) {
14429
+ onQuestionAnswer(pendingQuestion.toolUseId, null);
14430
+ }
14431
+ const cancelMsg = "I'd prefer not to answer this question right now.";
14432
+ addMessage({
14433
+ type: "user",
14434
+ content: cancelMsg
14435
+ });
14436
+ setPendingQuestion(null);
14437
+ markOverlayClosed();
14438
+ };
12686
14439
  const handleFixFlowCancel = () => {
12687
14440
  markOverlayClosed();
12688
14441
  setShowFixFlow(false);
@@ -12822,8 +14575,8 @@ var init_App = __esm({
12822
14575
  markOverlayClosed();
12823
14576
  setShowMcpServers(true);
12824
14577
  };
12825
- const isInitialMount = useRef7(true);
12826
- useEffect13(() => {
14578
+ const isInitialMount = useRef8(true);
14579
+ useEffect14(() => {
12827
14580
  const handleResize = () => {
12828
14581
  setTerminalWidth(process.stdout.columns || 80);
12829
14582
  };
@@ -12832,7 +14585,7 @@ var init_App = __esm({
12832
14585
  process.stdout.off("resize", handleResize);
12833
14586
  };
12834
14587
  }, []);
12835
- useEffect13(() => {
14588
+ useEffect14(() => {
12836
14589
  if (isInitialMount.current) {
12837
14590
  isInitialMount.current = false;
12838
14591
  return;
@@ -12894,7 +14647,7 @@ var init_App = __esm({
12894
14647
  },
12895
14648
  { isActive: !isOverlayOpen }
12896
14649
  );
12897
- useEffect13(() => {
14650
+ useEffect14(() => {
12898
14651
  if (config2.task) {
12899
14652
  addMessage({
12900
14653
  type: "user",
@@ -12902,14 +14655,14 @@ var init_App = __esm({
12902
14655
  });
12903
14656
  }
12904
14657
  }, []);
12905
- return /* @__PURE__ */ React30.createElement(
12906
- Box27,
14658
+ return /* @__PURE__ */ React31.createElement(
14659
+ Box28,
12907
14660
  {
12908
14661
  flexDirection: "column",
12909
14662
  paddingX: 1
12910
14663
  },
12911
- /* @__PURE__ */ React30.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
12912
- showSessionSelector && apiClient && /* @__PURE__ */ React30.createElement(
14664
+ /* @__PURE__ */ React31.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
14665
+ showSessionSelector && apiClient && /* @__PURE__ */ React31.createElement(
12913
14666
  SessionSelector,
12914
14667
  {
12915
14668
  apiClient,
@@ -12917,8 +14670,8 @@ var init_App = __esm({
12917
14670
  onSelect: handleSessionSelect
12918
14671
  }
12919
14672
  ),
12920
- isLoadingSession && /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "row" }, /* @__PURE__ */ React30.createElement(Box27, { width: 2 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.accent }, /* @__PURE__ */ React30.createElement(Spinner4, { type: "dots" }))), /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Fetching queries and context")),
12921
- showModelSelector && /* @__PURE__ */ React30.createElement(
14673
+ isLoadingSession && /* @__PURE__ */ React31.createElement(Box28, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "row" }, /* @__PURE__ */ React31.createElement(Box28, { width: 2 }, /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.accent }, /* @__PURE__ */ React31.createElement(Spinner4, { type: "dots" }))), /* @__PURE__ */ React31.createElement(Text26, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React31.createElement(Text26, { color: theme.text.dim }, "Fetching queries and context")),
14674
+ showModelSelector && /* @__PURE__ */ React31.createElement(
12922
14675
  ModelSelector,
12923
14676
  {
12924
14677
  currentModel: selectedModel,
@@ -12927,29 +14680,29 @@ var init_App = __esm({
12927
14680
  onSelect: handleModelSelect
12928
14681
  }
12929
14682
  ),
12930
- showProviderSelector && /* @__PURE__ */ React30.createElement(
14683
+ showProviderSelector && /* @__PURE__ */ React31.createElement(
12931
14684
  ProviderSelector,
12932
14685
  {
12933
- claudeMaxAvailable: isClaudeMaxAvailable(),
14686
+ claudeMaxAvailable,
12934
14687
  currentProvider: llmProvider,
12935
14688
  onCancel: handleProviderSelectorCancel,
12936
14689
  onSelect: handleProviderSelect
12937
14690
  }
12938
14691
  ),
12939
- showAuthDialog && /* @__PURE__ */ React30.createElement(
14692
+ showAuthDialog && /* @__PURE__ */ React31.createElement(
12940
14693
  AuthDialog,
12941
14694
  {
12942
14695
  onLogin: handleLogin
12943
14696
  }
12944
14697
  ),
12945
- showFeedbackDialog && /* @__PURE__ */ React30.createElement(
14698
+ showFeedbackDialog && /* @__PURE__ */ React31.createElement(
12946
14699
  FeedbackDialog,
12947
14700
  {
12948
14701
  onCancel: handleFeedbackCancel,
12949
14702
  onSubmit: handleFeedbackSubmit
12950
14703
  }
12951
14704
  ),
12952
- showFixFlow && apiClient && /* @__PURE__ */ React30.createElement(
14705
+ showFixFlow && apiClient && /* @__PURE__ */ React31.createElement(
12953
14706
  FixFlow,
12954
14707
  {
12955
14708
  apiClient,
@@ -12959,7 +14712,7 @@ var init_App = __esm({
12959
14712
  onStartFix: handleFixStart
12960
14713
  }
12961
14714
  ),
12962
- showMcpServers && /* @__PURE__ */ React30.createElement(
14715
+ showMcpServers && /* @__PURE__ */ React31.createElement(
12963
14716
  McpServersDisplay,
12964
14717
  {
12965
14718
  cwd: config2.cwd,
@@ -12969,8 +14722,8 @@ var init_App = __esm({
12969
14722
  onTest: handleMcpTest
12970
14723
  }
12971
14724
  ),
12972
- showMcpAdd && /* @__PURE__ */ React30.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
12973
- showMcpSelector && /* @__PURE__ */ React30.createElement(
14725
+ showMcpAdd && /* @__PURE__ */ React31.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
14726
+ showMcpSelector && /* @__PURE__ */ React31.createElement(
12974
14727
  McpServerSelector,
12975
14728
  {
12976
14729
  action: mcpSelectorAction,
@@ -12979,7 +14732,17 @@ var init_App = __esm({
12979
14732
  servers: mcpServers
12980
14733
  }
12981
14734
  ),
12982
- /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React30.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !isLoadingSession && /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React30.createElement(
14735
+ pendingQuestion && /* @__PURE__ */ React31.createElement(
14736
+ QuestionSelector,
14737
+ {
14738
+ multiSelect: pendingQuestion.multiSelect,
14739
+ onCancel: handleQuestionCancel,
14740
+ onSubmit: handleQuestionSubmit,
14741
+ options: pendingQuestion.options || [],
14742
+ question: pendingQuestion.question
14743
+ }
14744
+ ),
14745
+ /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React31.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !pendingQuestion && !isLoadingSession && /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React31.createElement(Box28, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React31.createElement(
12983
14746
  InputPrompt,
12984
14747
  {
12985
14748
  currentFolder,
@@ -12995,13 +14758,13 @@ var init_App = __esm({
12995
14758
  );
12996
14759
  };
12997
14760
  App = (props) => {
12998
- return /* @__PURE__ */ React30.createElement(AppContent, { ...props });
14761
+ return /* @__PURE__ */ React31.createElement(AppContent, { ...props });
12999
14762
  };
13000
14763
  }
13001
14764
  });
13002
14765
 
13003
14766
  // src/ui/hooks/useBracketedPaste.ts
13004
- import { useEffect as useEffect14 } from "react";
14767
+ import { useEffect as useEffect15 } from "react";
13005
14768
  var ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, useBracketedPaste;
13006
14769
  var init_useBracketedPaste = __esm({
13007
14770
  "src/ui/hooks/useBracketedPaste.ts"() {
@@ -13010,7 +14773,7 @@ var init_useBracketedPaste = __esm({
13010
14773
  ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
13011
14774
  DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
13012
14775
  useBracketedPaste = () => {
13013
- useEffect14(() => {
14776
+ useEffect15(() => {
13014
14777
  writeToStdout(ENABLE_BRACKETED_PASTE);
13015
14778
  const cleanup = () => {
13016
14779
  writeToStdout(DISABLE_BRACKETED_PASTE);
@@ -13031,7 +14794,7 @@ __export(interactive_exports, {
13031
14794
  runInteractive: () => runInteractive
13032
14795
  });
13033
14796
  import { render as render2 } from "ink";
13034
- import React31, { useEffect as useEffect15, useRef as useRef8 } from "react";
14797
+ import React32, { useEffect as useEffect16, useRef as useRef9 } from "react";
13035
14798
  function getToolDescription2(toolName, input) {
13036
14799
  switch (toolName) {
13037
14800
  case "Read":
@@ -13164,7 +14927,7 @@ async function runInteractive(config2) {
13164
14927
  webUrl = session.webUrl;
13165
14928
  }
13166
14929
  const { unmount, waitUntilExit } = render2(
13167
- /* @__PURE__ */ React31.createElement(
14930
+ /* @__PURE__ */ React32.createElement(
13168
14931
  InteractiveApp,
13169
14932
  {
13170
14933
  apiClient,
@@ -13229,7 +14992,7 @@ var init_interactive = __esm({
13229
14992
  init_settings_loader();
13230
14993
  init_stdio();
13231
14994
  init_version();
13232
- AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete }) => {
14995
+ AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete, onAgentCreated }) => {
13233
14996
  const {
13234
14997
  addMessage,
13235
14998
  updateLastMessage,
@@ -13243,17 +15006,18 @@ var init_interactive = __esm({
13243
15006
  setAgentMode,
13244
15007
  planFilePath,
13245
15008
  selectedModel,
13246
- llmProvider
15009
+ llmProvider,
15010
+ setPendingQuestion
13247
15011
  } = useSession();
13248
15012
  const { setUsageStats } = useUsageStats();
13249
- const agentRef = useRef8(null);
13250
- useEffect15(() => {
15013
+ const agentRef = useRef9(null);
15014
+ useEffect16(() => {
13251
15015
  if (shouldInterruptAgent && agentRef.current) {
13252
15016
  agentRef.current.abort();
13253
15017
  setShouldInterruptAgent(false);
13254
15018
  }
13255
15019
  }, [shouldInterruptAgent, setShouldInterruptAgent]);
13256
- useEffect15(() => {
15020
+ useEffect16(() => {
13257
15021
  let isMounted = true;
13258
15022
  const runAgent2 = async () => {
13259
15023
  setIsAgentRunning(true);
@@ -13295,6 +15059,16 @@ var init_interactive = __esm({
13295
15059
  },
13296
15060
  onTurnComplete: () => {
13297
15061
  if (isMounted) onTurnComplete?.();
15062
+ },
15063
+ onAskUserQuestion: (toolId, question, options, multiSelect) => {
15064
+ if (isMounted) {
15065
+ setPendingQuestion({
15066
+ toolUseId: toolId,
15067
+ question,
15068
+ options,
15069
+ multiSelect
15070
+ });
15071
+ }
13298
15072
  }
13299
15073
  },
13300
15074
  apiClient,
@@ -13303,9 +15077,18 @@ var init_interactive = __esm({
13303
15077
  );
13304
15078
  let oauthToken;
13305
15079
  if (llmProvider === "claude-max") {
13306
- if (isClaudeMaxAvailable()) {
13307
- oauthToken = "use-claude-max";
13308
- logger.info("Using Claude Max subscription for LLM calls");
15080
+ if (await isClaudeMaxAvailable()) {
15081
+ const { ClaudeOAuthService: ClaudeOAuthService2 } = await Promise.resolve().then(() => (init_claude_oauth(), claude_oauth_exports));
15082
+ const { getSecretStorage: getSecretStorage2 } = await Promise.resolve().then(() => (init_secret_storage(), secret_storage_exports));
15083
+ const secretStorage = getSecretStorage2();
15084
+ const oauthService = new ClaudeOAuthService2(secretStorage);
15085
+ const token = await oauthService.getAccessToken();
15086
+ if (token) {
15087
+ oauthToken = token;
15088
+ logger.info("Using Claude Max subscription for LLM calls");
15089
+ } else {
15090
+ logger.warn("Claude Max OAuth token not available. Falling back to Supatest Managed.");
15091
+ }
13309
15092
  } else {
13310
15093
  logger.warn("Claude Max selected but not available. Falling back to Supatest Managed.");
13311
15094
  }
@@ -13321,6 +15104,9 @@ var init_interactive = __esm({
13321
15104
  };
13322
15105
  const agent2 = new CoreAgent(presenter, messageBridge);
13323
15106
  agentRef.current = agent2;
15107
+ if (onAgentCreated) {
15108
+ onAgentCreated(agent2);
15109
+ }
13324
15110
  const result = await agent2.run(runConfig);
13325
15111
  if (isMounted) {
13326
15112
  onComplete(result.success, result.providerSessionId);
@@ -13361,17 +15147,18 @@ var init_interactive = __esm({
13361
15147
  setIsAgentRunning
13362
15148
  } = useSession();
13363
15149
  const { setUsageStats } = useUsageStats();
13364
- const [sessionId, setSessionId] = React31.useState(initialSessionId);
13365
- const [currentTask, setCurrentTask] = React31.useState(config2.task);
13366
- const [taskId, setTaskId] = React31.useState(0);
13367
- const [shouldRunAgent, setShouldRunAgent] = React31.useState(!!config2.task);
13368
- const [taskQueue, setTaskQueue] = React31.useState([]);
13369
- const [providerSessionId, setProviderSessionId] = React31.useState();
13370
- const messageBridgeRef = React31.useRef(null);
13371
- const lastSubmitRef = React31.useRef(null);
13372
- const [pendingInjected, setPendingInjected] = React31.useState([]);
13373
- const pendingInjectedRef = React31.useRef([]);
13374
- React31.useEffect(() => {
15150
+ const [sessionId, setSessionId] = React32.useState(initialSessionId);
15151
+ const [currentTask, setCurrentTask] = React32.useState(config2.task);
15152
+ const [taskId, setTaskId] = React32.useState(0);
15153
+ const [shouldRunAgent, setShouldRunAgent] = React32.useState(!!config2.task);
15154
+ const [taskQueue, setTaskQueue] = React32.useState([]);
15155
+ const [providerSessionId, setProviderSessionId] = React32.useState();
15156
+ const messageBridgeRef = React32.useRef(null);
15157
+ const agentRef = React32.useRef(null);
15158
+ const lastSubmitRef = React32.useRef(null);
15159
+ const [pendingInjected, setPendingInjected] = React32.useState([]);
15160
+ const pendingInjectedRef = React32.useRef([]);
15161
+ React32.useEffect(() => {
13375
15162
  pendingInjectedRef.current = pendingInjected;
13376
15163
  }, [pendingInjected]);
13377
15164
  const handleSubmitTask = async (task) => {
@@ -13389,6 +15176,7 @@ var init_interactive = __esm({
13389
15176
  });
13390
15177
  setSessionId(session.sessionId);
13391
15178
  setContextSessionId(session.sessionId);
15179
+ setProviderSessionId(void 0);
13392
15180
  } catch (error) {
13393
15181
  const errorMessage = error instanceof ApiError ? error.message : `Failed to create session: ${error instanceof Error ? error.message : String(error)}`;
13394
15182
  addMessage({
@@ -13434,7 +15222,7 @@ var init_interactive = __esm({
13434
15222
  if (shouldRunAgent && !messageBridgeRef.current) {
13435
15223
  messageBridgeRef.current = new MessageBridge(providerSessionId || "");
13436
15224
  }
13437
- React31.useEffect(() => {
15225
+ React32.useEffect(() => {
13438
15226
  if (!shouldRunAgent && taskQueue.length > 0) {
13439
15227
  const [nextTask, ...remaining] = taskQueue;
13440
15228
  setTaskQueue(remaining);
@@ -13448,20 +15236,27 @@ var init_interactive = __esm({
13448
15236
  setShouldRunAgent(true);
13449
15237
  }
13450
15238
  }, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
13451
- const handleClearSession = React31.useCallback(() => {
15239
+ const handleClearSession = React32.useCallback(() => {
13452
15240
  setSessionId(void 0);
13453
15241
  setContextSessionId(void 0);
13454
15242
  setProviderSessionId(void 0);
13455
15243
  setTaskQueue([]);
13456
15244
  setPendingInjected([]);
13457
15245
  }, [setContextSessionId]);
13458
- return /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement(
15246
+ const handleQuestionAnswer = React32.useCallback((questionId, answers) => {
15247
+ if (agentRef.current) {
15248
+ return agentRef.current.submitAnswer(questionId, answers);
15249
+ }
15250
+ return false;
15251
+ }, []);
15252
+ return /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(
13459
15253
  App,
13460
15254
  {
13461
15255
  apiClient,
13462
15256
  config: { ...config2, task: currentTask },
13463
15257
  onClearSession: handleClearSession,
13464
15258
  onExit,
15259
+ onQuestionAnswer: handleQuestionAnswer,
13465
15260
  onResumeSession: async (session) => {
13466
15261
  try {
13467
15262
  if (!apiClient) {
@@ -13477,7 +15272,9 @@ var init_interactive = __esm({
13477
15272
  const uiMessages = convertQueriesToUIMessages(queries);
13478
15273
  setSessionId(session.id);
13479
15274
  setContextSessionId(session.id);
13480
- setProviderSessionId(session.providerSessionId || void 0);
15275
+ const resumeProviderSessionId = session.providerSessionId || void 0;
15276
+ logger.debug(`Resume session: ${session.id}, providerSessionId: ${resumeProviderSessionId || "NOT SET"}`);
15277
+ setProviderSessionId(resumeProviderSessionId);
13481
15278
  const contextData = await apiClient.getSessionContext(session.id);
13482
15279
  if (contextData.contextTokens > 0) {
13483
15280
  const cacheRead = contextData.cacheReadTokens || 0;
@@ -13518,13 +15315,16 @@ var init_interactive = __esm({
13518
15315
  sessionId,
13519
15316
  webUrl
13520
15317
  }
13521
- ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React31.createElement(
15318
+ ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React32.createElement(
13522
15319
  AgentRunner,
13523
15320
  {
13524
15321
  apiClient,
13525
15322
  config: { ...config2, task: currentTask, providerSessionId },
13526
15323
  key: `${taskId}`,
13527
15324
  messageBridge: messageBridgeRef.current,
15325
+ onAgentCreated: (agent2) => {
15326
+ agentRef.current = agent2;
15327
+ },
13528
15328
  onComplete: handleAgentComplete,
13529
15329
  onTurnComplete: () => {
13530
15330
  const pending = pendingInjectedRef.current;
@@ -13543,7 +15343,7 @@ var init_interactive = __esm({
13543
15343
  useBracketedPaste();
13544
15344
  const settings = loadSupatestSettings(props.config.cwd || process.cwd());
13545
15345
  const initialProvider = settings.llmProvider || "supatest-managed";
13546
- return /* @__PURE__ */ React31.createElement(KeypressProvider, null, /* @__PURE__ */ React31.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React31.createElement(InteractiveAppContent, { ...props })));
15346
+ return /* @__PURE__ */ React32.createElement(KeypressProvider, null, /* @__PURE__ */ React32.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React32.createElement(InteractiveAppContent, { ...props })));
13547
15347
  };
13548
15348
  }
13549
15349
  });
@@ -13551,9 +15351,61 @@ var init_interactive = __esm({
13551
15351
  // src/index.ts
13552
15352
  await init_config();
13553
15353
  init_shared_es();
15354
+ import { Command } from "commander";
15355
+
15356
+ // src/commands/claude-login.ts
15357
+ init_claude_oauth();
15358
+ init_secret_storage();
15359
+ async function claudeLoginCommand() {
15360
+ const secretStorage = getSecretStorage();
15361
+ const oauthService = new ClaudeOAuthService(secretStorage);
15362
+ const isAuth = await oauthService.isAuthenticated();
15363
+ if (isAuth) {
15364
+ console.log("\u2705 You're already authenticated with Claude.");
15365
+ console.log("\nTo re-authenticate, first run: supatest claude-logout\n");
15366
+ return;
15367
+ }
15368
+ const result = await oauthService.authorize();
15369
+ if (!result.success) {
15370
+ console.error(`
15371
+ \u274C Authentication failed: ${result.error}
15372
+ `);
15373
+ process.exit(1);
15374
+ }
15375
+ }
15376
+ async function claudeLogoutCommand() {
15377
+ const secretStorage = getSecretStorage();
15378
+ const oauthService = new ClaudeOAuthService(secretStorage);
15379
+ const isAuth = await oauthService.isAuthenticated();
15380
+ if (!isAuth) {
15381
+ console.log("You're not currently authenticated with Claude.\n");
15382
+ return;
15383
+ }
15384
+ await oauthService.deleteTokens();
15385
+ console.log("\u2705 Successfully logged out from Claude.\n");
15386
+ }
15387
+ async function claudeStatusCommand() {
15388
+ const secretStorage = getSecretStorage();
15389
+ const oauthService = new ClaudeOAuthService(secretStorage);
15390
+ const status = await oauthService.getStatus();
15391
+ if (status.isAuthenticated) {
15392
+ console.log("\u2705 Authenticated with Claude");
15393
+ if (status.expiresAt) {
15394
+ const expiresDate = new Date(status.expiresAt);
15395
+ console.log(` Token expires: ${expiresDate.toLocaleString()}`);
15396
+ }
15397
+ } else {
15398
+ console.log("\u274C Not authenticated with Claude");
15399
+ if (status.error) {
15400
+ console.log(` Error: ${status.error}`);
15401
+ }
15402
+ }
15403
+ console.log();
15404
+ }
15405
+
15406
+ // src/index.ts
13554
15407
  init_setup();
13555
15408
  await init_config();
13556
- import { Command } from "commander";
13557
15409
 
13558
15410
  // src/modes/headless.ts
13559
15411
  init_api_client();
@@ -13567,7 +15419,7 @@ init_react();
13567
15419
  init_MessageList();
13568
15420
  init_SessionContext();
13569
15421
  import { execSync as execSync2 } from "child_process";
13570
- import { homedir as homedir4 } from "os";
15422
+ import { homedir as homedir5 } from "os";
13571
15423
  import { Box as Box13, useApp } from "ink";
13572
15424
  import React14, { useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
13573
15425
  var getGitBranch = () => {
@@ -13579,7 +15431,7 @@ var getGitBranch = () => {
13579
15431
  };
13580
15432
  var getCurrentFolder = () => {
13581
15433
  const cwd = process.cwd();
13582
- const home = homedir4();
15434
+ const home = homedir5();
13583
15435
  if (cwd.startsWith(home)) {
13584
15436
  return `~${cwd.slice(home.length)}`;
13585
15437
  }
@@ -13826,7 +15678,7 @@ async function runAgent(config2) {
13826
15678
  // src/utils/auto-update.ts
13827
15679
  init_version();
13828
15680
  init_error_logger();
13829
- import { execSync as execSync3, spawn } from "child_process";
15681
+ import { execSync as execSync3, spawn as spawn3 } from "child_process";
13830
15682
  import latestVersion from "latest-version";
13831
15683
  import { gt } from "semver";
13832
15684
  var UPDATE_CHECK_TIMEOUT = 3e3;
@@ -13868,7 +15720,7 @@ Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
13868
15720
  });
13869
15721
  }
13870
15722
  console.log("\u2713 Updated successfully\n");
13871
- const child = spawn(process.argv[0], process.argv.slice(1), {
15723
+ const child = spawn3(process.argv[0], process.argv.slice(1), {
13872
15724
  stdio: "inherit",
13873
15725
  detached: false
13874
15726
  });
@@ -14153,6 +16005,33 @@ program.command("setup").description("Check prerequisites and set up required to
14153
16005
  process.exit(1);
14154
16006
  }
14155
16007
  });
16008
+ program.command("claude-login").description("Authenticate with Claude using OAuth (uses your Claude Pro/Max subscription)").action(async () => {
16009
+ try {
16010
+ await claudeLoginCommand();
16011
+ } catch (error) {
16012
+ logError(error, { source: "claude-login" });
16013
+ logger.error(`Claude login failed: ${error instanceof Error ? error.message : String(error)}`);
16014
+ process.exit(1);
16015
+ }
16016
+ });
16017
+ program.command("claude-logout").description("Sign out from Claude OAuth").action(async () => {
16018
+ try {
16019
+ await claudeLogoutCommand();
16020
+ } catch (error) {
16021
+ logError(error, { source: "claude-logout" });
16022
+ logger.error(`Claude logout failed: ${error instanceof Error ? error.message : String(error)}`);
16023
+ process.exit(1);
16024
+ }
16025
+ });
16026
+ program.command("claude-status").description("Show Claude OAuth authentication status").action(async () => {
16027
+ try {
16028
+ await claudeStatusCommand();
16029
+ } catch (error) {
16030
+ logError(error, { source: "claude-status" });
16031
+ logger.error(`Claude status check failed: ${error instanceof Error ? error.message : String(error)}`);
16032
+ process.exit(1);
16033
+ }
16034
+ });
14156
16035
  var filteredArgv = process.argv.filter((arg, index) => {
14157
16036
  return !(arg === "--" && index > 1);
14158
16037
  });