@supatest/cli 0.0.39 → 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 +2459 -555
- 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",
|
|
@@ -7528,10 +8770,11 @@ var init_react = __esm({
|
|
|
7528
8770
|
|
|
7529
8771
|
// src/ui/contexts/SessionContext.tsx
|
|
7530
8772
|
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
7531
|
-
var SessionContext, SessionProvider, useSession;
|
|
8773
|
+
var UsageStatsContext, SessionContext, SessionProvider, useSession, useUsageStats;
|
|
7532
8774
|
var init_SessionContext = __esm({
|
|
7533
8775
|
"src/ui/contexts/SessionContext.tsx"() {
|
|
7534
8776
|
"use strict";
|
|
8777
|
+
UsageStatsContext = createContext(null);
|
|
7535
8778
|
SessionContext = createContext(null);
|
|
7536
8779
|
SessionProvider = ({
|
|
7537
8780
|
children,
|
|
@@ -7558,6 +8801,7 @@ var init_SessionContext = __esm({
|
|
|
7558
8801
|
const [allToolsExpanded, setAllToolsExpanded] = useState(true);
|
|
7559
8802
|
const [toolGroupsExpanded, setToolGroupsExpanded] = useState(false);
|
|
7560
8803
|
const [staticRemountKey, setStaticRemountKey] = useState(0);
|
|
8804
|
+
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
7561
8805
|
const addMessage = useCallback(
|
|
7562
8806
|
(message) => {
|
|
7563
8807
|
const expandableTools = ["Bash", "BashOutput", "Command Output"];
|
|
@@ -7671,8 +8915,6 @@ var init_SessionContext = __esm({
|
|
|
7671
8915
|
setTodos,
|
|
7672
8916
|
stats,
|
|
7673
8917
|
updateStats,
|
|
7674
|
-
usageStats,
|
|
7675
|
-
setUsageStats,
|
|
7676
8918
|
isAgentRunning,
|
|
7677
8919
|
setIsAgentRunning,
|
|
7678
8920
|
shouldInterruptAgent,
|
|
@@ -7690,7 +8932,9 @@ var init_SessionContext = __esm({
|
|
|
7690
8932
|
llmProvider,
|
|
7691
8933
|
setLlmProvider,
|
|
7692
8934
|
staticRemountKey,
|
|
7693
|
-
refreshStatic
|
|
8935
|
+
refreshStatic,
|
|
8936
|
+
pendingQuestion,
|
|
8937
|
+
setPendingQuestion
|
|
7694
8938
|
}), [
|
|
7695
8939
|
messages,
|
|
7696
8940
|
addMessage,
|
|
@@ -7706,7 +8950,6 @@ var init_SessionContext = __esm({
|
|
|
7706
8950
|
todos,
|
|
7707
8951
|
stats,
|
|
7708
8952
|
updateStats,
|
|
7709
|
-
usageStats,
|
|
7710
8953
|
isAgentRunning,
|
|
7711
8954
|
shouldInterruptAgent,
|
|
7712
8955
|
sessionId,
|
|
@@ -7716,9 +8959,14 @@ var init_SessionContext = __esm({
|
|
|
7716
8959
|
selectedModel,
|
|
7717
8960
|
llmProvider,
|
|
7718
8961
|
staticRemountKey,
|
|
7719
|
-
refreshStatic
|
|
8962
|
+
refreshStatic,
|
|
8963
|
+
pendingQuestion
|
|
7720
8964
|
]);
|
|
7721
|
-
|
|
8965
|
+
const usageStatsValue = useMemo(() => ({
|
|
8966
|
+
usageStats,
|
|
8967
|
+
setUsageStats
|
|
8968
|
+
}), [usageStats]);
|
|
8969
|
+
return /* @__PURE__ */ React.createElement(SessionContext.Provider, { value }, /* @__PURE__ */ React.createElement(UsageStatsContext.Provider, { value: usageStatsValue }, children));
|
|
7722
8970
|
};
|
|
7723
8971
|
useSession = () => {
|
|
7724
8972
|
const context = useContext(SessionContext);
|
|
@@ -7727,6 +8975,13 @@ var init_SessionContext = __esm({
|
|
|
7727
8975
|
}
|
|
7728
8976
|
return context;
|
|
7729
8977
|
};
|
|
8978
|
+
useUsageStats = () => {
|
|
8979
|
+
const context = useContext(UsageStatsContext);
|
|
8980
|
+
if (!context) {
|
|
8981
|
+
throw new Error("useUsageStats must be used within SessionProvider");
|
|
8982
|
+
}
|
|
8983
|
+
return context;
|
|
8984
|
+
};
|
|
7730
8985
|
}
|
|
7731
8986
|
});
|
|
7732
8987
|
|
|
@@ -8436,6 +9691,14 @@ function getCommandDisplay(toolName, input) {
|
|
|
8436
9691
|
return input.pattern ? [`"${input.pattern}"${input.path ? ` in ${input.path}` : ""}`] : null;
|
|
8437
9692
|
case "Glob":
|
|
8438
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
|
+
}
|
|
8439
9702
|
default:
|
|
8440
9703
|
return null;
|
|
8441
9704
|
}
|
|
@@ -8451,7 +9714,8 @@ function getToolStyle(toolName) {
|
|
|
8451
9714
|
Glob: { icon: "\u{1F50D}", color: theme.tool.search },
|
|
8452
9715
|
Grep: { icon: "\u{1F50D}", color: theme.tool.search },
|
|
8453
9716
|
Task: { icon: "\u{1F916}", color: theme.tool.agent },
|
|
8454
|
-
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 }
|
|
8455
9719
|
};
|
|
8456
9720
|
return styles[toolName] || { icon: "\u{1F527}", color: theme.text.secondary };
|
|
8457
9721
|
}
|
|
@@ -8485,6 +9749,9 @@ function getResultSummary(toolName, result) {
|
|
|
8485
9749
|
const lines = result.split("\n").filter((line) => line.trim());
|
|
8486
9750
|
return lines.length > 0 ? `${lines.length} lines of output` : "No output";
|
|
8487
9751
|
}
|
|
9752
|
+
case "AskUserQuestion": {
|
|
9753
|
+
return result ? `Answered: ${result.substring(0, 50)}${result.length > 50 ? "..." : ""}` : "Waiting for response...";
|
|
9754
|
+
}
|
|
8488
9755
|
default:
|
|
8489
9756
|
return null;
|
|
8490
9757
|
}
|
|
@@ -8569,16 +9836,7 @@ var init_UserMessage = __esm({
|
|
|
8569
9836
|
"use strict";
|
|
8570
9837
|
init_theme();
|
|
8571
9838
|
UserMessage = ({ text }) => {
|
|
8572
|
-
return /* @__PURE__ */ React11.createElement(Box10, {
|
|
8573
|
-
Box10,
|
|
8574
|
-
{
|
|
8575
|
-
borderColor: theme.text.dim,
|
|
8576
|
-
borderStyle: "round",
|
|
8577
|
-
paddingLeft: 1,
|
|
8578
|
-
paddingRight: 1
|
|
8579
|
-
},
|
|
8580
|
-
/* @__PURE__ */ React11.createElement(Text10, { color: theme.text.secondary }, text)
|
|
8581
|
-
));
|
|
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));
|
|
8582
9840
|
};
|
|
8583
9841
|
}
|
|
8584
9842
|
});
|
|
@@ -8621,8 +9879,8 @@ var init_QueuedMessageDisplay = __esm({
|
|
|
8621
9879
|
|
|
8622
9880
|
// src/ui/components/MessageList.tsx
|
|
8623
9881
|
import { Box as Box12, Static } from "ink";
|
|
8624
|
-
import React13, { memo as memo2, useCallback as useCallback2, useMemo as useMemo3
|
|
8625
|
-
var MessageList;
|
|
9882
|
+
import React13, { memo as memo2, useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
9883
|
+
var StaticHeader, MessageList;
|
|
8626
9884
|
var init_MessageList = __esm({
|
|
8627
9885
|
"src/ui/components/MessageList.tsx"() {
|
|
8628
9886
|
"use strict";
|
|
@@ -8637,6 +9895,11 @@ var init_MessageList = __esm({
|
|
|
8637
9895
|
init_ToolMessage();
|
|
8638
9896
|
init_UserMessage();
|
|
8639
9897
|
init_QueuedMessageDisplay();
|
|
9898
|
+
StaticHeader = memo2(({ currentFolder, gitBranch, headless, staticRemountKey }) => {
|
|
9899
|
+
const headerItems = useMemo3(() => [{ id: "header" }], []);
|
|
9900
|
+
return /* @__PURE__ */ React13.createElement(Static, { items: headerItems, key: `header-${staticRemountKey}` }, () => /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: "header" }, /* @__PURE__ */ React13.createElement(Header, { currentFolder, gitBranch, headless })));
|
|
9901
|
+
});
|
|
9902
|
+
StaticHeader.displayName = "StaticHeader";
|
|
8640
9903
|
MessageList = memo2(({ terminalWidth, currentFolder, gitBranch, headless = false, queuedTasks = [] }) => {
|
|
8641
9904
|
const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups } = useSession();
|
|
8642
9905
|
const handleToggle = useCallback2((id, currentExpanded) => {
|
|
@@ -8738,31 +10001,29 @@ var init_MessageList = __esm({
|
|
|
8738
10001
|
}
|
|
8739
10002
|
return renderMessage(group.messages[0]);
|
|
8740
10003
|
}, [toolGroupsExpanded, toggleToolGroups, handleToggle, renderMessage]);
|
|
8741
|
-
const lastUserMessageIndex = useMemo3(() => {
|
|
8742
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
8743
|
-
if (messages[i].type === "user") {
|
|
8744
|
-
return i;
|
|
8745
|
-
}
|
|
8746
|
-
}
|
|
8747
|
-
return -1;
|
|
8748
|
-
}, [messages]);
|
|
8749
10004
|
const hasPendingAssistant = useMemo3(
|
|
8750
10005
|
() => messages.some((m) => m.type === "assistant" && m.isPending),
|
|
8751
10006
|
[messages]
|
|
8752
10007
|
);
|
|
8753
|
-
const completedBoundaryRef = useRef3(-1);
|
|
8754
|
-
const completedBoundaryKey = useMemo3(() => {
|
|
8755
|
-
const currentBoundary = lastUserMessageIndex;
|
|
8756
|
-
if (currentBoundary !== completedBoundaryRef.current) {
|
|
8757
|
-
completedBoundaryRef.current = currentBoundary;
|
|
8758
|
-
return `boundary-${currentBoundary}`;
|
|
8759
|
-
}
|
|
8760
|
-
return `boundary-${completedBoundaryRef.current}`;
|
|
8761
|
-
}, [lastUserMessageIndex]);
|
|
8762
10008
|
const { completedGroups, currentTurnGroups } = useMemo3(() => {
|
|
8763
10009
|
const completed = [];
|
|
8764
10010
|
const currentTurn = [];
|
|
8765
|
-
const
|
|
10011
|
+
const isMessageComplete = (msg) => {
|
|
10012
|
+
switch (msg.type) {
|
|
10013
|
+
case "user":
|
|
10014
|
+
case "error":
|
|
10015
|
+
case "todo":
|
|
10016
|
+
case "thinking":
|
|
10017
|
+
return true;
|
|
10018
|
+
case "tool":
|
|
10019
|
+
return msg.toolResult !== void 0;
|
|
10020
|
+
case "assistant":
|
|
10021
|
+
return !msg.isPending;
|
|
10022
|
+
default:
|
|
10023
|
+
return true;
|
|
10024
|
+
}
|
|
10025
|
+
};
|
|
10026
|
+
const processTurn = (turnMessages, targetArray) => {
|
|
8766
10027
|
let currentToolGroup = [];
|
|
8767
10028
|
const flushToolGroup = () => {
|
|
8768
10029
|
if (currentToolGroup.length === 0) return;
|
|
@@ -8773,7 +10034,7 @@ var init_MessageList = __esm({
|
|
|
8773
10034
|
}
|
|
8774
10035
|
currentToolGroup = [];
|
|
8775
10036
|
};
|
|
8776
|
-
for (const msg of
|
|
10037
|
+
for (const msg of turnMessages) {
|
|
8777
10038
|
if (msg.type === "tool") {
|
|
8778
10039
|
currentToolGroup.push(msg);
|
|
8779
10040
|
} else {
|
|
@@ -8783,38 +10044,46 @@ var init_MessageList = __esm({
|
|
|
8783
10044
|
}
|
|
8784
10045
|
flushToolGroup();
|
|
8785
10046
|
};
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
if (msg
|
|
8790
|
-
|
|
8791
|
-
turnMessages = [];
|
|
8792
|
-
completed.push({ type: "single", messages: [msg] });
|
|
10047
|
+
const completeMessages = [];
|
|
10048
|
+
const inProgressMessages = [];
|
|
10049
|
+
for (const msg of messages) {
|
|
10050
|
+
if (isMessageComplete(msg)) {
|
|
10051
|
+
completeMessages.push(msg);
|
|
8793
10052
|
} else {
|
|
8794
|
-
|
|
10053
|
+
inProgressMessages.push(msg);
|
|
8795
10054
|
}
|
|
8796
10055
|
}
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
10056
|
+
let splitIndex = messages.length;
|
|
10057
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
10058
|
+
if (isMessageComplete(messages[i])) {
|
|
10059
|
+
splitIndex = i + 1;
|
|
10060
|
+
break;
|
|
10061
|
+
}
|
|
8800
10062
|
}
|
|
8801
|
-
const
|
|
8802
|
-
|
|
10063
|
+
const finalCompleteMessages = messages.slice(0, splitIndex);
|
|
10064
|
+
const finalInProgressMessages = messages.slice(splitIndex);
|
|
10065
|
+
processTurn(finalCompleteMessages, completed);
|
|
10066
|
+
processTurn(finalInProgressMessages, currentTurn);
|
|
8803
10067
|
return { completedGroups: completed, currentTurnGroups: currentTurn };
|
|
8804
|
-
}, [messages
|
|
8805
|
-
const staticItems = useMemo3(
|
|
8806
|
-
|
|
8807
|
-
...completedGroups.map((group, idx) => {
|
|
10068
|
+
}, [messages]);
|
|
10069
|
+
const staticItems = useMemo3(
|
|
10070
|
+
() => completedGroups.map((group, idx) => {
|
|
8808
10071
|
if (group.type === "group") {
|
|
8809
|
-
return { ...group, _isGroup: true, id: `group-${idx}` };
|
|
10072
|
+
return { ...group, _isGroup: true, id: `group-${group.messages[0]?.id || idx}` };
|
|
8810
10073
|
}
|
|
8811
10074
|
return { ...group.messages[0], _isMessage: true };
|
|
8812
|
-
})
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
10075
|
+
}),
|
|
10076
|
+
[completedGroups]
|
|
10077
|
+
);
|
|
10078
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(
|
|
10079
|
+
StaticHeader,
|
|
10080
|
+
{
|
|
10081
|
+
currentFolder,
|
|
10082
|
+
gitBranch,
|
|
10083
|
+
headless,
|
|
10084
|
+
staticRemountKey
|
|
8817
10085
|
}
|
|
10086
|
+
), staticItems.length > 0 && /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `messages-${staticRemountKey}-${toolGroupsExpanded}` }, (item) => {
|
|
8818
10087
|
if (item._isGroup) {
|
|
8819
10088
|
const content2 = renderGroupedMessage(item);
|
|
8820
10089
|
if (!content2) {
|
|
@@ -8908,7 +10177,7 @@ var init_stdio = __esm({
|
|
|
8908
10177
|
});
|
|
8909
10178
|
|
|
8910
10179
|
// src/utils/encryption.ts
|
|
8911
|
-
import
|
|
10180
|
+
import crypto2 from "crypto";
|
|
8912
10181
|
import { hostname, userInfo } from "os";
|
|
8913
10182
|
function deriveEncryptionKey() {
|
|
8914
10183
|
const host = hostname();
|
|
@@ -8917,7 +10186,7 @@ function deriveEncryptionKey() {
|
|
|
8917
10186
|
if (process.env.DEBUG_ENCRYPTION) {
|
|
8918
10187
|
console.error(`[encryption] hostname=${host}, username=${user}, salt=${salt}`);
|
|
8919
10188
|
}
|
|
8920
|
-
return
|
|
10189
|
+
return crypto2.scryptSync("supatest-cli-token", salt, KEY_LENGTH);
|
|
8921
10190
|
}
|
|
8922
10191
|
function getEncryptionKey() {
|
|
8923
10192
|
if (!cachedKey) {
|
|
@@ -8927,8 +10196,8 @@ function getEncryptionKey() {
|
|
|
8927
10196
|
}
|
|
8928
10197
|
function encrypt(plaintext) {
|
|
8929
10198
|
const key = getEncryptionKey();
|
|
8930
|
-
const iv =
|
|
8931
|
-
const cipher =
|
|
10199
|
+
const iv = crypto2.randomBytes(IV_LENGTH);
|
|
10200
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
|
|
8932
10201
|
let encrypted = cipher.update(plaintext, "utf8", "hex");
|
|
8933
10202
|
encrypted += cipher.final("hex");
|
|
8934
10203
|
const authTag = cipher.getAuthTag();
|
|
@@ -8943,7 +10212,7 @@ function decrypt(encryptedData) {
|
|
|
8943
10212
|
const iv = Buffer.from(ivHex, "hex");
|
|
8944
10213
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
8945
10214
|
const key = getEncryptionKey();
|
|
8946
|
-
const decipher =
|
|
10215
|
+
const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
|
|
8947
10216
|
decipher.setAuthTag(authTag);
|
|
8948
10217
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
8949
10218
|
decrypted += decipher.final("utf8");
|
|
@@ -8962,14 +10231,14 @@ var init_encryption = __esm({
|
|
|
8962
10231
|
|
|
8963
10232
|
// src/utils/token-storage.ts
|
|
8964
10233
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
8965
|
-
import { homedir as
|
|
8966
|
-
import { join as
|
|
10234
|
+
import { homedir as homedir6 } from "os";
|
|
10235
|
+
import { join as join8 } from "path";
|
|
8967
10236
|
function getTokenFilePath() {
|
|
8968
10237
|
const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
|
|
8969
10238
|
if (apiUrl === PRODUCTION_API_URL) {
|
|
8970
|
-
return
|
|
10239
|
+
return join8(CONFIG_DIR, "token.json");
|
|
8971
10240
|
}
|
|
8972
|
-
return
|
|
10241
|
+
return join8(CONFIG_DIR, "token.local.json");
|
|
8973
10242
|
}
|
|
8974
10243
|
function isV2Format(stored) {
|
|
8975
10244
|
return "version" in stored && stored.version === 2;
|
|
@@ -9037,10 +10306,10 @@ var init_token_storage = __esm({
|
|
|
9037
10306
|
"src/utils/token-storage.ts"() {
|
|
9038
10307
|
"use strict";
|
|
9039
10308
|
init_encryption();
|
|
9040
|
-
CONFIG_DIR =
|
|
10309
|
+
CONFIG_DIR = join8(homedir6(), ".supatest");
|
|
9041
10310
|
PRODUCTION_API_URL = "https://code-api.supatest.ai";
|
|
9042
10311
|
STORAGE_VERSION = 2;
|
|
9043
|
-
TOKEN_FILE =
|
|
10312
|
+
TOKEN_FILE = join8(CONFIG_DIR, "token.json");
|
|
9044
10313
|
}
|
|
9045
10314
|
});
|
|
9046
10315
|
|
|
@@ -9064,7 +10333,7 @@ var init_message_bridge = __esm({
|
|
|
9064
10333
|
this.sessionId = sessionId;
|
|
9065
10334
|
}
|
|
9066
10335
|
/**
|
|
9067
|
-
* Push a user message to be injected into the
|
|
10336
|
+
* Push a user message to be injected into the session.
|
|
9068
10337
|
* Call this from the UI when user submits a message during agent execution.
|
|
9069
10338
|
*/
|
|
9070
10339
|
push(text) {
|
|
@@ -9136,15 +10405,15 @@ var init_message_bridge = __esm({
|
|
|
9136
10405
|
});
|
|
9137
10406
|
|
|
9138
10407
|
// src/commands/login.ts
|
|
9139
|
-
import { spawn as
|
|
9140
|
-
import
|
|
9141
|
-
import
|
|
9142
|
-
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";
|
|
9143
10412
|
function escapeHtml(text) {
|
|
9144
10413
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9145
10414
|
}
|
|
9146
10415
|
function generateState() {
|
|
9147
|
-
return
|
|
10416
|
+
return crypto3.randomBytes(STATE_LENGTH).toString("hex");
|
|
9148
10417
|
}
|
|
9149
10418
|
async function exchangeCodeForToken(code, state) {
|
|
9150
10419
|
const response = await fetch(`${API_URL}/web/auth/cli-token-exchange`, {
|
|
@@ -9160,7 +10429,7 @@ async function exchangeCodeForToken(code, state) {
|
|
|
9160
10429
|
return { token: data.token, expiresAt: data.expiresAt };
|
|
9161
10430
|
}
|
|
9162
10431
|
function openBrowser(url) {
|
|
9163
|
-
const os3 =
|
|
10432
|
+
const os3 = platform2();
|
|
9164
10433
|
let command;
|
|
9165
10434
|
let args;
|
|
9166
10435
|
switch (os3) {
|
|
@@ -9177,11 +10446,11 @@ function openBrowser(url) {
|
|
|
9177
10446
|
args = [url];
|
|
9178
10447
|
}
|
|
9179
10448
|
const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
|
|
9180
|
-
|
|
10449
|
+
spawn4(command, args, options).unref();
|
|
9181
10450
|
}
|
|
9182
10451
|
function startCallbackServer(port, expectedState) {
|
|
9183
10452
|
return new Promise((resolve2, reject) => {
|
|
9184
|
-
const server =
|
|
10453
|
+
const server = http2.createServer(async (req, res) => {
|
|
9185
10454
|
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
9186
10455
|
res.writeHead(404);
|
|
9187
10456
|
res.end("Not Found");
|
|
@@ -9242,7 +10511,7 @@ function startCallbackServer(port, expectedState) {
|
|
|
9242
10511
|
const timeout = setTimeout(() => {
|
|
9243
10512
|
server.close();
|
|
9244
10513
|
reject(new Error("Login timeout - no response received after 5 minutes"));
|
|
9245
|
-
},
|
|
10514
|
+
}, CALLBACK_TIMEOUT_MS2);
|
|
9246
10515
|
server.on("close", () => {
|
|
9247
10516
|
clearTimeout(timeout);
|
|
9248
10517
|
});
|
|
@@ -9501,74 +10770,33 @@ ${loginUrl}
|
|
|
9501
10770
|
throw error;
|
|
9502
10771
|
}
|
|
9503
10772
|
}
|
|
9504
|
-
var CLI_LOGIN_PORT, FRONTEND_URL, API_URL,
|
|
10773
|
+
var CLI_LOGIN_PORT, FRONTEND_URL, API_URL, CALLBACK_TIMEOUT_MS2, STATE_LENGTH;
|
|
9505
10774
|
var init_login = __esm({
|
|
9506
10775
|
"src/commands/login.ts"() {
|
|
9507
10776
|
"use strict";
|
|
9508
10777
|
CLI_LOGIN_PORT = 8420;
|
|
9509
10778
|
FRONTEND_URL = process.env.SUPATEST_FRONTEND_URL || "https://code.supatest.ai";
|
|
9510
10779
|
API_URL = process.env.SUPATEST_API_URL || "https://code-api.supatest.ai";
|
|
9511
|
-
|
|
10780
|
+
CALLBACK_TIMEOUT_MS2 = 3e5;
|
|
9512
10781
|
STATE_LENGTH = 32;
|
|
9513
10782
|
}
|
|
9514
10783
|
});
|
|
9515
10784
|
|
|
9516
10785
|
// src/utils/claude-max.ts
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
import { homedir as homedir6 } from "os";
|
|
9520
|
-
import { join as join8 } from "path";
|
|
9521
|
-
function isClaudeMaxAvailable() {
|
|
9522
|
-
const platform2 = process.platform;
|
|
9523
|
-
logger.debug("[claude-max] Checking Claude Code credentials", { platform: platform2 });
|
|
9524
|
-
if (platform2 === "darwin") {
|
|
9525
|
-
return checkMacOSKeychain();
|
|
9526
|
-
} else {
|
|
9527
|
-
return checkCredentialsFile();
|
|
9528
|
-
}
|
|
9529
|
-
}
|
|
9530
|
-
function checkMacOSKeychain() {
|
|
10786
|
+
async function isClaudeMaxAvailable() {
|
|
10787
|
+
logger.debug("[claude-max] Checking Supatest OAuth credentials");
|
|
9531
10788
|
try {
|
|
9532
|
-
const
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
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;
|
|
9539
10795
|
}
|
|
9540
|
-
|
|
9541
|
-
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
9542
|
-
logger.debug("[claude-max] macOS keychain credentials check", {
|
|
9543
|
-
hasOauth,
|
|
9544
|
-
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
9545
|
-
});
|
|
9546
|
-
return hasOauth;
|
|
9547
|
-
} catch (error) {
|
|
9548
|
-
logger.debug("[claude-max] Error checking macOS keychain", {
|
|
9549
|
-
error: error instanceof Error ? error.message : String(error)
|
|
9550
|
-
});
|
|
10796
|
+
logger.debug("[claude-max] No Supatest OAuth credentials found");
|
|
9551
10797
|
return false;
|
|
9552
|
-
}
|
|
9553
|
-
}
|
|
9554
|
-
function checkCredentialsFile() {
|
|
9555
|
-
try {
|
|
9556
|
-
const credentialsPath = join8(homedir6(), ".claude", ".credentials.json");
|
|
9557
|
-
if (!existsSync6(credentialsPath)) {
|
|
9558
|
-
logger.debug("[claude-max] Credentials file not found", { path: credentialsPath });
|
|
9559
|
-
return false;
|
|
9560
|
-
}
|
|
9561
|
-
const credentialsJson = readFileSync5(credentialsPath, "utf-8");
|
|
9562
|
-
const credentials = JSON.parse(credentialsJson);
|
|
9563
|
-
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
9564
|
-
logger.debug("[claude-max] Credentials file check", {
|
|
9565
|
-
path: credentialsPath,
|
|
9566
|
-
hasOauth,
|
|
9567
|
-
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
9568
|
-
});
|
|
9569
|
-
return hasOauth;
|
|
9570
10798
|
} catch (error) {
|
|
9571
|
-
logger.debug("[claude-max] Error checking
|
|
10799
|
+
logger.debug("[claude-max] Error checking Supatest OAuth storage", {
|
|
9572
10800
|
error: error instanceof Error ? error.message : String(error)
|
|
9573
10801
|
});
|
|
9574
10802
|
return false;
|
|
@@ -9577,12 +10805,14 @@ function checkCredentialsFile() {
|
|
|
9577
10805
|
var init_claude_max = __esm({
|
|
9578
10806
|
"src/utils/claude-max.ts"() {
|
|
9579
10807
|
"use strict";
|
|
10808
|
+
init_claude_oauth();
|
|
9580
10809
|
init_logger();
|
|
10810
|
+
init_secret_storage();
|
|
9581
10811
|
}
|
|
9582
10812
|
});
|
|
9583
10813
|
|
|
9584
10814
|
// src/utils/mcp-manager.ts
|
|
9585
|
-
import { existsSync as
|
|
10815
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
9586
10816
|
import { homedir as homedir7 } from "os";
|
|
9587
10817
|
import { join as join9 } from "path";
|
|
9588
10818
|
function getGlobalMcpPath() {
|
|
@@ -9592,11 +10822,11 @@ function getProjectMcpPath(cwd) {
|
|
|
9592
10822
|
return join9(cwd, ".supatest", "mcp.json");
|
|
9593
10823
|
}
|
|
9594
10824
|
function loadMcpConfigFromFile(mcpPath, scope) {
|
|
9595
|
-
if (!
|
|
10825
|
+
if (!existsSync6(mcpPath)) {
|
|
9596
10826
|
return {};
|
|
9597
10827
|
}
|
|
9598
10828
|
try {
|
|
9599
|
-
const content =
|
|
10829
|
+
const content = readFileSync5(mcpPath, "utf-8");
|
|
9600
10830
|
const config2 = JSON.parse(content);
|
|
9601
10831
|
if (!config2.mcpServers) {
|
|
9602
10832
|
return {};
|
|
@@ -9630,7 +10860,7 @@ function loadMcpConfig(cwd) {
|
|
|
9630
10860
|
}
|
|
9631
10861
|
function saveMcpConfigToFile(mcpPath, servers) {
|
|
9632
10862
|
const mcpDir = join9(mcpPath, "..");
|
|
9633
|
-
if (!
|
|
10863
|
+
if (!existsSync6(mcpDir)) {
|
|
9634
10864
|
mkdirSync3(mcpDir, { recursive: true });
|
|
9635
10865
|
}
|
|
9636
10866
|
const config2 = {
|
|
@@ -9814,15 +11044,15 @@ var init_mcp_manager = __esm({
|
|
|
9814
11044
|
});
|
|
9815
11045
|
|
|
9816
11046
|
// src/utils/settings-loader.ts
|
|
9817
|
-
import { existsSync as
|
|
11047
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
9818
11048
|
import { join as join10 } from "path";
|
|
9819
11049
|
function loadSupatestSettings(cwd) {
|
|
9820
11050
|
const settingsPath = join10(cwd, ".supatest", "settings.json");
|
|
9821
|
-
if (!
|
|
11051
|
+
if (!existsSync7(settingsPath)) {
|
|
9822
11052
|
return {};
|
|
9823
11053
|
}
|
|
9824
11054
|
try {
|
|
9825
|
-
const content =
|
|
11055
|
+
const content = readFileSync6(settingsPath, "utf-8");
|
|
9826
11056
|
return JSON.parse(content);
|
|
9827
11057
|
} catch (error) {
|
|
9828
11058
|
console.warn(
|
|
@@ -9836,7 +11066,7 @@ function saveSupatestSettings(cwd, settings) {
|
|
|
9836
11066
|
const settingsDir = join10(cwd, ".supatest");
|
|
9837
11067
|
const settingsPath = join10(settingsDir, "settings.json");
|
|
9838
11068
|
try {
|
|
9839
|
-
if (!
|
|
11069
|
+
if (!existsSync7(settingsDir)) {
|
|
9840
11070
|
mkdirSync4(settingsDir, { recursive: true });
|
|
9841
11071
|
}
|
|
9842
11072
|
const existingSettings = loadSupatestSettings(cwd);
|
|
@@ -11015,18 +12245,37 @@ var init_TestSelector = __esm({
|
|
|
11015
12245
|
apiClient,
|
|
11016
12246
|
run,
|
|
11017
12247
|
onSelect,
|
|
11018
|
-
onCancel
|
|
12248
|
+
onCancel,
|
|
12249
|
+
assignments = []
|
|
11019
12250
|
}) => {
|
|
11020
12251
|
const [allTests, setAllTests] = useState7([]);
|
|
11021
12252
|
const [selectedTests, setSelectedTests] = useState7(/* @__PURE__ */ new Set());
|
|
11022
12253
|
const [cursorIndex, setCursorIndex] = useState7(0);
|
|
11023
12254
|
const [isLoading, setIsLoading] = useState7(false);
|
|
12255
|
+
const [isLoadingAssignments, setIsLoadingAssignments] = useState7(true);
|
|
11024
12256
|
const [hasMore, setHasMore] = useState7(true);
|
|
11025
12257
|
const [totalTests, setTotalTests] = useState7(0);
|
|
11026
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));
|
|
11027
12263
|
useEffect7(() => {
|
|
11028
12264
|
loadMoreTests();
|
|
11029
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
|
+
};
|
|
11030
12279
|
const loadMoreTests = async () => {
|
|
11031
12280
|
if (isLoading || !hasMore) {
|
|
11032
12281
|
return;
|
|
@@ -11042,7 +12291,8 @@ var init_TestSelector = __esm({
|
|
|
11042
12291
|
// Only fetch failed tests
|
|
11043
12292
|
});
|
|
11044
12293
|
setTotalTests(result.total);
|
|
11045
|
-
|
|
12294
|
+
const loadedCount = allTests.length + result.tests.length;
|
|
12295
|
+
setHasMore(result.tests.length === PAGE_SIZE2 && loadedCount < result.total);
|
|
11046
12296
|
setAllTests((prev) => [...prev, ...result.tests]);
|
|
11047
12297
|
} catch (err) {
|
|
11048
12298
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -11051,8 +12301,34 @@ var init_TestSelector = __esm({
|
|
|
11051
12301
|
setIsLoading(false);
|
|
11052
12302
|
}
|
|
11053
12303
|
};
|
|
11054
|
-
const
|
|
11055
|
-
|
|
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;
|
|
11056
12332
|
const toggleTest = (testId) => {
|
|
11057
12333
|
setSelectedTests((prev) => {
|
|
11058
12334
|
const next = new Set(prev);
|
|
@@ -11064,50 +12340,92 @@ var init_TestSelector = __esm({
|
|
|
11064
12340
|
return next;
|
|
11065
12341
|
});
|
|
11066
12342
|
};
|
|
11067
|
-
const selectAll = () => {
|
|
11068
|
-
setSelectedTests(new Set(allTests.map((t) => t.id)));
|
|
11069
|
-
};
|
|
11070
12343
|
const selectNone = () => {
|
|
11071
12344
|
setSelectedTests(/* @__PURE__ */ new Set());
|
|
11072
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
|
+
};
|
|
11073
12365
|
useInput3((input, key) => {
|
|
11074
|
-
if (allTests.length === 0 && !isLoading) {
|
|
12366
|
+
if (allTests.length === 0 && !isLoading && !isLoadingAssignments) {
|
|
11075
12367
|
if (key.escape || input === "q") {
|
|
11076
12368
|
onCancel();
|
|
11077
12369
|
}
|
|
11078
12370
|
return;
|
|
11079
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
|
+
}
|
|
11080
12382
|
if (key.upArrow) {
|
|
11081
12383
|
setCursorIndex((prev) => prev > 0 ? prev - 1 : totalItems - 1);
|
|
11082
12384
|
} else if (key.downArrow) {
|
|
11083
12385
|
const newIndex = cursorIndex < totalItems - 1 ? cursorIndex + 1 : 0;
|
|
11084
12386
|
setCursorIndex(newIndex);
|
|
11085
|
-
if (newIndex >= allTests.length - 2 && hasMore && !isLoading) {
|
|
12387
|
+
if (!groupByFile && newIndex >= allTests.length - 2 && hasMore && !isLoading) {
|
|
11086
12388
|
loadMoreTests();
|
|
11087
12389
|
}
|
|
11088
12390
|
} else if (input === " ") {
|
|
11089
|
-
if (
|
|
11090
|
-
|
|
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
|
+
}
|
|
11091
12401
|
} else {
|
|
11092
12402
|
const testIndex = cursorIndex - 1;
|
|
11093
|
-
|
|
11094
|
-
|
|
12403
|
+
const filteredTests2 = getFilteredTests();
|
|
12404
|
+
if (filteredTests2[testIndex]) {
|
|
12405
|
+
const test = filteredTests2[testIndex];
|
|
12406
|
+
if (!assignedTestIds.has(test.id)) {
|
|
12407
|
+
toggleTest(test.id);
|
|
12408
|
+
}
|
|
11095
12409
|
}
|
|
11096
12410
|
}
|
|
11097
|
-
} else if (input === "a") {
|
|
11098
|
-
selectAll();
|
|
11099
12411
|
} else if (input === "n") {
|
|
11100
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);
|
|
11101
12417
|
} else if (key.return) {
|
|
11102
|
-
|
|
11103
|
-
|
|
12418
|
+
const availableTests = getFilteredTests();
|
|
12419
|
+
if (isOnFixNext10) {
|
|
12420
|
+
const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
|
|
12421
|
+
onSelect(next10);
|
|
11104
12422
|
} else if (selectedTests.size > 0) {
|
|
11105
|
-
const testsToFix =
|
|
12423
|
+
const testsToFix = availableTests.filter((t) => selectedTests.has(t.id));
|
|
11106
12424
|
onSelect(testsToFix);
|
|
11107
|
-
} else {
|
|
12425
|
+
} else if (!groupByFile) {
|
|
11108
12426
|
const testIndex = cursorIndex - 1;
|
|
11109
|
-
if (
|
|
11110
|
-
onSelect([
|
|
12427
|
+
if (availableTests[testIndex] && !assignedTestIds.has(availableTests[testIndex].id)) {
|
|
12428
|
+
onSelect([availableTests[testIndex]]);
|
|
11111
12429
|
}
|
|
11112
12430
|
}
|
|
11113
12431
|
} else if (key.escape || input === "q") {
|
|
@@ -11124,44 +12442,66 @@ var init_TestSelector = __esm({
|
|
|
11124
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")));
|
|
11125
12443
|
}
|
|
11126
12444
|
const testStartIndex = Math.max(0, cursorIndex - 1);
|
|
11127
|
-
const adjustedStart =
|
|
11128
|
-
const adjustedEnd = Math.min(
|
|
11129
|
-
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);
|
|
11130
12448
|
const branch = run.git?.branch || "unknown";
|
|
11131
12449
|
const commit = run.git?.commit?.slice(0, 7) || "";
|
|
11132
|
-
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(
|
|
11133
12451
|
Text16,
|
|
11134
12452
|
{
|
|
11135
|
-
backgroundColor:
|
|
11136
|
-
bold:
|
|
11137
|
-
color:
|
|
12453
|
+
backgroundColor: isOnFixNext10 ? theme.text.accent : void 0,
|
|
12454
|
+
bold: isOnFixNext10,
|
|
12455
|
+
color: isOnFixNext10 ? "black" : theme.text.primary
|
|
11138
12456
|
},
|
|
11139
|
-
|
|
11140
|
-
"[Fix
|
|
11141
|
-
|
|
11142
|
-
" Failed Test",
|
|
11143
|
-
allTests.length !== 1 ? "s" : "",
|
|
12457
|
+
isOnFixNext10 ? "\u25B6 " : " ",
|
|
12458
|
+
"[Fix Next 10 Available Test",
|
|
12459
|
+
Math.min(10, filteredTests.length) !== 1 ? "s" : "",
|
|
11144
12460
|
"]"
|
|
11145
|
-
)), /* @__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")),
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
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..."))));
|
|
11158
12498
|
};
|
|
11159
12499
|
}
|
|
11160
12500
|
});
|
|
11161
12501
|
|
|
11162
12502
|
// src/ui/components/FixFlow.tsx
|
|
11163
12503
|
import { Box as Box19, Text as Text17, useInput as useInput4 } from "ink";
|
|
11164
|
-
import React22, { useState as useState8 } from "react";
|
|
12504
|
+
import React22, { useEffect as useEffect8, useState as useState8 } from "react";
|
|
11165
12505
|
var FixFlow;
|
|
11166
12506
|
var init_FixFlow = __esm({
|
|
11167
12507
|
"src/ui/components/FixFlow.tsx"() {
|
|
@@ -11180,6 +12520,8 @@ var init_FixFlow = __esm({
|
|
|
11180
12520
|
const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
|
|
11181
12521
|
const [selectedRun, setSelectedRun] = useState8(null);
|
|
11182
12522
|
const [selectedTests, setSelectedTests] = useState8([]);
|
|
12523
|
+
const [assignments, setAssignments] = useState8([]);
|
|
12524
|
+
const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
|
|
11183
12525
|
const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
|
|
11184
12526
|
const [loadError, setLoadError] = useState8(null);
|
|
11185
12527
|
useInput4((input, key) => {
|
|
@@ -11189,7 +12531,7 @@ var init_FixFlow = __esm({
|
|
|
11189
12531
|
setStep("select-run");
|
|
11190
12532
|
} else if (step === "select-run") {
|
|
11191
12533
|
onCancel();
|
|
11192
|
-
} else if (step === "loading-details" && loadError) {
|
|
12534
|
+
} else if ((step === "loading-details" || step === "claiming-tests" || step === "error") && loadError) {
|
|
11193
12535
|
setLoadError(null);
|
|
11194
12536
|
setStep("select-tests");
|
|
11195
12537
|
}
|
|
@@ -11198,13 +12540,47 @@ var init_FixFlow = __esm({
|
|
|
11198
12540
|
const handleRunSelect = (run) => {
|
|
11199
12541
|
setSelectedRun(run);
|
|
11200
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
|
+
}
|
|
11201
12552
|
};
|
|
11202
12553
|
const handleTestsSelect = async (tests) => {
|
|
11203
12554
|
setSelectedTests(tests);
|
|
11204
|
-
setStep("
|
|
11205
|
-
setLoadingProgress({ current: 0, total: tests.length });
|
|
12555
|
+
setStep("claiming-tests");
|
|
11206
12556
|
setLoadError(null);
|
|
11207
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 });
|
|
11208
12584
|
const testDetails = [];
|
|
11209
12585
|
const batchSize = 5;
|
|
11210
12586
|
for (let i = 0; i < tests.length; i += batchSize) {
|
|
@@ -11220,7 +12596,25 @@ var init_FixFlow = __esm({
|
|
|
11220
12596
|
setStep("fixing");
|
|
11221
12597
|
onStartFix(prompt, tests);
|
|
11222
12598
|
} catch (err) {
|
|
11223
|
-
|
|
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");
|
|
11224
12618
|
}
|
|
11225
12619
|
};
|
|
11226
12620
|
const handleRunCancel = () => {
|
|
@@ -11228,8 +12622,47 @@ var init_FixFlow = __esm({
|
|
|
11228
12622
|
};
|
|
11229
12623
|
const handleTestCancel = () => {
|
|
11230
12624
|
setSelectedRun(null);
|
|
12625
|
+
setAssignments([]);
|
|
11231
12626
|
setStep("select-run");
|
|
11232
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]);
|
|
11233
12666
|
switch (step) {
|
|
11234
12667
|
case "select-run":
|
|
11235
12668
|
return /* @__PURE__ */ React22.createElement(
|
|
@@ -11249,20 +12682,25 @@ var init_FixFlow = __esm({
|
|
|
11249
12682
|
TestSelector,
|
|
11250
12683
|
{
|
|
11251
12684
|
apiClient,
|
|
12685
|
+
assignments,
|
|
11252
12686
|
onCancel: handleTestCancel,
|
|
11253
12687
|
onSelect: handleTestsSelect,
|
|
11254
12688
|
run: selectedRun
|
|
11255
12689
|
}
|
|
11256
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...")));
|
|
11257
12693
|
case "loading-details":
|
|
11258
12694
|
if (loadError) {
|
|
11259
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")));
|
|
11260
12696
|
}
|
|
11261
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")));
|
|
11262
12700
|
case "fixing":
|
|
11263
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...")));
|
|
11264
12702
|
case "complete":
|
|
11265
|
-
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...")));
|
|
11266
12704
|
default:
|
|
11267
12705
|
return null;
|
|
11268
12706
|
}
|
|
@@ -11270,91 +12708,144 @@ var init_FixFlow = __esm({
|
|
|
11270
12708
|
}
|
|
11271
12709
|
});
|
|
11272
12710
|
|
|
11273
|
-
// src/ui/utils/file-
|
|
11274
|
-
import fs4 from "fs";
|
|
12711
|
+
// src/ui/utils/file-search.ts
|
|
11275
12712
|
import path4 from "path";
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
const
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
if (
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
const relativePath = path4.relative(rootDir, fullPath);
|
|
11292
|
-
if (entry.isDirectory()) {
|
|
11293
|
-
scan(fullPath, depth + 1);
|
|
11294
|
-
} else {
|
|
11295
|
-
files.push(relativePath);
|
|
11296
|
-
}
|
|
11297
|
-
}
|
|
11298
|
-
} 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;
|
|
11299
12728
|
}
|
|
11300
12729
|
}
|
|
11301
|
-
|
|
11302
|
-
|
|
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);
|
|
11303
12770
|
}
|
|
11304
|
-
var
|
|
11305
|
-
|
|
11306
|
-
"src/ui/utils/file-completion.ts"() {
|
|
12771
|
+
var init_file_search = __esm({
|
|
12772
|
+
"src/ui/utils/file-search.ts"() {
|
|
11307
12773
|
"use strict";
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
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();
|
|
11352
12843
|
}
|
|
11353
12844
|
});
|
|
11354
12845
|
|
|
11355
12846
|
// src/ui/components/ModelSelector.tsx
|
|
11356
12847
|
import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
|
|
11357
|
-
import React23, { useState as
|
|
12848
|
+
import React23, { useState as useState10 } from "react";
|
|
11358
12849
|
function getAvailableModels(isClaudeMax) {
|
|
11359
12850
|
if (isClaudeMax) {
|
|
11360
12851
|
return AVAILABLE_MODELS.map((m) => ({
|
|
@@ -11389,7 +12880,7 @@ var init_ModelSelector = __esm({
|
|
|
11389
12880
|
}) => {
|
|
11390
12881
|
const models = getAvailableModels(isClaudeMax);
|
|
11391
12882
|
const currentIndex = models.findIndex((m) => m.id === currentModel);
|
|
11392
|
-
const [selectedIndex, setSelectedIndex] =
|
|
12883
|
+
const [selectedIndex, setSelectedIndex] = useState10(currentIndex >= 0 ? currentIndex : 0);
|
|
11393
12884
|
useInput5((input, key) => {
|
|
11394
12885
|
if (key.upArrow) {
|
|
11395
12886
|
setSelectedIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
|
|
@@ -11431,7 +12922,7 @@ var init_ModelSelector = __esm({
|
|
|
11431
12922
|
import path5 from "path";
|
|
11432
12923
|
import chalk4 from "chalk";
|
|
11433
12924
|
import { Box as Box21, Text as Text19 } from "ink";
|
|
11434
|
-
import React24, { forwardRef, memo as memo3, useEffect as
|
|
12925
|
+
import React24, { forwardRef, memo as memo3, useEffect as useEffect10, useImperativeHandle, useState as useState11 } from "react";
|
|
11435
12926
|
var InputPromptInner, InputPrompt;
|
|
11436
12927
|
var init_InputPrompt = __esm({
|
|
11437
12928
|
"src/ui/components/InputPrompt.tsx"() {
|
|
@@ -11439,13 +12930,13 @@ var init_InputPrompt = __esm({
|
|
|
11439
12930
|
init_shared_es();
|
|
11440
12931
|
init_command_discovery();
|
|
11441
12932
|
init_SessionContext();
|
|
12933
|
+
init_useFileCompletion();
|
|
11442
12934
|
init_useKeypress();
|
|
11443
|
-
init_file_completion();
|
|
11444
12935
|
init_theme();
|
|
11445
12936
|
init_ModelSelector();
|
|
11446
12937
|
InputPromptInner = forwardRef(({
|
|
11447
12938
|
onSubmit,
|
|
11448
|
-
placeholder = "Enter your task (
|
|
12939
|
+
placeholder = "Enter your task... (Ctrl+W=del word, Alt+\u2190/\u2192=word nav, Ctrl+U/K=clear)",
|
|
11449
12940
|
disabled = false,
|
|
11450
12941
|
onHelpToggle,
|
|
11451
12942
|
cwd,
|
|
@@ -11454,14 +12945,40 @@ var init_InputPrompt = __esm({
|
|
|
11454
12945
|
onInputChange,
|
|
11455
12946
|
isClaudeMax = false
|
|
11456
12947
|
}, ref) => {
|
|
11457
|
-
const { agentMode, selectedModel, setSelectedModel, isAgentRunning
|
|
11458
|
-
const
|
|
11459
|
-
const [
|
|
11460
|
-
const [
|
|
11461
|
-
const
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
12948
|
+
const { agentMode, selectedModel, setSelectedModel, isAgentRunning } = useSession();
|
|
12949
|
+
const { usageStats } = useUsageStats();
|
|
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("");
|
|
11465
12982
|
const BUILTIN_SLASH_COMMANDS = [
|
|
11466
12983
|
{ name: "/help", desc: "Show help" },
|
|
11467
12984
|
{ name: "/resume", desc: "Resume session" },
|
|
@@ -11477,9 +12994,9 @@ var init_InputPrompt = __esm({
|
|
|
11477
12994
|
{ name: "/logout", desc: "Log out" },
|
|
11478
12995
|
{ name: "/exit", desc: "Exit CLI" }
|
|
11479
12996
|
];
|
|
11480
|
-
const [customCommands, setCustomCommands] =
|
|
11481
|
-
const [isSlashCommand, setIsSlashCommand] =
|
|
11482
|
-
|
|
12997
|
+
const [customCommands, setCustomCommands] = useState11([]);
|
|
12998
|
+
const [isSlashCommand, setIsSlashCommand] = useState11(false);
|
|
12999
|
+
useEffect10(() => {
|
|
11483
13000
|
try {
|
|
11484
13001
|
const projectDir = cwd || process.cwd();
|
|
11485
13002
|
const discovered = discoverCommands(projectDir);
|
|
@@ -11495,29 +13012,55 @@ var init_InputPrompt = __esm({
|
|
|
11495
13012
|
useImperativeHandle(ref, () => ({
|
|
11496
13013
|
clear: () => {
|
|
11497
13014
|
setValue("");
|
|
11498
|
-
|
|
13015
|
+
setCursorPos([0, 0]);
|
|
11499
13016
|
setShowSuggestions(false);
|
|
11500
13017
|
onInputChange?.("");
|
|
11501
13018
|
}
|
|
11502
13019
|
}));
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
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]);
|
|
11512
13032
|
const updateValue = (newValue, newCursor) => {
|
|
11513
13033
|
setValue(newValue);
|
|
11514
|
-
|
|
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
|
+
})();
|
|
11515
13049
|
checkSuggestions(newValue, newCursor);
|
|
11516
13050
|
onInputChange?.(newValue);
|
|
11517
13051
|
};
|
|
11518
13052
|
const checkSuggestions = (text, cursor) => {
|
|
11519
|
-
|
|
11520
|
-
|
|
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);
|
|
11521
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);
|
|
11522
13065
|
const customMatches = customCommands.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
|
|
11523
13066
|
const matches = [];
|
|
@@ -11539,25 +13082,26 @@ var init_InputPrompt = __esm({
|
|
|
11539
13082
|
}
|
|
11540
13083
|
}
|
|
11541
13084
|
setIsSlashCommand(false);
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
}
|
|
11557
|
-
}
|
|
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;
|
|
11558
13099
|
}
|
|
11559
13100
|
}
|
|
11560
|
-
|
|
13101
|
+
if (!foundMention) {
|
|
13102
|
+
setMentionQuery("");
|
|
13103
|
+
setShowSuggestions(false);
|
|
13104
|
+
}
|
|
11561
13105
|
};
|
|
11562
13106
|
const completeSuggestion = (submit = false) => {
|
|
11563
13107
|
if (!showSuggestions || suggestions.length === 0) return;
|
|
@@ -11566,27 +13110,35 @@ var init_InputPrompt = __esm({
|
|
|
11566
13110
|
if (submit) {
|
|
11567
13111
|
onSubmit(selectedCmd);
|
|
11568
13112
|
setValue("");
|
|
11569
|
-
|
|
13113
|
+
setCursorPos([0, 0]);
|
|
11570
13114
|
setShowSuggestions(false);
|
|
11571
13115
|
onInputChange?.("");
|
|
11572
13116
|
return;
|
|
11573
13117
|
}
|
|
11574
|
-
updateValue(selectedCmd, selectedCmd.length);
|
|
13118
|
+
updateValue(selectedCmd, [0, selectedCmd.length]);
|
|
11575
13119
|
setShowSuggestions(false);
|
|
11576
13120
|
return;
|
|
11577
13121
|
}
|
|
11578
13122
|
const selectedFile = suggestions[activeSuggestion];
|
|
11579
|
-
const
|
|
11580
|
-
const
|
|
11581
|
-
|
|
11582
|
-
const
|
|
11583
|
-
|
|
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);
|
|
11584
13135
|
setShowSuggestions(false);
|
|
11585
13136
|
};
|
|
11586
13137
|
useKeypress(
|
|
11587
13138
|
(key) => {
|
|
11588
13139
|
if (disabled) return;
|
|
11589
13140
|
const input = key.sequence;
|
|
13141
|
+
const cursorOffset = getCursorOffset();
|
|
11590
13142
|
if (input.length > 1 && (input.includes("/") || input.includes("\\"))) {
|
|
11591
13143
|
let cleanPath = input.trim();
|
|
11592
13144
|
if (cleanPath.startsWith('"') && cleanPath.endsWith('"') || cleanPath.startsWith("'") && cleanPath.endsWith("'")) {
|
|
@@ -11610,7 +13162,7 @@ var init_InputPrompt = __esm({
|
|
|
11610
13162
|
return;
|
|
11611
13163
|
}
|
|
11612
13164
|
}
|
|
11613
|
-
if (key.shift && key.name === "m" && !isAgentRunning) {
|
|
13165
|
+
if ((key.ctrl || key.meta) && key.shift && key.name === "m" && !isAgentRunning) {
|
|
11614
13166
|
setSelectedModel(getNextModel(selectedModel, isClaudeMax));
|
|
11615
13167
|
return;
|
|
11616
13168
|
}
|
|
@@ -11659,7 +13211,7 @@ var init_InputPrompt = __esm({
|
|
|
11659
13211
|
if (value.trim()) {
|
|
11660
13212
|
onSubmit(value.trim());
|
|
11661
13213
|
setValue("");
|
|
11662
|
-
|
|
13214
|
+
setCursorPos([0, 0]);
|
|
11663
13215
|
setShowSuggestions(false);
|
|
11664
13216
|
onInputChange?.("");
|
|
11665
13217
|
}
|
|
@@ -11670,11 +13222,55 @@ var init_InputPrompt = __esm({
|
|
|
11670
13222
|
updateValue(newValue, cursorOffset - 1);
|
|
11671
13223
|
}
|
|
11672
13224
|
} else if (key.name === "left") {
|
|
11673
|
-
|
|
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
|
+
}
|
|
11674
13233
|
} else if (key.name === "right") {
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
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);
|
|
11678
13274
|
} else if (key.name === "tab" && !showSuggestions) {
|
|
11679
13275
|
} else if (key.paste) {
|
|
11680
13276
|
const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
|
|
@@ -11688,18 +13284,7 @@ var init_InputPrompt = __esm({
|
|
|
11688
13284
|
);
|
|
11689
13285
|
const lines = value ? value.split("\n") : [];
|
|
11690
13286
|
const hasContent = value.trim().length > 0;
|
|
11691
|
-
|
|
11692
|
-
let cursorCol = cursorOffset;
|
|
11693
|
-
let charCount = 0;
|
|
11694
|
-
for (let i = 0; i < lines.length; i++) {
|
|
11695
|
-
const lineLength = lines[i].length;
|
|
11696
|
-
if (charCount + lineLength >= cursorOffset) {
|
|
11697
|
-
cursorLine = i;
|
|
11698
|
-
cursorCol = cursorOffset - charCount;
|
|
11699
|
-
break;
|
|
11700
|
-
}
|
|
11701
|
-
charCount += lineLength + 1;
|
|
11702
|
-
}
|
|
13287
|
+
const [cursorLine, cursorCol] = cursorPos;
|
|
11703
13288
|
return /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React24.createElement(
|
|
11704
13289
|
Box21,
|
|
11705
13290
|
{
|
|
@@ -11709,13 +13294,13 @@ var init_InputPrompt = __esm({
|
|
|
11709
13294
|
marginBottom: 0,
|
|
11710
13295
|
paddingX: 1
|
|
11711
13296
|
},
|
|
11712
|
-
suggestions.map((item, idx) => {
|
|
13297
|
+
isLoadingFiles && !isSlashCommand ? /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "Searching files...") : suggestions.length > 0 ? suggestions.map((item, idx) => {
|
|
11713
13298
|
const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
|
|
11714
13299
|
if (isSeparator) {
|
|
11715
13300
|
return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, key: item }, " ", item);
|
|
11716
13301
|
}
|
|
11717
13302
|
return /* @__PURE__ */ React24.createElement(Text19, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
|
|
11718
|
-
})
|
|
13303
|
+
}) : /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "No matches")
|
|
11719
13304
|
), /* @__PURE__ */ React24.createElement(
|
|
11720
13305
|
Box21,
|
|
11721
13306
|
{
|
|
@@ -11736,7 +13321,7 @@ var init_InputPrompt = __esm({
|
|
|
11736
13321
|
}
|
|
11737
13322
|
return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.primary, key: idx }, line);
|
|
11738
13323
|
})), !hasContent && disabled && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
|
|
11739
|
-
), /* @__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", ")"))));
|
|
11740
13325
|
});
|
|
11741
13326
|
InputPromptInner.displayName = "InputPromptInner";
|
|
11742
13327
|
InputPrompt = memo3(InputPromptInner);
|
|
@@ -11746,19 +13331,19 @@ var init_InputPrompt = __esm({
|
|
|
11746
13331
|
|
|
11747
13332
|
// src/ui/components/McpAddDialog.tsx
|
|
11748
13333
|
import { Box as Box22, Text as Text20, useInput as useInput6 } from "ink";
|
|
11749
|
-
import React25, { useState as
|
|
13334
|
+
import React25, { useState as useState12 } from "react";
|
|
11750
13335
|
var McpAddDialog;
|
|
11751
13336
|
var init_McpAddDialog = __esm({
|
|
11752
13337
|
"src/ui/components/McpAddDialog.tsx"() {
|
|
11753
13338
|
"use strict";
|
|
11754
13339
|
init_theme();
|
|
11755
13340
|
McpAddDialog = ({ onConfirm, onCancel }) => {
|
|
11756
|
-
const [mode, setMode] =
|
|
11757
|
-
const [serverName, setServerName] =
|
|
11758
|
-
const [command, setCommand] =
|
|
11759
|
-
const [args, setArgs] =
|
|
11760
|
-
const [description, setDescription] =
|
|
11761
|
-
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");
|
|
11762
13347
|
useInput6((input, key) => {
|
|
11763
13348
|
if (key.escape) {
|
|
11764
13349
|
onCancel();
|
|
@@ -11853,7 +13438,7 @@ var init_McpAddDialog = __esm({
|
|
|
11853
13438
|
|
|
11854
13439
|
// src/ui/components/McpServerSelector.tsx
|
|
11855
13440
|
import { Box as Box23, Text as Text21, useInput as useInput7 } from "ink";
|
|
11856
|
-
import React26, { useState as
|
|
13441
|
+
import React26, { useState as useState13 } from "react";
|
|
11857
13442
|
var McpServerSelector;
|
|
11858
13443
|
var init_McpServerSelector = __esm({
|
|
11859
13444
|
"src/ui/components/McpServerSelector.tsx"() {
|
|
@@ -11865,7 +13450,7 @@ var init_McpServerSelector = __esm({
|
|
|
11865
13450
|
onSelect,
|
|
11866
13451
|
onCancel
|
|
11867
13452
|
}) => {
|
|
11868
|
-
const [selectedIndex, setSelectedIndex] =
|
|
13453
|
+
const [selectedIndex, setSelectedIndex] = useState13(0);
|
|
11869
13454
|
useInput7((input, key) => {
|
|
11870
13455
|
if (key.escape) {
|
|
11871
13456
|
onCancel();
|
|
@@ -11924,7 +13509,7 @@ var init_McpServerSelector = __esm({
|
|
|
11924
13509
|
// src/ui/components/McpServersDisplay.tsx
|
|
11925
13510
|
import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
|
|
11926
13511
|
import Spinner3 from "ink-spinner";
|
|
11927
|
-
import React27, { useEffect as
|
|
13512
|
+
import React27, { useEffect as useEffect11, useState as useState14 } from "react";
|
|
11928
13513
|
var McpServersDisplay;
|
|
11929
13514
|
var init_McpServersDisplay = __esm({
|
|
11930
13515
|
"src/ui/components/McpServersDisplay.tsx"() {
|
|
@@ -11938,9 +13523,9 @@ var init_McpServersDisplay = __esm({
|
|
|
11938
13523
|
onTest,
|
|
11939
13524
|
cwd
|
|
11940
13525
|
}) => {
|
|
11941
|
-
const [servers, setServers] =
|
|
11942
|
-
const [isTestingAll, setIsTestingAll] =
|
|
11943
|
-
|
|
13526
|
+
const [servers, setServers] = useState14([]);
|
|
13527
|
+
const [isTestingAll, setIsTestingAll] = useState14(false);
|
|
13528
|
+
useEffect11(() => {
|
|
11944
13529
|
const projectDir = cwd || process.cwd();
|
|
11945
13530
|
const loadedServers = loadMcpConfig(projectDir);
|
|
11946
13531
|
const serversArray = Object.values(loadedServers).map((server) => ({
|
|
@@ -12010,7 +13595,7 @@ var init_McpServersDisplay = __esm({
|
|
|
12010
13595
|
|
|
12011
13596
|
// src/ui/components/ProviderSelector.tsx
|
|
12012
13597
|
import { Box as Box25, Text as Text23, useInput as useInput9 } from "ink";
|
|
12013
|
-
import React28, { useState as
|
|
13598
|
+
import React28, { useState as useState15 } from "react";
|
|
12014
13599
|
var PROVIDERS, ProviderSelector;
|
|
12015
13600
|
var init_ProviderSelector = __esm({
|
|
12016
13601
|
"src/ui/components/ProviderSelector.tsx"() {
|
|
@@ -12025,7 +13610,7 @@ var init_ProviderSelector = __esm({
|
|
|
12025
13610
|
{
|
|
12026
13611
|
id: "claude-max",
|
|
12027
13612
|
name: "Claude Max",
|
|
12028
|
-
description: "Direct Claude Max subscription (requires
|
|
13613
|
+
description: "Direct Claude Max subscription (requires OAuth login)"
|
|
12029
13614
|
}
|
|
12030
13615
|
];
|
|
12031
13616
|
ProviderSelector = ({
|
|
@@ -12040,7 +13625,7 @@ var init_ProviderSelector = __esm({
|
|
|
12040
13625
|
const currentIndex = availableProviders.findIndex(
|
|
12041
13626
|
(p) => p.id === currentProvider
|
|
12042
13627
|
);
|
|
12043
|
-
const [selectedIndex, setSelectedIndex] =
|
|
13628
|
+
const [selectedIndex, setSelectedIndex] = useState15(
|
|
12044
13629
|
currentIndex >= 0 ? currentIndex : 0
|
|
12045
13630
|
);
|
|
12046
13631
|
useInput9((input, key) => {
|
|
@@ -12080,9 +13665,125 @@ var init_ProviderSelector = __esm({
|
|
|
12080
13665
|
}
|
|
12081
13666
|
});
|
|
12082
13667
|
|
|
12083
|
-
// src/ui/components/
|
|
13668
|
+
// src/ui/components/QuestionSelector.tsx
|
|
12084
13669
|
import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
|
|
12085
|
-
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";
|
|
12086
13787
|
function getSessionPrefix(authMethod) {
|
|
12087
13788
|
return authMethod === "api-key" ? "[Team]" : "[Me]";
|
|
12088
13789
|
}
|
|
@@ -12097,13 +13798,13 @@ var init_SessionSelector = __esm({
|
|
|
12097
13798
|
onSelect,
|
|
12098
13799
|
onCancel
|
|
12099
13800
|
}) => {
|
|
12100
|
-
const [allSessions, setAllSessions] =
|
|
12101
|
-
const [selectedIndex, setSelectedIndex] =
|
|
12102
|
-
const [isLoading, setIsLoading] =
|
|
12103
|
-
const [hasMore, setHasMore] =
|
|
12104
|
-
const [totalSessions, setTotalSessions] =
|
|
12105
|
-
const [error, setError] =
|
|
12106
|
-
|
|
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(() => {
|
|
12107
13808
|
loadMoreSessions();
|
|
12108
13809
|
}, []);
|
|
12109
13810
|
const loadMoreSessions = async () => {
|
|
@@ -12129,7 +13830,7 @@ var init_SessionSelector = __esm({
|
|
|
12129
13830
|
setIsLoading(false);
|
|
12130
13831
|
}
|
|
12131
13832
|
};
|
|
12132
|
-
|
|
13833
|
+
useInput11((input, key) => {
|
|
12133
13834
|
if (allSessions.length === 0) {
|
|
12134
13835
|
if (key.escape || input === "q") {
|
|
12135
13836
|
onCancel();
|
|
@@ -12153,13 +13854,13 @@ var init_SessionSelector = __esm({
|
|
|
12153
13854
|
}
|
|
12154
13855
|
});
|
|
12155
13856
|
if (error) {
|
|
12156
|
-
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")));
|
|
12157
13858
|
}
|
|
12158
13859
|
if (allSessions.length === 0 && isLoading) {
|
|
12159
|
-
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"));
|
|
12160
13861
|
}
|
|
12161
13862
|
if (allSessions.length === 0 && !isLoading) {
|
|
12162
|
-
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")));
|
|
12163
13864
|
}
|
|
12164
13865
|
const VISIBLE_ITEMS3 = 10;
|
|
12165
13866
|
let startIndex;
|
|
@@ -12179,7 +13880,7 @@ var init_SessionSelector = __esm({
|
|
|
12179
13880
|
const visibleSessions = allSessions.slice(startIndex, endIndex);
|
|
12180
13881
|
const MAX_TITLE_WIDTH = 50;
|
|
12181
13882
|
const PREFIX_WIDTH = 6;
|
|
12182
|
-
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) => {
|
|
12183
13884
|
const actualIndex = startIndex + index;
|
|
12184
13885
|
const isSelected = actualIndex === selectedIndex;
|
|
12185
13886
|
const title = item.session.title || "Untitled session";
|
|
@@ -12203,18 +13904,18 @@ var init_SessionSelector = __esm({
|
|
|
12203
13904
|
const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
|
|
12204
13905
|
const indicator = isSelected ? "\u25B6 " : " ";
|
|
12205
13906
|
const bgColor = isSelected ? theme.text.accent : void 0;
|
|
12206
|
-
return /* @__PURE__ */
|
|
12207
|
-
})), /* @__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..."))));
|
|
12208
13909
|
};
|
|
12209
13910
|
}
|
|
12210
13911
|
});
|
|
12211
13912
|
|
|
12212
13913
|
// src/ui/hooks/useModeToggle.ts
|
|
12213
|
-
import { useEffect as
|
|
13914
|
+
import { useEffect as useEffect13 } from "react";
|
|
12214
13915
|
function useModeToggle() {
|
|
12215
13916
|
const { subscribe, unsubscribe } = useKeypressContext();
|
|
12216
13917
|
const { agentMode, setAgentMode, isAgentRunning } = useSession();
|
|
12217
|
-
|
|
13918
|
+
useEffect13(() => {
|
|
12218
13919
|
const handleKeypress = (key) => {
|
|
12219
13920
|
if (key.name === "tab" && key.shift && !isAgentRunning) {
|
|
12220
13921
|
const newMode = agentMode === "plan" ? "build" : "plan";
|
|
@@ -12235,13 +13936,13 @@ var init_useModeToggle = __esm({
|
|
|
12235
13936
|
});
|
|
12236
13937
|
|
|
12237
13938
|
// src/ui/hooks/useOverlayEscapeGuard.ts
|
|
12238
|
-
import { useCallback as useCallback4, useMemo as useMemo4, useRef as
|
|
13939
|
+
import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef7 } from "react";
|
|
12239
13940
|
var useOverlayEscapeGuard;
|
|
12240
13941
|
var init_useOverlayEscapeGuard = __esm({
|
|
12241
13942
|
"src/ui/hooks/useOverlayEscapeGuard.ts"() {
|
|
12242
13943
|
"use strict";
|
|
12243
13944
|
useOverlayEscapeGuard = ({ overlays, suppressionMs = 250 }) => {
|
|
12244
|
-
const suppressUntilRef =
|
|
13945
|
+
const suppressUntilRef = useRef7(0);
|
|
12245
13946
|
const markOverlayClosed = useCallback4(() => {
|
|
12246
13947
|
suppressUntilRef.current = Date.now() + suppressionMs;
|
|
12247
13948
|
}, [suppressionMs]);
|
|
@@ -12253,11 +13954,11 @@ var init_useOverlayEscapeGuard = __esm({
|
|
|
12253
13954
|
});
|
|
12254
13955
|
|
|
12255
13956
|
// src/ui/App.tsx
|
|
12256
|
-
import { execSync as
|
|
13957
|
+
import { execSync as execSync5 } from "child_process";
|
|
12257
13958
|
import { homedir as homedir8 } from "os";
|
|
12258
|
-
import { Box as
|
|
13959
|
+
import { Box as Box28, Text as Text26, useApp as useApp2, useStdout as useStdout2 } from "ink";
|
|
12259
13960
|
import Spinner4 from "ink-spinner";
|
|
12260
|
-
import
|
|
13961
|
+
import React31, { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef8, useState as useState18 } from "react";
|
|
12261
13962
|
var getGitBranch2, getCurrentFolder2, AppContent, App;
|
|
12262
13963
|
var init_App = __esm({
|
|
12263
13964
|
"src/ui/App.tsx"() {
|
|
@@ -12284,6 +13985,7 @@ var init_App = __esm({
|
|
|
12284
13985
|
init_MessageList();
|
|
12285
13986
|
init_ModelSelector();
|
|
12286
13987
|
init_ProviderSelector();
|
|
13988
|
+
init_QuestionSelector();
|
|
12287
13989
|
init_SessionSelector();
|
|
12288
13990
|
init_SessionContext();
|
|
12289
13991
|
init_useKeypress();
|
|
@@ -12293,7 +13995,7 @@ var init_App = __esm({
|
|
|
12293
13995
|
init_theme();
|
|
12294
13996
|
getGitBranch2 = () => {
|
|
12295
13997
|
try {
|
|
12296
|
-
return
|
|
13998
|
+
return execSync5("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
|
|
12297
13999
|
} catch {
|
|
12298
14000
|
return "";
|
|
12299
14001
|
}
|
|
@@ -12306,36 +14008,37 @@ var init_App = __esm({
|
|
|
12306
14008
|
}
|
|
12307
14009
|
return cwd;
|
|
12308
14010
|
};
|
|
12309
|
-
AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
|
|
14011
|
+
AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession, onQuestionAnswer }) => {
|
|
12310
14012
|
const { exit } = useApp2();
|
|
12311
14013
|
const { stdout } = useStdout2();
|
|
12312
|
-
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();
|
|
12313
14015
|
useModeToggle();
|
|
12314
|
-
const [terminalWidth, setTerminalWidth] =
|
|
12315
|
-
const [showInput, setShowInput] =
|
|
12316
|
-
const [gitBranch] =
|
|
12317
|
-
const [currentFolder] =
|
|
12318
|
-
const hasInputContentRef =
|
|
12319
|
-
const [exitWarning, setExitWarning] =
|
|
12320
|
-
const inputPromptRef =
|
|
12321
|
-
const [showSessionSelector, setShowSessionSelector] =
|
|
12322
|
-
const [showModelSelector, setShowModelSelector] =
|
|
12323
|
-
const [showProviderSelector, setShowProviderSelector] =
|
|
12324
|
-
const [
|
|
12325
|
-
const [
|
|
12326
|
-
const [
|
|
12327
|
-
const [
|
|
12328
|
-
const [
|
|
12329
|
-
const [
|
|
12330
|
-
const [
|
|
12331
|
-
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(
|
|
12332
14035
|
"remove"
|
|
12333
14036
|
);
|
|
12334
|
-
const [mcpServers, setMcpServers] =
|
|
12335
|
-
const [authState, setAuthState] =
|
|
14037
|
+
const [mcpServers, setMcpServers] = useState18([]);
|
|
14038
|
+
const [authState, setAuthState] = useState18(
|
|
12336
14039
|
() => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
|
|
12337
14040
|
);
|
|
12338
|
-
const [showAuthDialog, setShowAuthDialog] =
|
|
14041
|
+
const [showAuthDialog, setShowAuthDialog] = useState18(false);
|
|
12339
14042
|
const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
|
|
12340
14043
|
overlays: [
|
|
12341
14044
|
showSessionSelector,
|
|
@@ -12346,15 +14049,16 @@ var init_App = __esm({
|
|
|
12346
14049
|
showFixFlow,
|
|
12347
14050
|
showMcpServers,
|
|
12348
14051
|
showMcpAdd,
|
|
12349
|
-
showMcpSelector
|
|
14052
|
+
showMcpSelector,
|
|
14053
|
+
!!pendingQuestion
|
|
12350
14054
|
]
|
|
12351
14055
|
});
|
|
12352
|
-
|
|
14056
|
+
useEffect14(() => {
|
|
12353
14057
|
if (!config2.supatestApiKey) {
|
|
12354
14058
|
setShowAuthDialog(true);
|
|
12355
14059
|
}
|
|
12356
14060
|
}, [config2.supatestApiKey]);
|
|
12357
|
-
|
|
14061
|
+
useEffect14(() => {
|
|
12358
14062
|
if (sessionId) {
|
|
12359
14063
|
setSessionId(sessionId);
|
|
12360
14064
|
}
|
|
@@ -12369,6 +14073,7 @@ var init_App = __esm({
|
|
|
12369
14073
|
type: "assistant",
|
|
12370
14074
|
content: "Opening browser for authentication..."
|
|
12371
14075
|
});
|
|
14076
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
12372
14077
|
try {
|
|
12373
14078
|
const result = await loginCommand();
|
|
12374
14079
|
saveToken(result.token, result.expiresAt);
|
|
@@ -12463,6 +14168,7 @@ var init_App = __esm({
|
|
|
12463
14168
|
return;
|
|
12464
14169
|
}
|
|
12465
14170
|
if (command === "/provider") {
|
|
14171
|
+
isClaudeMaxAvailable().then(setClaudeMaxAvailable);
|
|
12466
14172
|
setShowProviderSelector(true);
|
|
12467
14173
|
return;
|
|
12468
14174
|
}
|
|
@@ -12515,6 +14221,7 @@ var init_App = __esm({
|
|
|
12515
14221
|
type: "assistant",
|
|
12516
14222
|
content: "Running setup..."
|
|
12517
14223
|
});
|
|
14224
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
12518
14225
|
try {
|
|
12519
14226
|
const result = await setupCommand({ cwd: config2.cwd || process.cwd() });
|
|
12520
14227
|
addMessage({
|
|
@@ -12608,9 +14315,45 @@ var init_App = __esm({
|
|
|
12608
14315
|
markOverlayClosed();
|
|
12609
14316
|
setShowModelSelector(false);
|
|
12610
14317
|
};
|
|
12611
|
-
const handleProviderSelect = (provider) => {
|
|
14318
|
+
const handleProviderSelect = async (provider) => {
|
|
12612
14319
|
setShowProviderSelector(false);
|
|
12613
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
|
+
}
|
|
12614
14357
|
setLlmProvider(provider);
|
|
12615
14358
|
saveSupatestSettings(config2.cwd || process.cwd(), { llmProvider: provider });
|
|
12616
14359
|
addMessage({
|
|
@@ -12661,6 +14404,38 @@ var init_App = __esm({
|
|
|
12661
14404
|
markOverlayClosed();
|
|
12662
14405
|
setShowFeedbackDialog(false);
|
|
12663
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
|
+
};
|
|
12664
14439
|
const handleFixFlowCancel = () => {
|
|
12665
14440
|
markOverlayClosed();
|
|
12666
14441
|
setShowFixFlow(false);
|
|
@@ -12800,8 +14575,8 @@ var init_App = __esm({
|
|
|
12800
14575
|
markOverlayClosed();
|
|
12801
14576
|
setShowMcpServers(true);
|
|
12802
14577
|
};
|
|
12803
|
-
const isInitialMount =
|
|
12804
|
-
|
|
14578
|
+
const isInitialMount = useRef8(true);
|
|
14579
|
+
useEffect14(() => {
|
|
12805
14580
|
const handleResize = () => {
|
|
12806
14581
|
setTerminalWidth(process.stdout.columns || 80);
|
|
12807
14582
|
};
|
|
@@ -12810,7 +14585,7 @@ var init_App = __esm({
|
|
|
12810
14585
|
process.stdout.off("resize", handleResize);
|
|
12811
14586
|
};
|
|
12812
14587
|
}, []);
|
|
12813
|
-
|
|
14588
|
+
useEffect14(() => {
|
|
12814
14589
|
if (isInitialMount.current) {
|
|
12815
14590
|
isInitialMount.current = false;
|
|
12816
14591
|
return;
|
|
@@ -12872,7 +14647,7 @@ var init_App = __esm({
|
|
|
12872
14647
|
},
|
|
12873
14648
|
{ isActive: !isOverlayOpen }
|
|
12874
14649
|
);
|
|
12875
|
-
|
|
14650
|
+
useEffect14(() => {
|
|
12876
14651
|
if (config2.task) {
|
|
12877
14652
|
addMessage({
|
|
12878
14653
|
type: "user",
|
|
@@ -12880,14 +14655,14 @@ var init_App = __esm({
|
|
|
12880
14655
|
});
|
|
12881
14656
|
}
|
|
12882
14657
|
}, []);
|
|
12883
|
-
return /* @__PURE__ */
|
|
12884
|
-
|
|
14658
|
+
return /* @__PURE__ */ React31.createElement(
|
|
14659
|
+
Box28,
|
|
12885
14660
|
{
|
|
12886
14661
|
flexDirection: "column",
|
|
12887
14662
|
paddingX: 1
|
|
12888
14663
|
},
|
|
12889
|
-
/* @__PURE__ */
|
|
12890
|
-
showSessionSelector && apiClient && /* @__PURE__ */
|
|
14664
|
+
/* @__PURE__ */ React31.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
|
|
14665
|
+
showSessionSelector && apiClient && /* @__PURE__ */ React31.createElement(
|
|
12891
14666
|
SessionSelector,
|
|
12892
14667
|
{
|
|
12893
14668
|
apiClient,
|
|
@@ -12895,8 +14670,8 @@ var init_App = __esm({
|
|
|
12895
14670
|
onSelect: handleSessionSelect
|
|
12896
14671
|
}
|
|
12897
14672
|
),
|
|
12898
|
-
isLoadingSession && /* @__PURE__ */
|
|
12899
|
-
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(
|
|
12900
14675
|
ModelSelector,
|
|
12901
14676
|
{
|
|
12902
14677
|
currentModel: selectedModel,
|
|
@@ -12905,29 +14680,29 @@ var init_App = __esm({
|
|
|
12905
14680
|
onSelect: handleModelSelect
|
|
12906
14681
|
}
|
|
12907
14682
|
),
|
|
12908
|
-
showProviderSelector && /* @__PURE__ */
|
|
14683
|
+
showProviderSelector && /* @__PURE__ */ React31.createElement(
|
|
12909
14684
|
ProviderSelector,
|
|
12910
14685
|
{
|
|
12911
|
-
claudeMaxAvailable
|
|
14686
|
+
claudeMaxAvailable,
|
|
12912
14687
|
currentProvider: llmProvider,
|
|
12913
14688
|
onCancel: handleProviderSelectorCancel,
|
|
12914
14689
|
onSelect: handleProviderSelect
|
|
12915
14690
|
}
|
|
12916
14691
|
),
|
|
12917
|
-
showAuthDialog && /* @__PURE__ */
|
|
14692
|
+
showAuthDialog && /* @__PURE__ */ React31.createElement(
|
|
12918
14693
|
AuthDialog,
|
|
12919
14694
|
{
|
|
12920
14695
|
onLogin: handleLogin
|
|
12921
14696
|
}
|
|
12922
14697
|
),
|
|
12923
|
-
showFeedbackDialog && /* @__PURE__ */
|
|
14698
|
+
showFeedbackDialog && /* @__PURE__ */ React31.createElement(
|
|
12924
14699
|
FeedbackDialog,
|
|
12925
14700
|
{
|
|
12926
14701
|
onCancel: handleFeedbackCancel,
|
|
12927
14702
|
onSubmit: handleFeedbackSubmit
|
|
12928
14703
|
}
|
|
12929
14704
|
),
|
|
12930
|
-
showFixFlow && apiClient && /* @__PURE__ */
|
|
14705
|
+
showFixFlow && apiClient && /* @__PURE__ */ React31.createElement(
|
|
12931
14706
|
FixFlow,
|
|
12932
14707
|
{
|
|
12933
14708
|
apiClient,
|
|
@@ -12937,7 +14712,7 @@ var init_App = __esm({
|
|
|
12937
14712
|
onStartFix: handleFixStart
|
|
12938
14713
|
}
|
|
12939
14714
|
),
|
|
12940
|
-
showMcpServers && /* @__PURE__ */
|
|
14715
|
+
showMcpServers && /* @__PURE__ */ React31.createElement(
|
|
12941
14716
|
McpServersDisplay,
|
|
12942
14717
|
{
|
|
12943
14718
|
cwd: config2.cwd,
|
|
@@ -12947,8 +14722,8 @@ var init_App = __esm({
|
|
|
12947
14722
|
onTest: handleMcpTest
|
|
12948
14723
|
}
|
|
12949
14724
|
),
|
|
12950
|
-
showMcpAdd && /* @__PURE__ */
|
|
12951
|
-
showMcpSelector && /* @__PURE__ */
|
|
14725
|
+
showMcpAdd && /* @__PURE__ */ React31.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
|
|
14726
|
+
showMcpSelector && /* @__PURE__ */ React31.createElement(
|
|
12952
14727
|
McpServerSelector,
|
|
12953
14728
|
{
|
|
12954
14729
|
action: mcpSelectorAction,
|
|
@@ -12957,7 +14732,17 @@ var init_App = __esm({
|
|
|
12957
14732
|
servers: mcpServers
|
|
12958
14733
|
}
|
|
12959
14734
|
),
|
|
12960
|
-
|
|
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(
|
|
12961
14746
|
InputPrompt,
|
|
12962
14747
|
{
|
|
12963
14748
|
currentFolder,
|
|
@@ -12973,13 +14758,13 @@ var init_App = __esm({
|
|
|
12973
14758
|
);
|
|
12974
14759
|
};
|
|
12975
14760
|
App = (props) => {
|
|
12976
|
-
return /* @__PURE__ */
|
|
14761
|
+
return /* @__PURE__ */ React31.createElement(AppContent, { ...props });
|
|
12977
14762
|
};
|
|
12978
14763
|
}
|
|
12979
14764
|
});
|
|
12980
14765
|
|
|
12981
14766
|
// src/ui/hooks/useBracketedPaste.ts
|
|
12982
|
-
import { useEffect as
|
|
14767
|
+
import { useEffect as useEffect15 } from "react";
|
|
12983
14768
|
var ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, useBracketedPaste;
|
|
12984
14769
|
var init_useBracketedPaste = __esm({
|
|
12985
14770
|
"src/ui/hooks/useBracketedPaste.ts"() {
|
|
@@ -12988,7 +14773,7 @@ var init_useBracketedPaste = __esm({
|
|
|
12988
14773
|
ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
|
|
12989
14774
|
DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
|
|
12990
14775
|
useBracketedPaste = () => {
|
|
12991
|
-
|
|
14776
|
+
useEffect15(() => {
|
|
12992
14777
|
writeToStdout(ENABLE_BRACKETED_PASTE);
|
|
12993
14778
|
const cleanup = () => {
|
|
12994
14779
|
writeToStdout(DISABLE_BRACKETED_PASTE);
|
|
@@ -13009,7 +14794,7 @@ __export(interactive_exports, {
|
|
|
13009
14794
|
runInteractive: () => runInteractive
|
|
13010
14795
|
});
|
|
13011
14796
|
import { render as render2 } from "ink";
|
|
13012
|
-
import
|
|
14797
|
+
import React32, { useEffect as useEffect16, useRef as useRef9 } from "react";
|
|
13013
14798
|
function getToolDescription2(toolName, input) {
|
|
13014
14799
|
switch (toolName) {
|
|
13015
14800
|
case "Read":
|
|
@@ -13142,7 +14927,7 @@ async function runInteractive(config2) {
|
|
|
13142
14927
|
webUrl = session.webUrl;
|
|
13143
14928
|
}
|
|
13144
14929
|
const { unmount, waitUntilExit } = render2(
|
|
13145
|
-
/* @__PURE__ */
|
|
14930
|
+
/* @__PURE__ */ React32.createElement(
|
|
13146
14931
|
InteractiveApp,
|
|
13147
14932
|
{
|
|
13148
14933
|
apiClient,
|
|
@@ -13158,11 +14943,14 @@ async function runInteractive(config2) {
|
|
|
13158
14943
|
stdout: inkStdout,
|
|
13159
14944
|
stderr: inkStderr,
|
|
13160
14945
|
stdin: process.stdin,
|
|
14946
|
+
// Keep alternateBuffer: false for native terminal scrollback and text selection
|
|
13161
14947
|
alternateBuffer: false,
|
|
13162
|
-
// Like Gemini CLI - allows terminal to handle scroll & selection
|
|
13163
14948
|
exitOnCtrlC: false,
|
|
13164
|
-
//
|
|
13165
|
-
|
|
14949
|
+
// Disable incrementalRendering - it doesn't work well without alternateBuffer
|
|
14950
|
+
// and can cause more flickering issues
|
|
14951
|
+
incrementalRendering: false,
|
|
14952
|
+
// Prevent Ink from patching console methods
|
|
14953
|
+
patchConsole: false
|
|
13166
14954
|
}
|
|
13167
14955
|
);
|
|
13168
14956
|
unmountFn = unmount;
|
|
@@ -13204,7 +14992,7 @@ var init_interactive = __esm({
|
|
|
13204
14992
|
init_settings_loader();
|
|
13205
14993
|
init_stdio();
|
|
13206
14994
|
init_version();
|
|
13207
|
-
AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete }) => {
|
|
14995
|
+
AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete, onAgentCreated }) => {
|
|
13208
14996
|
const {
|
|
13209
14997
|
addMessage,
|
|
13210
14998
|
updateLastMessage,
|
|
@@ -13212,23 +15000,24 @@ var init_interactive = __esm({
|
|
|
13212
15000
|
setIsAgentRunning,
|
|
13213
15001
|
updateStats,
|
|
13214
15002
|
setTodos,
|
|
13215
|
-
setUsageStats,
|
|
13216
15003
|
shouldInterruptAgent,
|
|
13217
15004
|
setShouldInterruptAgent,
|
|
13218
15005
|
agentMode,
|
|
13219
15006
|
setAgentMode,
|
|
13220
15007
|
planFilePath,
|
|
13221
15008
|
selectedModel,
|
|
13222
|
-
llmProvider
|
|
15009
|
+
llmProvider,
|
|
15010
|
+
setPendingQuestion
|
|
13223
15011
|
} = useSession();
|
|
13224
|
-
const
|
|
13225
|
-
|
|
15012
|
+
const { setUsageStats } = useUsageStats();
|
|
15013
|
+
const agentRef = useRef9(null);
|
|
15014
|
+
useEffect16(() => {
|
|
13226
15015
|
if (shouldInterruptAgent && agentRef.current) {
|
|
13227
15016
|
agentRef.current.abort();
|
|
13228
15017
|
setShouldInterruptAgent(false);
|
|
13229
15018
|
}
|
|
13230
15019
|
}, [shouldInterruptAgent, setShouldInterruptAgent]);
|
|
13231
|
-
|
|
15020
|
+
useEffect16(() => {
|
|
13232
15021
|
let isMounted = true;
|
|
13233
15022
|
const runAgent2 = async () => {
|
|
13234
15023
|
setIsAgentRunning(true);
|
|
@@ -13270,6 +15059,16 @@ var init_interactive = __esm({
|
|
|
13270
15059
|
},
|
|
13271
15060
|
onTurnComplete: () => {
|
|
13272
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
|
+
}
|
|
13273
15072
|
}
|
|
13274
15073
|
},
|
|
13275
15074
|
apiClient,
|
|
@@ -13278,9 +15077,18 @@ var init_interactive = __esm({
|
|
|
13278
15077
|
);
|
|
13279
15078
|
let oauthToken;
|
|
13280
15079
|
if (llmProvider === "claude-max") {
|
|
13281
|
-
if (isClaudeMaxAvailable()) {
|
|
13282
|
-
|
|
13283
|
-
|
|
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
|
+
}
|
|
13284
15092
|
} else {
|
|
13285
15093
|
logger.warn("Claude Max selected but not available. Falling back to Supatest Managed.");
|
|
13286
15094
|
}
|
|
@@ -13296,6 +15104,9 @@ var init_interactive = __esm({
|
|
|
13296
15104
|
};
|
|
13297
15105
|
const agent2 = new CoreAgent(presenter, messageBridge);
|
|
13298
15106
|
agentRef.current = agent2;
|
|
15107
|
+
if (onAgentCreated) {
|
|
15108
|
+
onAgentCreated(agent2);
|
|
15109
|
+
}
|
|
13299
15110
|
const result = await agent2.run(runConfig);
|
|
13300
15111
|
if (isMounted) {
|
|
13301
15112
|
onComplete(result.success, result.providerSessionId);
|
|
@@ -13333,20 +15144,21 @@ var init_interactive = __esm({
|
|
|
13333
15144
|
addMessage,
|
|
13334
15145
|
loadMessages,
|
|
13335
15146
|
setSessionId: setContextSessionId,
|
|
13336
|
-
setIsAgentRunning
|
|
13337
|
-
setUsageStats
|
|
15147
|
+
setIsAgentRunning
|
|
13338
15148
|
} = useSession();
|
|
13339
|
-
const
|
|
13340
|
-
const [
|
|
13341
|
-
const [
|
|
13342
|
-
const [
|
|
13343
|
-
const [
|
|
13344
|
-
const [
|
|
13345
|
-
const
|
|
13346
|
-
const
|
|
13347
|
-
const
|
|
13348
|
-
const
|
|
13349
|
-
|
|
15149
|
+
const { setUsageStats } = useUsageStats();
|
|
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(() => {
|
|
13350
15162
|
pendingInjectedRef.current = pendingInjected;
|
|
13351
15163
|
}, [pendingInjected]);
|
|
13352
15164
|
const handleSubmitTask = async (task) => {
|
|
@@ -13364,6 +15176,7 @@ var init_interactive = __esm({
|
|
|
13364
15176
|
});
|
|
13365
15177
|
setSessionId(session.sessionId);
|
|
13366
15178
|
setContextSessionId(session.sessionId);
|
|
15179
|
+
setProviderSessionId(void 0);
|
|
13367
15180
|
} catch (error) {
|
|
13368
15181
|
const errorMessage = error instanceof ApiError ? error.message : `Failed to create session: ${error instanceof Error ? error.message : String(error)}`;
|
|
13369
15182
|
addMessage({
|
|
@@ -13409,7 +15222,7 @@ var init_interactive = __esm({
|
|
|
13409
15222
|
if (shouldRunAgent && !messageBridgeRef.current) {
|
|
13410
15223
|
messageBridgeRef.current = new MessageBridge(providerSessionId || "");
|
|
13411
15224
|
}
|
|
13412
|
-
|
|
15225
|
+
React32.useEffect(() => {
|
|
13413
15226
|
if (!shouldRunAgent && taskQueue.length > 0) {
|
|
13414
15227
|
const [nextTask, ...remaining] = taskQueue;
|
|
13415
15228
|
setTaskQueue(remaining);
|
|
@@ -13423,20 +15236,27 @@ var init_interactive = __esm({
|
|
|
13423
15236
|
setShouldRunAgent(true);
|
|
13424
15237
|
}
|
|
13425
15238
|
}, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
|
|
13426
|
-
const handleClearSession =
|
|
15239
|
+
const handleClearSession = React32.useCallback(() => {
|
|
13427
15240
|
setSessionId(void 0);
|
|
13428
15241
|
setContextSessionId(void 0);
|
|
13429
15242
|
setProviderSessionId(void 0);
|
|
13430
15243
|
setTaskQueue([]);
|
|
13431
15244
|
setPendingInjected([]);
|
|
13432
15245
|
}, [setContextSessionId]);
|
|
13433
|
-
|
|
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(
|
|
13434
15253
|
App,
|
|
13435
15254
|
{
|
|
13436
15255
|
apiClient,
|
|
13437
15256
|
config: { ...config2, task: currentTask },
|
|
13438
15257
|
onClearSession: handleClearSession,
|
|
13439
15258
|
onExit,
|
|
15259
|
+
onQuestionAnswer: handleQuestionAnswer,
|
|
13440
15260
|
onResumeSession: async (session) => {
|
|
13441
15261
|
try {
|
|
13442
15262
|
if (!apiClient) {
|
|
@@ -13452,7 +15272,9 @@ var init_interactive = __esm({
|
|
|
13452
15272
|
const uiMessages = convertQueriesToUIMessages(queries);
|
|
13453
15273
|
setSessionId(session.id);
|
|
13454
15274
|
setContextSessionId(session.id);
|
|
13455
|
-
|
|
15275
|
+
const resumeProviderSessionId = session.providerSessionId || void 0;
|
|
15276
|
+
logger.debug(`Resume session: ${session.id}, providerSessionId: ${resumeProviderSessionId || "NOT SET"}`);
|
|
15277
|
+
setProviderSessionId(resumeProviderSessionId);
|
|
13456
15278
|
const contextData = await apiClient.getSessionContext(session.id);
|
|
13457
15279
|
if (contextData.contextTokens > 0) {
|
|
13458
15280
|
const cacheRead = contextData.cacheReadTokens || 0;
|
|
@@ -13493,13 +15315,16 @@ var init_interactive = __esm({
|
|
|
13493
15315
|
sessionId,
|
|
13494
15316
|
webUrl
|
|
13495
15317
|
}
|
|
13496
|
-
), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */
|
|
15318
|
+
), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React32.createElement(
|
|
13497
15319
|
AgentRunner,
|
|
13498
15320
|
{
|
|
13499
15321
|
apiClient,
|
|
13500
15322
|
config: { ...config2, task: currentTask, providerSessionId },
|
|
13501
15323
|
key: `${taskId}`,
|
|
13502
15324
|
messageBridge: messageBridgeRef.current,
|
|
15325
|
+
onAgentCreated: (agent2) => {
|
|
15326
|
+
agentRef.current = agent2;
|
|
15327
|
+
},
|
|
13503
15328
|
onComplete: handleAgentComplete,
|
|
13504
15329
|
onTurnComplete: () => {
|
|
13505
15330
|
const pending = pendingInjectedRef.current;
|
|
@@ -13518,7 +15343,7 @@ var init_interactive = __esm({
|
|
|
13518
15343
|
useBracketedPaste();
|
|
13519
15344
|
const settings = loadSupatestSettings(props.config.cwd || process.cwd());
|
|
13520
15345
|
const initialProvider = settings.llmProvider || "supatest-managed";
|
|
13521
|
-
return /* @__PURE__ */
|
|
15346
|
+
return /* @__PURE__ */ React32.createElement(KeypressProvider, null, /* @__PURE__ */ React32.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React32.createElement(InteractiveAppContent, { ...props })));
|
|
13522
15347
|
};
|
|
13523
15348
|
}
|
|
13524
15349
|
});
|
|
@@ -13526,9 +15351,61 @@ var init_interactive = __esm({
|
|
|
13526
15351
|
// src/index.ts
|
|
13527
15352
|
await init_config();
|
|
13528
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
|
|
13529
15407
|
init_setup();
|
|
13530
15408
|
await init_config();
|
|
13531
|
-
import { Command } from "commander";
|
|
13532
15409
|
|
|
13533
15410
|
// src/modes/headless.ts
|
|
13534
15411
|
init_api_client();
|
|
@@ -13542,7 +15419,7 @@ init_react();
|
|
|
13542
15419
|
init_MessageList();
|
|
13543
15420
|
init_SessionContext();
|
|
13544
15421
|
import { execSync as execSync2 } from "child_process";
|
|
13545
|
-
import { homedir as
|
|
15422
|
+
import { homedir as homedir5 } from "os";
|
|
13546
15423
|
import { Box as Box13, useApp } from "ink";
|
|
13547
15424
|
import React14, { useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
|
|
13548
15425
|
var getGitBranch = () => {
|
|
@@ -13554,7 +15431,7 @@ var getGitBranch = () => {
|
|
|
13554
15431
|
};
|
|
13555
15432
|
var getCurrentFolder = () => {
|
|
13556
15433
|
const cwd = process.cwd();
|
|
13557
|
-
const home =
|
|
15434
|
+
const home = homedir5();
|
|
13558
15435
|
if (cwd.startsWith(home)) {
|
|
13559
15436
|
return `~${cwd.slice(home.length)}`;
|
|
13560
15437
|
}
|
|
@@ -13567,9 +15444,9 @@ var HeadlessAgentRunner = ({ config: config2, sessionId, apiClient, onComplete }
|
|
|
13567
15444
|
updateMessageByToolId,
|
|
13568
15445
|
setIsAgentRunning,
|
|
13569
15446
|
updateStats,
|
|
13570
|
-
setTodos
|
|
13571
|
-
setUsageStats
|
|
15447
|
+
setTodos
|
|
13572
15448
|
} = useSession();
|
|
15449
|
+
const { setUsageStats } = useUsageStats();
|
|
13573
15450
|
const agentRef = useRef4(null);
|
|
13574
15451
|
useEffect2(() => {
|
|
13575
15452
|
let isMounted = true;
|
|
@@ -13801,7 +15678,7 @@ async function runAgent(config2) {
|
|
|
13801
15678
|
// src/utils/auto-update.ts
|
|
13802
15679
|
init_version();
|
|
13803
15680
|
init_error_logger();
|
|
13804
|
-
import { execSync as execSync3, spawn } from "child_process";
|
|
15681
|
+
import { execSync as execSync3, spawn as spawn3 } from "child_process";
|
|
13805
15682
|
import latestVersion from "latest-version";
|
|
13806
15683
|
import { gt } from "semver";
|
|
13807
15684
|
var UPDATE_CHECK_TIMEOUT = 3e3;
|
|
@@ -13843,7 +15720,7 @@ Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
|
|
|
13843
15720
|
});
|
|
13844
15721
|
}
|
|
13845
15722
|
console.log("\u2713 Updated successfully\n");
|
|
13846
|
-
const child =
|
|
15723
|
+
const child = spawn3(process.argv[0], process.argv.slice(1), {
|
|
13847
15724
|
stdio: "inherit",
|
|
13848
15725
|
detached: false
|
|
13849
15726
|
});
|
|
@@ -14128,6 +16005,33 @@ program.command("setup").description("Check prerequisites and set up required to
|
|
|
14128
16005
|
process.exit(1);
|
|
14129
16006
|
}
|
|
14130
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
|
+
});
|
|
14131
16035
|
var filteredArgv = process.argv.filter((arg, index) => {
|
|
14132
16036
|
return !(arg === "--" && index > 1);
|
|
14133
16037
|
});
|