@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.
- package/dist/index.js +2375 -496
- 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
|
|
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
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
5735
|
-
|
|
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
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
6914
|
+
CLI_VERSION = "0.0.41";
|
|
5895
6915
|
}
|
|
5896
6916
|
});
|
|
5897
6917
|
|
|
5898
6918
|
// src/utils/error-logger.ts
|
|
5899
|
-
import * as
|
|
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 (!
|
|
5905
|
-
|
|
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 (!
|
|
5915
|
-
const stats =
|
|
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 (
|
|
5919
|
-
|
|
6938
|
+
if (fs3.existsSync(oldLogFile)) {
|
|
6939
|
+
fs3.unlinkSync(oldLogFile);
|
|
5920
6940
|
}
|
|
5921
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7847
|
+
const commandsDir = join4(cwd, ".supatest", "commands");
|
|
6695
7848
|
const relativePath = commandName.replace(/\./g, "/") + ".md";
|
|
6696
|
-
const filePath =
|
|
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 =
|
|
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
|
|
6751
|
-
import { join as
|
|
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 =
|
|
7953
|
+
const globalMcpPath = join5(homedir3(), ".supatest", "mcp.json");
|
|
6801
7954
|
const globalServers = loadMcpServersFromFile(globalMcpPath);
|
|
6802
|
-
const projectMcpPath =
|
|
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
|
|
7967
|
+
import { join as join6 } from "path";
|
|
6815
7968
|
function loadProjectInstructions(cwd) {
|
|
6816
7969
|
const paths = [
|
|
6817
|
-
|
|
6818
|
-
|
|
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
|
|
6839
|
-
import { dirname, join as
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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, {
|
|
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
|
|
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
|
|
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 =
|
|
8951
|
-
const cipher =
|
|
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 =
|
|
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
|
|
8986
|
-
import { join as
|
|
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
|
|
10239
|
+
return join8(CONFIG_DIR, "token.json");
|
|
8991
10240
|
}
|
|
8992
|
-
return
|
|
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 =
|
|
10309
|
+
CONFIG_DIR = join8(homedir6(), ".supatest");
|
|
9061
10310
|
PRODUCTION_API_URL = "https://code-api.supatest.ai";
|
|
9062
10311
|
STORAGE_VERSION = 2;
|
|
9063
|
-
TOKEN_FILE =
|
|
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
|
|
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
|
|
9160
|
-
import
|
|
9161
|
-
import
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9165
10414
|
}
|
|
9166
10415
|
function generateState() {
|
|
9167
|
-
return
|
|
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 =
|
|
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
|
-
|
|
10449
|
+
spawn4(command, args, options).unref();
|
|
9201
10450
|
}
|
|
9202
10451
|
function startCallbackServer(port, expectedState) {
|
|
9203
10452
|
return new Promise((resolve2, reject) => {
|
|
9204
|
-
const server =
|
|
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
|
-
},
|
|
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,
|
|
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
|
-
|
|
10780
|
+
CALLBACK_TIMEOUT_MS2 = 3e5;
|
|
9532
10781
|
STATE_LENGTH = 32;
|
|
9533
10782
|
}
|
|
9534
10783
|
});
|
|
9535
10784
|
|
|
9536
10785
|
// src/utils/claude-max.ts
|
|
9537
|
-
|
|
9538
|
-
|
|
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
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
10825
|
+
if (!existsSync6(mcpPath)) {
|
|
9616
10826
|
return {};
|
|
9617
10827
|
}
|
|
9618
10828
|
try {
|
|
9619
|
-
const content =
|
|
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 (!
|
|
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
|
|
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 (!
|
|
11051
|
+
if (!existsSync7(settingsPath)) {
|
|
9842
11052
|
return {};
|
|
9843
11053
|
}
|
|
9844
11054
|
try {
|
|
9845
|
-
const content =
|
|
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 (!
|
|
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
|
|
11076
|
-
|
|
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 (
|
|
11111
|
-
|
|
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
|
-
|
|
11115
|
-
|
|
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
|
-
|
|
11124
|
-
|
|
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 =
|
|
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 (
|
|
11131
|
-
onSelect([
|
|
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 =
|
|
11149
|
-
const adjustedEnd = Math.min(
|
|
11150
|
-
const visibleTests =
|
|
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
|
|
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:
|
|
11157
|
-
bold:
|
|
11158
|
-
color:
|
|
12453
|
+
backgroundColor: isOnFixNext10 ? theme.text.accent : void 0,
|
|
12454
|
+
bold: isOnFixNext10,
|
|
12455
|
+
color: isOnFixNext10 ? "black" : theme.text.primary
|
|
11159
12456
|
},
|
|
11160
|
-
|
|
11161
|
-
"[Fix
|
|
11162
|
-
|
|
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")),
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
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("
|
|
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
|
-
|
|
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" }, "
|
|
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-
|
|
11295
|
-
import fs4 from "fs";
|
|
12711
|
+
// src/ui/utils/file-search.ts
|
|
11296
12712
|
import path4 from "path";
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
const
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
if (
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
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
|
-
|
|
11323
|
-
|
|
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
|
|
11326
|
-
|
|
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
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
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
|
|
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] =
|
|
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
|
|
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 (
|
|
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] =
|
|
11481
|
-
const [
|
|
11482
|
-
const
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
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] =
|
|
11503
|
-
const [isSlashCommand, setIsSlashCommand] =
|
|
11504
|
-
|
|
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
|
-
|
|
13015
|
+
setCursorPos([0, 0]);
|
|
11521
13016
|
setShowSuggestions(false);
|
|
11522
13017
|
onInputChange?.("");
|
|
11523
13018
|
}
|
|
11524
13019
|
}));
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11542
|
-
|
|
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
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
11602
|
-
const
|
|
11603
|
-
|
|
11604
|
-
const
|
|
11605
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
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
|
-
|
|
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
|
|
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] =
|
|
11779
|
-
const [serverName, setServerName] =
|
|
11780
|
-
const [command, setCommand] =
|
|
11781
|
-
const [args, setArgs] =
|
|
11782
|
-
const [description, setDescription] =
|
|
11783
|
-
const [scope, setScope] =
|
|
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
|
|
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] =
|
|
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
|
|
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] =
|
|
11964
|
-
const [isTestingAll, setIsTestingAll] =
|
|
11965
|
-
|
|
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
|
|
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
|
|
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] =
|
|
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/
|
|
13668
|
+
// src/ui/components/QuestionSelector.tsx
|
|
12106
13669
|
import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
|
|
12107
|
-
import React29, {
|
|
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] =
|
|
12123
|
-
const [selectedIndex, setSelectedIndex] =
|
|
12124
|
-
const [isLoading, setIsLoading] =
|
|
12125
|
-
const [hasMore, setHasMore] =
|
|
12126
|
-
const [totalSessions, setTotalSessions] =
|
|
12127
|
-
const [error, setError] =
|
|
12128
|
-
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
12229
|
-
})), /* @__PURE__ */
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
13957
|
+
import { execSync as execSync5 } from "child_process";
|
|
12279
13958
|
import { homedir as homedir8 } from "os";
|
|
12280
|
-
import { Box as
|
|
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
|
|
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
|
|
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] =
|
|
12337
|
-
const [showInput, setShowInput] =
|
|
12338
|
-
const [gitBranch] =
|
|
12339
|
-
const [currentFolder] =
|
|
12340
|
-
const hasInputContentRef =
|
|
12341
|
-
const [exitWarning, setExitWarning] =
|
|
12342
|
-
const inputPromptRef =
|
|
12343
|
-
const [showSessionSelector, setShowSessionSelector] =
|
|
12344
|
-
const [showModelSelector, setShowModelSelector] =
|
|
12345
|
-
const [showProviderSelector, setShowProviderSelector] =
|
|
12346
|
-
const [
|
|
12347
|
-
const [
|
|
12348
|
-
const [
|
|
12349
|
-
const [
|
|
12350
|
-
const [
|
|
12351
|
-
const [
|
|
12352
|
-
const [
|
|
12353
|
-
const [
|
|
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] =
|
|
12357
|
-
const [authState, setAuthState] =
|
|
14037
|
+
const [mcpServers, setMcpServers] = useState18([]);
|
|
14038
|
+
const [authState, setAuthState] = useState18(
|
|
12358
14039
|
() => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
|
|
12359
14040
|
);
|
|
12360
|
-
const [showAuthDialog, setShowAuthDialog] =
|
|
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
|
-
|
|
14056
|
+
useEffect14(() => {
|
|
12375
14057
|
if (!config2.supatestApiKey) {
|
|
12376
14058
|
setShowAuthDialog(true);
|
|
12377
14059
|
}
|
|
12378
14060
|
}, [config2.supatestApiKey]);
|
|
12379
|
-
|
|
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 =
|
|
12826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
12906
|
-
|
|
14658
|
+
return /* @__PURE__ */ React31.createElement(
|
|
14659
|
+
Box28,
|
|
12907
14660
|
{
|
|
12908
14661
|
flexDirection: "column",
|
|
12909
14662
|
paddingX: 1
|
|
12910
14663
|
},
|
|
12911
|
-
/* @__PURE__ */
|
|
12912
|
-
showSessionSelector && apiClient && /* @__PURE__ */
|
|
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__ */
|
|
12921
|
-
showModelSelector && /* @__PURE__ */
|
|
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__ */
|
|
14683
|
+
showProviderSelector && /* @__PURE__ */ React31.createElement(
|
|
12931
14684
|
ProviderSelector,
|
|
12932
14685
|
{
|
|
12933
|
-
claudeMaxAvailable
|
|
14686
|
+
claudeMaxAvailable,
|
|
12934
14687
|
currentProvider: llmProvider,
|
|
12935
14688
|
onCancel: handleProviderSelectorCancel,
|
|
12936
14689
|
onSelect: handleProviderSelect
|
|
12937
14690
|
}
|
|
12938
14691
|
),
|
|
12939
|
-
showAuthDialog && /* @__PURE__ */
|
|
14692
|
+
showAuthDialog && /* @__PURE__ */ React31.createElement(
|
|
12940
14693
|
AuthDialog,
|
|
12941
14694
|
{
|
|
12942
14695
|
onLogin: handleLogin
|
|
12943
14696
|
}
|
|
12944
14697
|
),
|
|
12945
|
-
showFeedbackDialog && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
12973
|
-
showMcpSelector && /* @__PURE__ */
|
|
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
|
-
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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
|
|
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__ */
|
|
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 =
|
|
13250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13308
|
-
|
|
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] =
|
|
13365
|
-
const [currentTask, setCurrentTask] =
|
|
13366
|
-
const [taskId, setTaskId] =
|
|
13367
|
-
const [shouldRunAgent, setShouldRunAgent] =
|
|
13368
|
-
const [taskQueue, setTaskQueue] =
|
|
13369
|
-
const [providerSessionId, setProviderSessionId] =
|
|
13370
|
-
const messageBridgeRef =
|
|
13371
|
-
const
|
|
13372
|
-
const
|
|
13373
|
-
const
|
|
13374
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
});
|