@supatest/cli 0.0.40 → 0.0.42
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 +2274 -463
- 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>
|
|
@@ -313,7 +313,7 @@ Use these commands in interactive mode (type them and press Enter):
|
|
|
313
313
|
- Choose between "Supatest Managed" (default) or "Claude Max"
|
|
314
314
|
- Supatest Managed: Uses models through Supatest infrastructure
|
|
315
315
|
- Claude Max: Uses your Claude subscription directly
|
|
316
|
-
-
|
|
316
|
+
- Authentication flow starts automatically when selecting Claude Max
|
|
317
317
|
- Available on macOS, Linux, and Windows
|
|
318
318
|
|
|
319
319
|
- **/mcp** - Show configured MCP servers
|
|
@@ -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,11 +5624,438 @@ 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;
|
|
5632
6054
|
}
|
|
5633
6055
|
});
|
|
5634
6056
|
|
|
5635
6057
|
// src/commands/setup.ts
|
|
5636
|
-
import { execSync, spawnSync } from "child_process";
|
|
6058
|
+
import { execSync, spawn, spawnSync } from "child_process";
|
|
5637
6059
|
import fs from "fs";
|
|
5638
6060
|
import os from "os";
|
|
5639
6061
|
import path from "path";
|
|
@@ -5730,47 +6152,64 @@ function checkNodeVersion() {
|
|
|
5730
6152
|
version: nodeVersion.raw
|
|
5731
6153
|
};
|
|
5732
6154
|
}
|
|
5733
|
-
function installChromium() {
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
try {
|
|
5737
|
-
const result = spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
6155
|
+
async function installChromium() {
|
|
6156
|
+
return new Promise((resolve2) => {
|
|
6157
|
+
const child = spawn("npx", ["playwright", "install", "chromium"], {
|
|
5738
6158
|
stdio: "inherit",
|
|
5739
6159
|
shell: true
|
|
5740
6160
|
// Required for Windows where npx is npx.cmd
|
|
5741
6161
|
});
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
6162
|
+
child.on("close", (code) => {
|
|
6163
|
+
if (code === 0) {
|
|
6164
|
+
resolve2({
|
|
6165
|
+
ok: true,
|
|
6166
|
+
message: "Chromium browser installed successfully."
|
|
6167
|
+
});
|
|
6168
|
+
} else {
|
|
6169
|
+
resolve2({
|
|
6170
|
+
ok: false,
|
|
6171
|
+
message: `Playwright install exited with code ${code}`
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6174
|
+
});
|
|
6175
|
+
child.on("error", (error) => {
|
|
6176
|
+
resolve2({
|
|
6177
|
+
ok: false,
|
|
6178
|
+
message: `Failed to install Chromium: ${error.message}`
|
|
6179
|
+
});
|
|
6180
|
+
});
|
|
6181
|
+
});
|
|
5758
6182
|
}
|
|
5759
6183
|
function createSupatestConfig(cwd) {
|
|
5760
6184
|
const supatestDir = path.join(cwd, ".supatest");
|
|
5761
6185
|
const mcpJsonPath = path.join(supatestDir, "mcp.json");
|
|
5762
6186
|
try {
|
|
6187
|
+
if (!fs.existsSync(supatestDir)) {
|
|
6188
|
+
fs.mkdirSync(supatestDir, { recursive: true });
|
|
6189
|
+
}
|
|
6190
|
+
let config2;
|
|
6191
|
+
let fileExisted = false;
|
|
5763
6192
|
if (fs.existsSync(mcpJsonPath)) {
|
|
6193
|
+
fileExisted = true;
|
|
6194
|
+
const existingContent = fs.readFileSync(mcpJsonPath, "utf-8");
|
|
6195
|
+
config2 = JSON.parse(existingContent);
|
|
6196
|
+
} else {
|
|
6197
|
+
config2 = {};
|
|
6198
|
+
}
|
|
6199
|
+
if (!config2.mcpServers || typeof config2.mcpServers !== "object") {
|
|
6200
|
+
config2.mcpServers = {};
|
|
6201
|
+
}
|
|
6202
|
+
if (!config2.mcpServers.playwright) {
|
|
6203
|
+
config2.mcpServers.playwright = DEFAULT_MCP_CONFIG.mcpServers.playwright;
|
|
6204
|
+
}
|
|
6205
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
6206
|
+
if (fileExisted) {
|
|
5764
6207
|
return {
|
|
5765
6208
|
ok: true,
|
|
5766
|
-
message: "mcp.json
|
|
6209
|
+
message: "Updated .supatest/mcp.json with Playwright MCP server configuration",
|
|
5767
6210
|
created: false
|
|
5768
6211
|
};
|
|
5769
6212
|
}
|
|
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
6213
|
return {
|
|
5775
6214
|
ok: true,
|
|
5776
6215
|
message: "Created .supatest/mcp.json with Playwright MCP server configuration",
|
|
@@ -5799,7 +6238,6 @@ async function setupCommand(options) {
|
|
|
5799
6238
|
const output = [];
|
|
5800
6239
|
const log = (msg) => {
|
|
5801
6240
|
output.push(msg);
|
|
5802
|
-
console.log(msg);
|
|
5803
6241
|
};
|
|
5804
6242
|
const result = {
|
|
5805
6243
|
nodeVersionOk: false,
|
|
@@ -5833,7 +6271,7 @@ async function setupCommand(options) {
|
|
|
5833
6271
|
result.playwrightInstalled = true;
|
|
5834
6272
|
} else {
|
|
5835
6273
|
log(" \u{1F4E6} Chromium browser not found. Installing...\n");
|
|
5836
|
-
const chromiumResult = installChromium();
|
|
6274
|
+
const chromiumResult = await installChromium();
|
|
5837
6275
|
result.playwrightInstalled = chromiumResult.ok;
|
|
5838
6276
|
log("");
|
|
5839
6277
|
if (chromiumResult.ok) {
|
|
@@ -5891,7 +6329,7 @@ var CLI_VERSION;
|
|
|
5891
6329
|
var init_version = __esm({
|
|
5892
6330
|
"src/version.ts"() {
|
|
5893
6331
|
"use strict";
|
|
5894
|
-
CLI_VERSION = "0.0.
|
|
6332
|
+
CLI_VERSION = "0.0.42";
|
|
5895
6333
|
}
|
|
5896
6334
|
});
|
|
5897
6335
|
|
|
@@ -6600,18 +7038,145 @@ var init_api_client = __esm({
|
|
|
6600
7038
|
throw new ApiError(response.status, response.statusText, errorText);
|
|
6601
7039
|
}
|
|
6602
7040
|
const data = await response.json();
|
|
6603
|
-
logger.debug(`Fetched test: ${testId}`);
|
|
6604
|
-
return data;
|
|
7041
|
+
logger.debug(`Fetched test: ${testId}`);
|
|
7042
|
+
return data;
|
|
7043
|
+
}
|
|
7044
|
+
/**
|
|
7045
|
+
* Get test result history (for sparkline visualization)
|
|
7046
|
+
* @param testId - The test ID
|
|
7047
|
+
* @param limit - Maximum number of history items
|
|
7048
|
+
* @returns Test history
|
|
7049
|
+
*/
|
|
7050
|
+
async getTestHistory(testId, limit = 20) {
|
|
7051
|
+
const url = `${this.apiUrl}/v1/reporting/tests/${testId}/history?limit=${limit}`;
|
|
7052
|
+
logger.debug(`Fetching test history: ${testId}`);
|
|
7053
|
+
const response = await fetch(url, {
|
|
7054
|
+
method: "GET",
|
|
7055
|
+
headers: {
|
|
7056
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
7057
|
+
}
|
|
7058
|
+
});
|
|
7059
|
+
if (!response.ok) {
|
|
7060
|
+
const errorText = await response.text();
|
|
7061
|
+
throw new ApiError(response.status, response.statusText, errorText);
|
|
7062
|
+
}
|
|
7063
|
+
const data = await response.json();
|
|
7064
|
+
logger.debug(`Fetched ${data.history.length} history items for test ${testId}`);
|
|
7065
|
+
return data;
|
|
7066
|
+
}
|
|
7067
|
+
/**
|
|
7068
|
+
* Assign tests to yourself (or someone else)
|
|
7069
|
+
* @param params - Assignment parameters
|
|
7070
|
+
* @returns Assignment result with assigned tests and conflicts
|
|
7071
|
+
*/
|
|
7072
|
+
async assignTests(params) {
|
|
7073
|
+
const url = `${this.apiUrl}/v1/fix-assignments/assign`;
|
|
7074
|
+
logger.debug(`Assigning tests`, {
|
|
7075
|
+
runId: params.runId,
|
|
7076
|
+
count: params.testRunIds.length
|
|
7077
|
+
});
|
|
7078
|
+
const response = await fetch(url, {
|
|
7079
|
+
method: "POST",
|
|
7080
|
+
headers: {
|
|
7081
|
+
"Content-Type": "application/json",
|
|
7082
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
7083
|
+
},
|
|
7084
|
+
body: JSON.stringify({
|
|
7085
|
+
testRunIds: params.testRunIds,
|
|
7086
|
+
assignedTo: params.assignedTo
|
|
7087
|
+
})
|
|
7088
|
+
});
|
|
7089
|
+
if (!response.ok) {
|
|
7090
|
+
const errorText = await response.text();
|
|
7091
|
+
throw new ApiError(response.status, response.statusText, errorText);
|
|
7092
|
+
}
|
|
7093
|
+
const data = await response.json();
|
|
7094
|
+
logger.debug(
|
|
7095
|
+
`Assigned ${data.assigned.length} tests, ${data.conflicts.length} conflicts`
|
|
7096
|
+
);
|
|
7097
|
+
return data;
|
|
7098
|
+
}
|
|
7099
|
+
/**
|
|
7100
|
+
* Mark assignment as complete
|
|
7101
|
+
* @param params - Completion parameters
|
|
7102
|
+
* @returns Success status
|
|
7103
|
+
*/
|
|
7104
|
+
async completeAssignment(params) {
|
|
7105
|
+
const url = `${this.apiUrl}/v1/fix-assignments/${params.assignmentId}/status`;
|
|
7106
|
+
logger.debug(`Completing assignment`, {
|
|
7107
|
+
assignmentId: params.assignmentId,
|
|
7108
|
+
status: params.status
|
|
7109
|
+
});
|
|
7110
|
+
const response = await fetch(url, {
|
|
7111
|
+
method: "PATCH",
|
|
7112
|
+
headers: {
|
|
7113
|
+
"Content-Type": "application/json",
|
|
7114
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
7115
|
+
},
|
|
7116
|
+
body: JSON.stringify({
|
|
7117
|
+
status: params.status,
|
|
7118
|
+
fixSessionId: params.fixSessionId
|
|
7119
|
+
})
|
|
7120
|
+
});
|
|
7121
|
+
if (!response.ok) {
|
|
7122
|
+
const errorText = await response.text();
|
|
7123
|
+
throw new ApiError(response.status, response.statusText, errorText);
|
|
7124
|
+
}
|
|
7125
|
+
const data = await response.json();
|
|
7126
|
+
logger.debug(`Assignment completed: ${params.assignmentId}`);
|
|
7127
|
+
return data;
|
|
7128
|
+
}
|
|
7129
|
+
/**
|
|
7130
|
+
* Release claim (delete assignment)
|
|
7131
|
+
* @param assignmentId - The assignment ID to release
|
|
7132
|
+
* @returns Success status
|
|
7133
|
+
*/
|
|
7134
|
+
async releaseAssignment(assignmentId) {
|
|
7135
|
+
const url = `${this.apiUrl}/v1/fix-assignments/${assignmentId}`;
|
|
7136
|
+
logger.debug(`Releasing assignment: ${assignmentId}`);
|
|
7137
|
+
const response = await fetch(url, {
|
|
7138
|
+
method: "DELETE",
|
|
7139
|
+
headers: {
|
|
7140
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
7141
|
+
}
|
|
7142
|
+
});
|
|
7143
|
+
if (!response.ok) {
|
|
7144
|
+
const errorText = await response.text();
|
|
7145
|
+
throw new ApiError(response.status, response.statusText, errorText);
|
|
7146
|
+
}
|
|
7147
|
+
const data = await response.json();
|
|
7148
|
+
logger.debug(`Assignment released: ${assignmentId}`);
|
|
7149
|
+
return data;
|
|
7150
|
+
}
|
|
7151
|
+
/**
|
|
7152
|
+
* Get my current assignments
|
|
7153
|
+
* @returns List of current assignments
|
|
7154
|
+
*/
|
|
7155
|
+
async getMyAssignments() {
|
|
7156
|
+
const url = `${this.apiUrl}/v1/fix-assignments?status=assigned`;
|
|
7157
|
+
logger.debug(`Fetching my assignments`);
|
|
7158
|
+
const response = await fetch(url, {
|
|
7159
|
+
method: "GET",
|
|
7160
|
+
headers: {
|
|
7161
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
7162
|
+
}
|
|
7163
|
+
});
|
|
7164
|
+
if (!response.ok) {
|
|
7165
|
+
const errorText = await response.text();
|
|
7166
|
+
throw new ApiError(response.status, response.statusText, errorText);
|
|
7167
|
+
}
|
|
7168
|
+
const data = await response.json();
|
|
7169
|
+
logger.debug(`Fetched ${data.assignments.length} assignments`);
|
|
7170
|
+
return { assignments: data.assignments };
|
|
6605
7171
|
}
|
|
6606
7172
|
/**
|
|
6607
|
-
* Get
|
|
6608
|
-
* @param
|
|
6609
|
-
* @
|
|
6610
|
-
* @returns Test history
|
|
7173
|
+
* Get assignments for a specific run (to show what others are working on)
|
|
7174
|
+
* @param runId - The run ID
|
|
7175
|
+
* @returns List of assignments for the run
|
|
6611
7176
|
*/
|
|
6612
|
-
async
|
|
6613
|
-
const url = `${this.apiUrl}/v1/
|
|
6614
|
-
logger.debug(`Fetching
|
|
7177
|
+
async getRunAssignments(runId) {
|
|
7178
|
+
const url = `${this.apiUrl}/v1/fix-assignments?runId=${runId}&status=assigned`;
|
|
7179
|
+
logger.debug(`Fetching assignments for run: ${runId}`);
|
|
6615
7180
|
const response = await fetch(url, {
|
|
6616
7181
|
method: "GET",
|
|
6617
7182
|
headers: {
|
|
@@ -6623,8 +7188,14 @@ var init_api_client = __esm({
|
|
|
6623
7188
|
throw new ApiError(response.status, response.statusText, errorText);
|
|
6624
7189
|
}
|
|
6625
7190
|
const data = await response.json();
|
|
6626
|
-
logger.debug(`Fetched ${data.
|
|
6627
|
-
return
|
|
7191
|
+
logger.debug(`Fetched ${data.assignments.length} assignments for run ${runId}`);
|
|
7192
|
+
return {
|
|
7193
|
+
assignments: data.assignments.map((a) => ({
|
|
7194
|
+
testRunId: a.testRunId,
|
|
7195
|
+
assignedTo: a.assignedTo,
|
|
7196
|
+
assignedAt: a.assignedAt
|
|
7197
|
+
}))
|
|
7198
|
+
};
|
|
6628
7199
|
}
|
|
6629
7200
|
};
|
|
6630
7201
|
}
|
|
@@ -6853,6 +7424,8 @@ var init_agent = __esm({
|
|
|
6853
7424
|
presenter;
|
|
6854
7425
|
abortController = null;
|
|
6855
7426
|
messageBridge = null;
|
|
7427
|
+
// Map of pending questions waiting for user answers
|
|
7428
|
+
pendingQuestionResolvers = /* @__PURE__ */ new Map();
|
|
6856
7429
|
constructor(presenter, messageBridge) {
|
|
6857
7430
|
this.presenter = presenter;
|
|
6858
7431
|
this.messageBridge = messageBridge ?? null;
|
|
@@ -6866,9 +7439,37 @@ var init_agent = __esm({
|
|
|
6866
7439
|
this.abortController.abort();
|
|
6867
7440
|
}
|
|
6868
7441
|
}
|
|
7442
|
+
/**
|
|
7443
|
+
* Create a promise that waits for user to answer a question.
|
|
7444
|
+
* Used by canUseTool callback to pause agent execution until user responds.
|
|
7445
|
+
*/
|
|
7446
|
+
createQuestionPromise(questionId) {
|
|
7447
|
+
return new Promise((resolve2) => {
|
|
7448
|
+
this.pendingQuestionResolvers.set(questionId, resolve2);
|
|
7449
|
+
});
|
|
7450
|
+
}
|
|
7451
|
+
/**
|
|
7452
|
+
* Submit an answer for a pending question.
|
|
7453
|
+
* Called from the UI when user responds to AskUserQuestion.
|
|
7454
|
+
* @param questionId - The unique ID of the question
|
|
7455
|
+
* @param answers - The user's answers, or null if dismissed
|
|
7456
|
+
* @returns true if question was found and resolved, false otherwise
|
|
7457
|
+
*/
|
|
7458
|
+
submitAnswer(questionId, answers) {
|
|
7459
|
+
const resolver = this.pendingQuestionResolvers.get(questionId);
|
|
7460
|
+
if (resolver) {
|
|
7461
|
+
resolver(answers);
|
|
7462
|
+
this.pendingQuestionResolvers.delete(questionId);
|
|
7463
|
+
return true;
|
|
7464
|
+
}
|
|
7465
|
+
return false;
|
|
7466
|
+
}
|
|
6869
7467
|
async run(config2) {
|
|
6870
7468
|
this.abortController = new AbortController();
|
|
6871
7469
|
this.presenter.onStart(config2);
|
|
7470
|
+
if (config2.providerSessionId) {
|
|
7471
|
+
this.presenter.onLog(`Resuming with providerSessionId: ${config2.providerSessionId}`);
|
|
7472
|
+
}
|
|
6872
7473
|
const claudeCodePath = await this.resolveClaudeCodePath();
|
|
6873
7474
|
let prompt = config2.task;
|
|
6874
7475
|
if (config2.logs) {
|
|
@@ -6951,6 +7552,9 @@ ${projectInstructions}`,
|
|
|
6951
7552
|
CLAUDE_CONFIG_DIR: cleanEnv.CLAUDE_CONFIG_DIR || "(using default ~/.claude/)"
|
|
6952
7553
|
});
|
|
6953
7554
|
cleanEnv.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
|
|
7555
|
+
cleanEnv.CLAUDE_CODE_ENABLE_TELEMETRY = "0";
|
|
7556
|
+
cleanEnv.DISABLE_TELEMETRY = "1";
|
|
7557
|
+
cleanEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
|
|
6954
7558
|
const selectedModel = config2.selectedModel || "premium";
|
|
6955
7559
|
const defaultModel = resolveToAnthropicModel(selectedModel);
|
|
6956
7560
|
const queryOptions = {
|
|
@@ -6982,7 +7586,7 @@ ${projectInstructions}`,
|
|
|
6982
7586
|
return servers;
|
|
6983
7587
|
})(),
|
|
6984
7588
|
// Resume from previous session if providerSessionId is provided
|
|
6985
|
-
// This allows the agent to continue
|
|
7589
|
+
// This allows the agent to continue sessions with full context
|
|
6986
7590
|
// Note: Sessions expire after ~30 days due to Anthropic's data retention policy
|
|
6987
7591
|
...config2.providerSessionId && {
|
|
6988
7592
|
resume: config2.providerSessionId
|
|
@@ -6999,6 +7603,29 @@ ${projectInstructions}`,
|
|
|
6999
7603
|
env: cleanEnv,
|
|
7000
7604
|
stderr: (msg) => {
|
|
7001
7605
|
this.presenter.onLog(`[Agent Failure] ${msg}`);
|
|
7606
|
+
},
|
|
7607
|
+
// Intercept AskUserQuestion tool to wait for user response
|
|
7608
|
+
canUseTool: async (toolName, input) => {
|
|
7609
|
+
if (toolName !== "AskUserQuestion") {
|
|
7610
|
+
return { behavior: "allow", updatedInput: input };
|
|
7611
|
+
}
|
|
7612
|
+
const questions = input.questions || [];
|
|
7613
|
+
if (questions.length === 0) {
|
|
7614
|
+
logger.warn("[AskUserQuestion] No questions provided");
|
|
7615
|
+
return { behavior: "deny", message: "No questions provided" };
|
|
7616
|
+
}
|
|
7617
|
+
const questionId = `ask-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
7618
|
+
this.presenter.onToolUse?.(toolName, input, questionId);
|
|
7619
|
+
const answers = await this.createQuestionPromise(questionId);
|
|
7620
|
+
if (answers === null) {
|
|
7621
|
+
this.presenter.onToolResult?.(questionId, JSON.stringify({ dismissed: true }));
|
|
7622
|
+
return { behavior: "deny", message: "User dismissed the question" };
|
|
7623
|
+
}
|
|
7624
|
+
this.presenter.onToolResult?.(questionId, JSON.stringify({ answers }));
|
|
7625
|
+
return {
|
|
7626
|
+
behavior: "allow",
|
|
7627
|
+
updatedInput: { questions, answers }
|
|
7628
|
+
};
|
|
7002
7629
|
}
|
|
7003
7630
|
};
|
|
7004
7631
|
let resultText = "";
|
|
@@ -7023,7 +7650,7 @@ ${projectInstructions}`,
|
|
|
7023
7650
|
};
|
|
7024
7651
|
const isSessionExpiredError = (errorMsg) => {
|
|
7025
7652
|
const expiredPatterns = [
|
|
7026
|
-
"no
|
|
7653
|
+
"no session found",
|
|
7027
7654
|
"session not found",
|
|
7028
7655
|
"session expired",
|
|
7029
7656
|
"invalid session"
|
|
@@ -7031,6 +7658,20 @@ ${projectInstructions}`,
|
|
|
7031
7658
|
const lowerError = errorMsg.toLowerCase();
|
|
7032
7659
|
return expiredPatterns.some((pattern) => lowerError.includes(pattern));
|
|
7033
7660
|
};
|
|
7661
|
+
const isContextWindowExceededError = (errorMsg) => {
|
|
7662
|
+
const contextPatterns = [
|
|
7663
|
+
"exceed context limit",
|
|
7664
|
+
"exceeds context limit",
|
|
7665
|
+
"context length",
|
|
7666
|
+
"maximum context",
|
|
7667
|
+
"prompt is too long",
|
|
7668
|
+
"input_length and max_tokens",
|
|
7669
|
+
"requested token count exceeds",
|
|
7670
|
+
"context window"
|
|
7671
|
+
];
|
|
7672
|
+
const lowerError = errorMsg.toLowerCase();
|
|
7673
|
+
return contextPatterns.some((pattern) => lowerError.includes(pattern));
|
|
7674
|
+
};
|
|
7034
7675
|
const runQuery = async (options) => {
|
|
7035
7676
|
logger.debug("[agent] Starting query", {
|
|
7036
7677
|
model: options.model,
|
|
@@ -7181,11 +7822,17 @@ ${projectInstructions}`,
|
|
|
7181
7822
|
wasInterrupted = true;
|
|
7182
7823
|
logger.debug("[agent] Query was aborted by user");
|
|
7183
7824
|
} else if (config2.providerSessionId && isSessionExpiredError(errorMessage)) {
|
|
7184
|
-
const expiredMessage = "Can't continue
|
|
7825
|
+
const expiredMessage = "Can't continue session older than 30 days. Please start a new session.";
|
|
7185
7826
|
this.presenter.onError(expiredMessage, true);
|
|
7186
7827
|
hasError = true;
|
|
7187
7828
|
errors.push(expiredMessage);
|
|
7188
7829
|
logger.debug("[agent] Session expired error");
|
|
7830
|
+
} else if (isContextWindowExceededError(errorMessage)) {
|
|
7831
|
+
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";
|
|
7832
|
+
this.presenter.onError(contextMessage, true);
|
|
7833
|
+
hasError = true;
|
|
7834
|
+
errors.push(contextMessage);
|
|
7835
|
+
logger.debug("[agent] Context window exceeded error");
|
|
7189
7836
|
} else {
|
|
7190
7837
|
logError(error, {
|
|
7191
7838
|
source: "CoreAgent.run",
|
|
@@ -7270,6 +7917,10 @@ function getToolDescription(toolName, input) {
|
|
|
7270
7917
|
case "BashOutput":
|
|
7271
7918
|
case "Command Output":
|
|
7272
7919
|
return input?.bash_id || "shell output";
|
|
7920
|
+
case "AskUserQuestion": {
|
|
7921
|
+
const question = input?.questions?.[0]?.question || input?.question || "Question";
|
|
7922
|
+
return question.length > 60 ? `${question.substring(0, 60)}...` : question;
|
|
7923
|
+
}
|
|
7273
7924
|
default:
|
|
7274
7925
|
return toolName;
|
|
7275
7926
|
}
|
|
@@ -7402,6 +8053,15 @@ var init_react = __esm({
|
|
|
7402
8053
|
this.callbacks.onExitPlanMode?.();
|
|
7403
8054
|
} else if (tool === "EnterPlanMode") {
|
|
7404
8055
|
this.callbacks.onEnterPlanMode?.();
|
|
8056
|
+
} else if (tool === "AskUserQuestion") {
|
|
8057
|
+
const questions = input?.questions || [];
|
|
8058
|
+
if (questions.length > 0) {
|
|
8059
|
+
const firstQuestion = questions[0];
|
|
8060
|
+
const question = firstQuestion.question || "";
|
|
8061
|
+
const options = firstQuestion.options || [];
|
|
8062
|
+
const multiSelect = firstQuestion.multiSelect || false;
|
|
8063
|
+
this.callbacks.onAskUserQuestion?.(toolId, question, options, multiSelect);
|
|
8064
|
+
}
|
|
7405
8065
|
}
|
|
7406
8066
|
streamEventAsync(this.apiClient, this.sessionId, {
|
|
7407
8067
|
type: "tool_use",
|
|
@@ -7559,6 +8219,7 @@ var init_SessionContext = __esm({
|
|
|
7559
8219
|
const [allToolsExpanded, setAllToolsExpanded] = useState(true);
|
|
7560
8220
|
const [toolGroupsExpanded, setToolGroupsExpanded] = useState(false);
|
|
7561
8221
|
const [staticRemountKey, setStaticRemountKey] = useState(0);
|
|
8222
|
+
const [pendingQuestion, setPendingQuestion] = useState(null);
|
|
7562
8223
|
const addMessage = useCallback(
|
|
7563
8224
|
(message) => {
|
|
7564
8225
|
const expandableTools = ["Bash", "BashOutput", "Command Output"];
|
|
@@ -7689,7 +8350,9 @@ var init_SessionContext = __esm({
|
|
|
7689
8350
|
llmProvider,
|
|
7690
8351
|
setLlmProvider,
|
|
7691
8352
|
staticRemountKey,
|
|
7692
|
-
refreshStatic
|
|
8353
|
+
refreshStatic,
|
|
8354
|
+
pendingQuestion,
|
|
8355
|
+
setPendingQuestion
|
|
7693
8356
|
}), [
|
|
7694
8357
|
messages,
|
|
7695
8358
|
addMessage,
|
|
@@ -7714,7 +8377,8 @@ var init_SessionContext = __esm({
|
|
|
7714
8377
|
selectedModel,
|
|
7715
8378
|
llmProvider,
|
|
7716
8379
|
staticRemountKey,
|
|
7717
|
-
refreshStatic
|
|
8380
|
+
refreshStatic,
|
|
8381
|
+
pendingQuestion
|
|
7718
8382
|
]);
|
|
7719
8383
|
const usageStatsValue = useMemo(() => ({
|
|
7720
8384
|
usageStats,
|
|
@@ -8445,6 +9109,14 @@ function getCommandDisplay(toolName, input) {
|
|
|
8445
9109
|
return input.pattern ? [`"${input.pattern}"${input.path ? ` in ${input.path}` : ""}`] : null;
|
|
8446
9110
|
case "Glob":
|
|
8447
9111
|
return input.pattern ? [input.pattern] : null;
|
|
9112
|
+
case "AskUserQuestion": {
|
|
9113
|
+
const questions = input.questions || [];
|
|
9114
|
+
if (questions.length > 0) {
|
|
9115
|
+
const question = questions[0].question || "Question";
|
|
9116
|
+
return [question.length > 80 ? `${question.substring(0, 80)}...` : question];
|
|
9117
|
+
}
|
|
9118
|
+
return null;
|
|
9119
|
+
}
|
|
8448
9120
|
default:
|
|
8449
9121
|
return null;
|
|
8450
9122
|
}
|
|
@@ -8460,7 +9132,8 @@ function getToolStyle(toolName) {
|
|
|
8460
9132
|
Glob: { icon: "\u{1F50D}", color: theme.tool.search },
|
|
8461
9133
|
Grep: { icon: "\u{1F50D}", color: theme.tool.search },
|
|
8462
9134
|
Task: { icon: "\u{1F916}", color: theme.tool.agent },
|
|
8463
|
-
TodoWrite: { icon: "\u{1F4DD}", color: theme.text.info }
|
|
9135
|
+
TodoWrite: { icon: "\u{1F4DD}", color: theme.text.info },
|
|
9136
|
+
AskUserQuestion: { icon: "\u2753", color: theme.text.info }
|
|
8464
9137
|
};
|
|
8465
9138
|
return styles[toolName] || { icon: "\u{1F527}", color: theme.text.secondary };
|
|
8466
9139
|
}
|
|
@@ -8494,6 +9167,9 @@ function getResultSummary(toolName, result) {
|
|
|
8494
9167
|
const lines = result.split("\n").filter((line) => line.trim());
|
|
8495
9168
|
return lines.length > 0 ? `${lines.length} lines of output` : "No output";
|
|
8496
9169
|
}
|
|
9170
|
+
case "AskUserQuestion": {
|
|
9171
|
+
return result ? `Answered: ${result.substring(0, 50)}${result.length > 50 ? "..." : ""}` : "Waiting for response...";
|
|
9172
|
+
}
|
|
8497
9173
|
default:
|
|
8498
9174
|
return null;
|
|
8499
9175
|
}
|
|
@@ -8578,16 +9254,7 @@ var init_UserMessage = __esm({
|
|
|
8578
9254
|
"use strict";
|
|
8579
9255
|
init_theme();
|
|
8580
9256
|
UserMessage = ({ text }) => {
|
|
8581
|
-
return /* @__PURE__ */ React11.createElement(Box10, {
|
|
8582
|
-
Box10,
|
|
8583
|
-
{
|
|
8584
|
-
borderColor: theme.text.dim,
|
|
8585
|
-
borderStyle: "round",
|
|
8586
|
-
paddingLeft: 1,
|
|
8587
|
-
paddingRight: 1
|
|
8588
|
-
},
|
|
8589
|
-
/* @__PURE__ */ React11.createElement(Text10, { color: theme.text.secondary }, text)
|
|
8590
|
-
));
|
|
9257
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.info }, "\u{1F464} "), /* @__PURE__ */ React11.createElement(Text10, { color: theme.text.secondary }, text));
|
|
8591
9258
|
};
|
|
8592
9259
|
}
|
|
8593
9260
|
});
|
|
@@ -8928,7 +9595,7 @@ var init_stdio = __esm({
|
|
|
8928
9595
|
});
|
|
8929
9596
|
|
|
8930
9597
|
// src/utils/encryption.ts
|
|
8931
|
-
import
|
|
9598
|
+
import crypto2 from "crypto";
|
|
8932
9599
|
import { hostname, userInfo } from "os";
|
|
8933
9600
|
function deriveEncryptionKey() {
|
|
8934
9601
|
const host = hostname();
|
|
@@ -8937,7 +9604,7 @@ function deriveEncryptionKey() {
|
|
|
8937
9604
|
if (process.env.DEBUG_ENCRYPTION) {
|
|
8938
9605
|
console.error(`[encryption] hostname=${host}, username=${user}, salt=${salt}`);
|
|
8939
9606
|
}
|
|
8940
|
-
return
|
|
9607
|
+
return crypto2.scryptSync("supatest-cli-token", salt, KEY_LENGTH);
|
|
8941
9608
|
}
|
|
8942
9609
|
function getEncryptionKey() {
|
|
8943
9610
|
if (!cachedKey) {
|
|
@@ -8947,8 +9614,8 @@ function getEncryptionKey() {
|
|
|
8947
9614
|
}
|
|
8948
9615
|
function encrypt(plaintext) {
|
|
8949
9616
|
const key = getEncryptionKey();
|
|
8950
|
-
const iv =
|
|
8951
|
-
const cipher =
|
|
9617
|
+
const iv = crypto2.randomBytes(IV_LENGTH);
|
|
9618
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
|
|
8952
9619
|
let encrypted = cipher.update(plaintext, "utf8", "hex");
|
|
8953
9620
|
encrypted += cipher.final("hex");
|
|
8954
9621
|
const authTag = cipher.getAuthTag();
|
|
@@ -8963,7 +9630,7 @@ function decrypt(encryptedData) {
|
|
|
8963
9630
|
const iv = Buffer.from(ivHex, "hex");
|
|
8964
9631
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
8965
9632
|
const key = getEncryptionKey();
|
|
8966
|
-
const decipher =
|
|
9633
|
+
const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
|
|
8967
9634
|
decipher.setAuthTag(authTag);
|
|
8968
9635
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
8969
9636
|
decrypted += decipher.final("utf8");
|
|
@@ -9084,7 +9751,7 @@ var init_message_bridge = __esm({
|
|
|
9084
9751
|
this.sessionId = sessionId;
|
|
9085
9752
|
}
|
|
9086
9753
|
/**
|
|
9087
|
-
* Push a user message to be injected into the
|
|
9754
|
+
* Push a user message to be injected into the session.
|
|
9088
9755
|
* Call this from the UI when user submits a message during agent execution.
|
|
9089
9756
|
*/
|
|
9090
9757
|
push(text) {
|
|
@@ -9156,15 +9823,15 @@ var init_message_bridge = __esm({
|
|
|
9156
9823
|
});
|
|
9157
9824
|
|
|
9158
9825
|
// src/commands/login.ts
|
|
9159
|
-
import { spawn as
|
|
9160
|
-
import
|
|
9826
|
+
import { spawn as spawn3 } from "child_process";
|
|
9827
|
+
import crypto3 from "crypto";
|
|
9161
9828
|
import http from "http";
|
|
9162
9829
|
import { platform } from "os";
|
|
9163
9830
|
function escapeHtml(text) {
|
|
9164
9831
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9165
9832
|
}
|
|
9166
9833
|
function generateState() {
|
|
9167
|
-
return
|
|
9834
|
+
return crypto3.randomBytes(STATE_LENGTH).toString("hex");
|
|
9168
9835
|
}
|
|
9169
9836
|
async function exchangeCodeForToken(code, state) {
|
|
9170
9837
|
const response = await fetch(`${API_URL}/web/auth/cli-token-exchange`, {
|
|
@@ -9197,7 +9864,7 @@ function openBrowser(url) {
|
|
|
9197
9864
|
args = [url];
|
|
9198
9865
|
}
|
|
9199
9866
|
const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
|
|
9200
|
-
|
|
9867
|
+
spawn3(command, args, options).unref();
|
|
9201
9868
|
}
|
|
9202
9869
|
function startCallbackServer(port, expectedState) {
|
|
9203
9870
|
return new Promise((resolve2, reject) => {
|
|
@@ -9533,62 +10200,603 @@ var init_login = __esm({
|
|
|
9533
10200
|
}
|
|
9534
10201
|
});
|
|
9535
10202
|
|
|
9536
|
-
// src/utils/claude-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
10203
|
+
// src/utils/claude-oauth.ts
|
|
10204
|
+
var claude_oauth_exports = {};
|
|
10205
|
+
__export(claude_oauth_exports, {
|
|
10206
|
+
ClaudeOAuthService: () => ClaudeOAuthService
|
|
10207
|
+
});
|
|
10208
|
+
import { spawn as spawn4 } from "child_process";
|
|
10209
|
+
import { createHash, randomBytes } from "crypto";
|
|
10210
|
+
import http2 from "http";
|
|
10211
|
+
import { platform as platform2 } from "os";
|
|
10212
|
+
var OAUTH_CONFIG, CALLBACK_PORT, CALLBACK_TIMEOUT_MS2, ClaudeOAuthService;
|
|
10213
|
+
var init_claude_oauth = __esm({
|
|
10214
|
+
"src/utils/claude-oauth.ts"() {
|
|
10215
|
+
"use strict";
|
|
10216
|
+
OAUTH_CONFIG = {
|
|
10217
|
+
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
10218
|
+
// Claude Code's client ID
|
|
10219
|
+
authorizationEndpoint: "https://claude.ai/oauth/authorize",
|
|
10220
|
+
tokenEndpoint: "https://console.anthropic.com/v1/oauth/token",
|
|
10221
|
+
redirectUri: "http://localhost:8421/callback",
|
|
10222
|
+
// Local callback for CLI
|
|
10223
|
+
scopes: ["user:inference", "user:profile", "org:create_api_key"]
|
|
10224
|
+
};
|
|
10225
|
+
CALLBACK_PORT = 8421;
|
|
10226
|
+
CALLBACK_TIMEOUT_MS2 = 3e5;
|
|
10227
|
+
ClaudeOAuthService = class _ClaudeOAuthService {
|
|
10228
|
+
secretStorage;
|
|
10229
|
+
static TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
10230
|
+
// 5 minutes
|
|
10231
|
+
pendingCodeVerifier = null;
|
|
10232
|
+
// Store code verifier for PKCE
|
|
10233
|
+
constructor(secretStorage) {
|
|
10234
|
+
this.secretStorage = secretStorage;
|
|
10235
|
+
}
|
|
10236
|
+
/**
|
|
10237
|
+
* Starts the OAuth authorization flow
|
|
10238
|
+
* Opens the default browser for user authentication
|
|
10239
|
+
* Returns after successful authentication
|
|
10240
|
+
*/
|
|
10241
|
+
async authorize() {
|
|
10242
|
+
try {
|
|
10243
|
+
const state = this.generateRandomState();
|
|
10244
|
+
const pkce = this.generatePKCEChallenge();
|
|
10245
|
+
this.pendingCodeVerifier = pkce.codeVerifier;
|
|
10246
|
+
const authUrl = this.buildAuthorizationUrl(state, pkce.codeChallenge);
|
|
10247
|
+
console.log("\nAuthenticating with Claude...\n");
|
|
10248
|
+
console.log(`Opening browser to: ${authUrl}
|
|
10249
|
+
`);
|
|
10250
|
+
const tokenPromise = this.startCallbackServer(CALLBACK_PORT, state);
|
|
10251
|
+
try {
|
|
10252
|
+
this.openBrowser(authUrl);
|
|
10253
|
+
} catch (error) {
|
|
10254
|
+
console.warn("Failed to open browser automatically:", error);
|
|
10255
|
+
console.log(`
|
|
10256
|
+
Please manually open this URL in your browser:
|
|
10257
|
+
${authUrl}
|
|
10258
|
+
`);
|
|
10259
|
+
}
|
|
10260
|
+
await tokenPromise;
|
|
10261
|
+
console.log("\n\u2705 Successfully authenticated with Claude!\n");
|
|
10262
|
+
return { success: true };
|
|
10263
|
+
} catch (error) {
|
|
10264
|
+
this.pendingCodeVerifier = null;
|
|
10265
|
+
return {
|
|
10266
|
+
success: false,
|
|
10267
|
+
error: error instanceof Error ? error.message : "Authentication failed"
|
|
10268
|
+
};
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
10271
|
+
/**
|
|
10272
|
+
* Start local HTTP server to receive OAuth callback
|
|
10273
|
+
*/
|
|
10274
|
+
startCallbackServer(port, expectedState) {
|
|
10275
|
+
return new Promise((resolve2, reject) => {
|
|
10276
|
+
const server = http2.createServer(async (req, res) => {
|
|
10277
|
+
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
10278
|
+
res.writeHead(404);
|
|
10279
|
+
res.end("Not Found");
|
|
10280
|
+
return;
|
|
10281
|
+
}
|
|
10282
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
10283
|
+
const code = url.searchParams.get("code");
|
|
10284
|
+
const returnedState = url.searchParams.get("state");
|
|
10285
|
+
const error = url.searchParams.get("error");
|
|
10286
|
+
if (error) {
|
|
10287
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
10288
|
+
res.end(this.buildErrorPage(error));
|
|
10289
|
+
server.close();
|
|
10290
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
10291
|
+
return;
|
|
10292
|
+
}
|
|
10293
|
+
if (returnedState !== expectedState) {
|
|
10294
|
+
const errorMsg = "Security error: state parameter mismatch";
|
|
10295
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
10296
|
+
res.end(this.buildErrorPage(errorMsg));
|
|
10297
|
+
server.close();
|
|
10298
|
+
reject(new Error(errorMsg));
|
|
10299
|
+
return;
|
|
10300
|
+
}
|
|
10301
|
+
if (!code) {
|
|
10302
|
+
const errorMsg = "No authorization code received";
|
|
10303
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
10304
|
+
res.end(this.buildErrorPage(errorMsg));
|
|
10305
|
+
server.close();
|
|
10306
|
+
reject(new Error(errorMsg));
|
|
10307
|
+
return;
|
|
10308
|
+
}
|
|
10309
|
+
try {
|
|
10310
|
+
await this.submitAuthCode(code, returnedState);
|
|
10311
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
10312
|
+
res.end(this.buildSuccessPage());
|
|
10313
|
+
server.close();
|
|
10314
|
+
resolve2();
|
|
10315
|
+
} catch (err) {
|
|
10316
|
+
const errorMsg = err instanceof Error ? err.message : "Token exchange failed";
|
|
10317
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
10318
|
+
res.end(this.buildErrorPage(errorMsg));
|
|
10319
|
+
server.close();
|
|
10320
|
+
reject(err);
|
|
10321
|
+
}
|
|
10322
|
+
});
|
|
10323
|
+
server.on("error", (error) => {
|
|
10324
|
+
if (error.code === "EADDRINUSE") {
|
|
10325
|
+
reject(new Error("Port already in use. Please try again."));
|
|
10326
|
+
} else {
|
|
10327
|
+
reject(error);
|
|
10328
|
+
}
|
|
10329
|
+
});
|
|
10330
|
+
const timeout = setTimeout(() => {
|
|
10331
|
+
server.close();
|
|
10332
|
+
reject(new Error("Authentication timeout - no response received after 5 minutes"));
|
|
10333
|
+
}, CALLBACK_TIMEOUT_MS2);
|
|
10334
|
+
server.on("close", () => {
|
|
10335
|
+
clearTimeout(timeout);
|
|
10336
|
+
});
|
|
10337
|
+
server.listen(port, "127.0.0.1", () => {
|
|
10338
|
+
console.log(`Waiting for authentication callback on http://localhost:${port}/callback`);
|
|
10339
|
+
});
|
|
10340
|
+
});
|
|
10341
|
+
}
|
|
10342
|
+
/**
|
|
10343
|
+
* Submits the authorization code and exchanges it for tokens
|
|
10344
|
+
*/
|
|
10345
|
+
async submitAuthCode(code, state) {
|
|
10346
|
+
const tokens = await this.exchangeCodeForTokens(code, state);
|
|
10347
|
+
await this.saveTokens(tokens);
|
|
10348
|
+
return tokens;
|
|
10349
|
+
}
|
|
10350
|
+
/**
|
|
10351
|
+
* Exchanges authorization code for access and refresh tokens
|
|
10352
|
+
*/
|
|
10353
|
+
async exchangeCodeForTokens(code, state) {
|
|
10354
|
+
if (!this.pendingCodeVerifier) {
|
|
10355
|
+
throw new Error("No PKCE code verifier found. Please start the auth flow first.");
|
|
10356
|
+
}
|
|
10357
|
+
const body = {
|
|
10358
|
+
grant_type: "authorization_code",
|
|
10359
|
+
code,
|
|
10360
|
+
state,
|
|
10361
|
+
// Non-standard: state in body
|
|
10362
|
+
redirect_uri: OAUTH_CONFIG.redirectUri,
|
|
10363
|
+
client_id: OAUTH_CONFIG.clientId,
|
|
10364
|
+
code_verifier: this.pendingCodeVerifier
|
|
10365
|
+
// PKCE verifier
|
|
10366
|
+
};
|
|
10367
|
+
const response = await fetch(OAUTH_CONFIG.tokenEndpoint, {
|
|
10368
|
+
method: "POST",
|
|
10369
|
+
headers: {
|
|
10370
|
+
"Content-Type": "application/json"
|
|
10371
|
+
// Non-standard: JSON instead of form-encoded
|
|
10372
|
+
},
|
|
10373
|
+
body: JSON.stringify(body)
|
|
10374
|
+
});
|
|
10375
|
+
this.pendingCodeVerifier = null;
|
|
10376
|
+
if (!response.ok) {
|
|
10377
|
+
const error = await response.text();
|
|
10378
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
10379
|
+
}
|
|
10380
|
+
const data = await response.json();
|
|
10381
|
+
return {
|
|
10382
|
+
accessToken: data.access_token,
|
|
10383
|
+
refreshToken: data.refresh_token,
|
|
10384
|
+
expiresAt: Date.now() + data.expires_in * 1e3
|
|
10385
|
+
};
|
|
10386
|
+
}
|
|
10387
|
+
/**
|
|
10388
|
+
* Refreshes the access token using the refresh token
|
|
10389
|
+
*/
|
|
10390
|
+
async refreshTokens() {
|
|
10391
|
+
const tokens = await this.getTokens();
|
|
10392
|
+
if (!tokens) {
|
|
10393
|
+
throw new Error("No tokens found to refresh");
|
|
10394
|
+
}
|
|
10395
|
+
const body = {
|
|
10396
|
+
grant_type: "refresh_token",
|
|
10397
|
+
refresh_token: tokens.refreshToken,
|
|
10398
|
+
client_id: OAUTH_CONFIG.clientId
|
|
10399
|
+
};
|
|
10400
|
+
const response = await fetch(OAUTH_CONFIG.tokenEndpoint, {
|
|
10401
|
+
method: "POST",
|
|
10402
|
+
headers: {
|
|
10403
|
+
"Content-Type": "application/json"
|
|
10404
|
+
},
|
|
10405
|
+
body: JSON.stringify(body)
|
|
10406
|
+
});
|
|
10407
|
+
if (!response.ok) {
|
|
10408
|
+
const error = await response.text();
|
|
10409
|
+
throw new Error(`Token refresh failed: ${error}`);
|
|
10410
|
+
}
|
|
10411
|
+
const data = await response.json();
|
|
10412
|
+
const newTokens = {
|
|
10413
|
+
accessToken: data.access_token,
|
|
10414
|
+
refreshToken: data.refresh_token,
|
|
10415
|
+
expiresAt: Date.now() + data.expires_in * 1e3
|
|
10416
|
+
};
|
|
10417
|
+
await this.saveTokens(newTokens);
|
|
10418
|
+
return newTokens;
|
|
10419
|
+
}
|
|
10420
|
+
/**
|
|
10421
|
+
* Gets the current access token, refreshing if necessary
|
|
10422
|
+
*/
|
|
10423
|
+
async getAccessToken() {
|
|
10424
|
+
const tokens = await this.getTokens();
|
|
10425
|
+
if (!tokens) {
|
|
10426
|
+
return null;
|
|
10427
|
+
}
|
|
10428
|
+
if (Date.now() > tokens.expiresAt - _ClaudeOAuthService.TOKEN_REFRESH_BUFFER_MS) {
|
|
10429
|
+
try {
|
|
10430
|
+
const refreshedTokens = await this.refreshTokens();
|
|
10431
|
+
return refreshedTokens.accessToken;
|
|
10432
|
+
} catch (error) {
|
|
10433
|
+
console.warn("Token refresh failed:", error);
|
|
10434
|
+
return null;
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10437
|
+
return tokens.accessToken;
|
|
10438
|
+
}
|
|
10439
|
+
/**
|
|
10440
|
+
* Gets the stored tokens
|
|
10441
|
+
*/
|
|
10442
|
+
async getTokens() {
|
|
10443
|
+
try {
|
|
10444
|
+
const accessToken = await this.secretStorage.getSecret("claude_oauth_access_token");
|
|
10445
|
+
const refreshToken = await this.secretStorage.getSecret("claude_oauth_refresh_token");
|
|
10446
|
+
const expiresAt = await this.secretStorage.getSecret("claude_oauth_expires_at");
|
|
10447
|
+
if (!accessToken || !refreshToken || !expiresAt) {
|
|
10448
|
+
return null;
|
|
10449
|
+
}
|
|
10450
|
+
return {
|
|
10451
|
+
accessToken,
|
|
10452
|
+
refreshToken,
|
|
10453
|
+
expiresAt: parseInt(expiresAt, 10)
|
|
10454
|
+
};
|
|
10455
|
+
} catch (error) {
|
|
10456
|
+
console.error("Failed to get OAuth tokens:", error);
|
|
10457
|
+
return null;
|
|
10458
|
+
}
|
|
10459
|
+
}
|
|
10460
|
+
/**
|
|
10461
|
+
* Saves OAuth tokens to secure storage
|
|
10462
|
+
*/
|
|
10463
|
+
async saveTokens(tokens) {
|
|
10464
|
+
await this.secretStorage.setSecret("claude_oauth_access_token", tokens.accessToken);
|
|
10465
|
+
await this.secretStorage.setSecret("claude_oauth_refresh_token", tokens.refreshToken);
|
|
10466
|
+
await this.secretStorage.setSecret("claude_oauth_expires_at", tokens.expiresAt.toString());
|
|
10467
|
+
}
|
|
10468
|
+
/**
|
|
10469
|
+
* Deletes stored OAuth tokens
|
|
10470
|
+
*/
|
|
10471
|
+
async deleteTokens() {
|
|
10472
|
+
await this.secretStorage.deleteSecret("claude_oauth_access_token");
|
|
10473
|
+
await this.secretStorage.deleteSecret("claude_oauth_refresh_token");
|
|
10474
|
+
await this.secretStorage.deleteSecret("claude_oauth_expires_at");
|
|
10475
|
+
}
|
|
10476
|
+
/**
|
|
10477
|
+
* Checks if user is authenticated via OAuth
|
|
10478
|
+
*/
|
|
10479
|
+
async isAuthenticated() {
|
|
10480
|
+
const tokens = await this.getTokens();
|
|
10481
|
+
return tokens !== null;
|
|
10482
|
+
}
|
|
10483
|
+
/**
|
|
10484
|
+
* Gets the current OAuth authentication status
|
|
10485
|
+
*/
|
|
10486
|
+
async getStatus() {
|
|
10487
|
+
try {
|
|
10488
|
+
const tokens = await this.getTokens();
|
|
10489
|
+
if (!tokens) {
|
|
10490
|
+
return { isAuthenticated: false };
|
|
10491
|
+
}
|
|
10492
|
+
return {
|
|
10493
|
+
isAuthenticated: true,
|
|
10494
|
+
expiresAt: tokens.expiresAt
|
|
10495
|
+
};
|
|
10496
|
+
} catch (error) {
|
|
10497
|
+
return {
|
|
10498
|
+
isAuthenticated: false,
|
|
10499
|
+
error: error instanceof Error ? error.message : "Failed to get OAuth status"
|
|
10500
|
+
};
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
/**
|
|
10504
|
+
* Builds the authorization URL with all required parameters
|
|
10505
|
+
*/
|
|
10506
|
+
buildAuthorizationUrl(state, codeChallenge) {
|
|
10507
|
+
const params = new URLSearchParams({
|
|
10508
|
+
response_type: "code",
|
|
10509
|
+
client_id: OAUTH_CONFIG.clientId,
|
|
10510
|
+
redirect_uri: OAUTH_CONFIG.redirectUri,
|
|
10511
|
+
scope: OAUTH_CONFIG.scopes.join(" "),
|
|
10512
|
+
state
|
|
10513
|
+
});
|
|
10514
|
+
if (codeChallenge) {
|
|
10515
|
+
params.set("code_challenge", codeChallenge);
|
|
10516
|
+
params.set("code_challenge_method", "S256");
|
|
10517
|
+
}
|
|
10518
|
+
return `${OAUTH_CONFIG.authorizationEndpoint}?${params.toString()}`;
|
|
10519
|
+
}
|
|
10520
|
+
/**
|
|
10521
|
+
* Generates a random state string for CSRF protection
|
|
10522
|
+
*/
|
|
10523
|
+
generateRandomState() {
|
|
10524
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(32))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
10525
|
+
}
|
|
10526
|
+
/**
|
|
10527
|
+
* Generates PKCE code verifier and challenge
|
|
10528
|
+
* PKCE (Proof Key for Code Exchange) adds security to OAuth for public clients
|
|
10529
|
+
*/
|
|
10530
|
+
generatePKCEChallenge() {
|
|
10531
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
10532
|
+
const hash = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
10533
|
+
return {
|
|
10534
|
+
codeVerifier,
|
|
10535
|
+
codeChallenge: hash
|
|
10536
|
+
};
|
|
10537
|
+
}
|
|
10538
|
+
/**
|
|
10539
|
+
* Open a URL in the default browser cross-platform
|
|
10540
|
+
*/
|
|
10541
|
+
openBrowser(url) {
|
|
10542
|
+
const os3 = platform2();
|
|
10543
|
+
let command;
|
|
10544
|
+
let args;
|
|
10545
|
+
switch (os3) {
|
|
10546
|
+
case "darwin":
|
|
10547
|
+
command = "open";
|
|
10548
|
+
args = [url];
|
|
10549
|
+
break;
|
|
10550
|
+
case "win32":
|
|
10551
|
+
command = "start";
|
|
10552
|
+
args = ["", url];
|
|
10553
|
+
break;
|
|
10554
|
+
default:
|
|
10555
|
+
command = "xdg-open";
|
|
10556
|
+
args = [url];
|
|
10557
|
+
break;
|
|
10558
|
+
}
|
|
10559
|
+
const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
|
|
10560
|
+
spawn4(command, args, options).unref();
|
|
10561
|
+
}
|
|
10562
|
+
/**
|
|
10563
|
+
* Build success HTML page
|
|
10564
|
+
*/
|
|
10565
|
+
buildSuccessPage() {
|
|
10566
|
+
return `
|
|
10567
|
+
<!DOCTYPE html>
|
|
10568
|
+
<html lang="en">
|
|
10569
|
+
<head>
|
|
10570
|
+
<meta charset="UTF-8" />
|
|
10571
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10572
|
+
<title>Authentication Successful - Supatest CLI</title>
|
|
10573
|
+
<style>
|
|
10574
|
+
body {
|
|
10575
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10576
|
+
display: flex;
|
|
10577
|
+
align-items: center;
|
|
10578
|
+
justify-content: center;
|
|
10579
|
+
height: 100vh;
|
|
10580
|
+
margin: 0;
|
|
10581
|
+
background: #fefefe;
|
|
10582
|
+
}
|
|
10583
|
+
.container {
|
|
10584
|
+
background: white;
|
|
10585
|
+
padding: 3rem 2rem;
|
|
10586
|
+
border-radius: 12px;
|
|
10587
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
10588
|
+
border: 1px solid #e5e7eb;
|
|
10589
|
+
text-align: center;
|
|
10590
|
+
max-width: 400px;
|
|
10591
|
+
}
|
|
10592
|
+
.success-icon {
|
|
10593
|
+
font-size: 48px;
|
|
10594
|
+
margin-bottom: 1rem;
|
|
10595
|
+
}
|
|
10596
|
+
h1 {
|
|
10597
|
+
color: #10b981;
|
|
10598
|
+
margin: 0 0 1rem 0;
|
|
10599
|
+
font-size: 24px;
|
|
10600
|
+
}
|
|
10601
|
+
p {
|
|
10602
|
+
color: #666;
|
|
10603
|
+
margin: 0;
|
|
10604
|
+
line-height: 1.5;
|
|
10605
|
+
}
|
|
10606
|
+
</style>
|
|
10607
|
+
</head>
|
|
10608
|
+
<body>
|
|
10609
|
+
<div class="container">
|
|
10610
|
+
<div class="success-icon">\u2705</div>
|
|
10611
|
+
<h1>Authentication Successful!</h1>
|
|
10612
|
+
<p>You're now authenticated with Claude.</p>
|
|
10613
|
+
<p style="margin-top: 1rem;">You can close this window and return to your terminal.</p>
|
|
10614
|
+
</div>
|
|
10615
|
+
</body>
|
|
10616
|
+
</html>
|
|
10617
|
+
`;
|
|
10618
|
+
}
|
|
10619
|
+
/**
|
|
10620
|
+
* Build error HTML page
|
|
10621
|
+
*/
|
|
10622
|
+
buildErrorPage(errorMessage) {
|
|
10623
|
+
const escapedError = errorMessage.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
10624
|
+
return `
|
|
10625
|
+
<!DOCTYPE html>
|
|
10626
|
+
<html lang="en">
|
|
10627
|
+
<head>
|
|
10628
|
+
<meta charset="UTF-8" />
|
|
10629
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10630
|
+
<title>Authentication Failed - Supatest CLI</title>
|
|
10631
|
+
<style>
|
|
10632
|
+
body {
|
|
10633
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10634
|
+
display: flex;
|
|
10635
|
+
align-items: center;
|
|
10636
|
+
justify-content: center;
|
|
10637
|
+
height: 100vh;
|
|
10638
|
+
margin: 0;
|
|
10639
|
+
background: #fefefe;
|
|
10640
|
+
}
|
|
10641
|
+
.container {
|
|
10642
|
+
background: white;
|
|
10643
|
+
padding: 3rem 2rem;
|
|
10644
|
+
border-radius: 12px;
|
|
10645
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
10646
|
+
border: 1px solid #e5e7eb;
|
|
10647
|
+
text-align: center;
|
|
10648
|
+
max-width: 400px;
|
|
10649
|
+
}
|
|
10650
|
+
.error-icon {
|
|
10651
|
+
font-size: 48px;
|
|
10652
|
+
margin-bottom: 1rem;
|
|
10653
|
+
}
|
|
10654
|
+
h1 {
|
|
10655
|
+
color: #dc2626;
|
|
10656
|
+
margin: 0 0 1rem 0;
|
|
10657
|
+
font-size: 24px;
|
|
10658
|
+
}
|
|
10659
|
+
p {
|
|
10660
|
+
color: #666;
|
|
10661
|
+
margin: 0;
|
|
10662
|
+
line-height: 1.5;
|
|
10663
|
+
}
|
|
10664
|
+
</style>
|
|
10665
|
+
</head>
|
|
10666
|
+
<body>
|
|
10667
|
+
<div class="container">
|
|
10668
|
+
<div class="error-icon">\u274C</div>
|
|
10669
|
+
<h1>Authentication Failed</h1>
|
|
10670
|
+
<p>${escapedError}</p>
|
|
10671
|
+
<p style="margin-top: 1rem;">You can close this window and try again.</p>
|
|
10672
|
+
</div>
|
|
10673
|
+
</body>
|
|
10674
|
+
</html>
|
|
10675
|
+
`;
|
|
10676
|
+
}
|
|
10677
|
+
};
|
|
9548
10678
|
}
|
|
10679
|
+
});
|
|
10680
|
+
|
|
10681
|
+
// src/utils/secret-storage.ts
|
|
10682
|
+
var secret_storage_exports = {};
|
|
10683
|
+
__export(secret_storage_exports, {
|
|
10684
|
+
deleteSecret: () => deleteSecret,
|
|
10685
|
+
getSecret: () => getSecret,
|
|
10686
|
+
getSecretStorage: () => getSecretStorage,
|
|
10687
|
+
listSecrets: () => listSecrets,
|
|
10688
|
+
setSecret: () => setSecret
|
|
10689
|
+
});
|
|
10690
|
+
import { promises as fs4 } from "fs";
|
|
10691
|
+
import { homedir as homedir6 } from "os";
|
|
10692
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
10693
|
+
async function getSecret(key) {
|
|
10694
|
+
return storage.getSecret(key);
|
|
9549
10695
|
}
|
|
9550
|
-
function
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
9562
|
-
logger.debug("[claude-max] macOS keychain credentials check", {
|
|
9563
|
-
hasOauth,
|
|
9564
|
-
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
9565
|
-
});
|
|
9566
|
-
return hasOauth;
|
|
9567
|
-
} catch (error) {
|
|
9568
|
-
logger.debug("[claude-max] Error checking macOS keychain", {
|
|
9569
|
-
error: error instanceof Error ? error.message : String(error)
|
|
9570
|
-
});
|
|
9571
|
-
return false;
|
|
9572
|
-
}
|
|
10696
|
+
async function setSecret(key, value) {
|
|
10697
|
+
await storage.setSecret(key, value);
|
|
10698
|
+
}
|
|
10699
|
+
async function deleteSecret(key) {
|
|
10700
|
+
return storage.deleteSecret(key);
|
|
10701
|
+
}
|
|
10702
|
+
async function listSecrets() {
|
|
10703
|
+
return storage.listSecrets();
|
|
10704
|
+
}
|
|
10705
|
+
function getSecretStorage() {
|
|
10706
|
+
return storage;
|
|
9573
10707
|
}
|
|
9574
|
-
|
|
10708
|
+
var SECRET_FILE_NAME, FileSecretStorage, storage;
|
|
10709
|
+
var init_secret_storage = __esm({
|
|
10710
|
+
"src/utils/secret-storage.ts"() {
|
|
10711
|
+
"use strict";
|
|
10712
|
+
SECRET_FILE_NAME = "secrets.json";
|
|
10713
|
+
FileSecretStorage = class {
|
|
10714
|
+
secretFilePath;
|
|
10715
|
+
constructor() {
|
|
10716
|
+
const rootDirName = process.env.NODE_ENV === "development" ? ".supatest-dev" : ".supatest";
|
|
10717
|
+
const secretsDir = join8(homedir6(), rootDirName, "claude-auth");
|
|
10718
|
+
this.secretFilePath = join8(secretsDir, SECRET_FILE_NAME);
|
|
10719
|
+
}
|
|
10720
|
+
async ensureDirectoryExists() {
|
|
10721
|
+
const dir = dirname2(this.secretFilePath);
|
|
10722
|
+
await fs4.mkdir(dir, { recursive: true, mode: 448 });
|
|
10723
|
+
}
|
|
10724
|
+
async loadSecrets() {
|
|
10725
|
+
try {
|
|
10726
|
+
const data = await fs4.readFile(this.secretFilePath, "utf-8");
|
|
10727
|
+
const secrets = JSON.parse(data);
|
|
10728
|
+
return new Map(Object.entries(secrets));
|
|
10729
|
+
} catch (error) {
|
|
10730
|
+
const err = error;
|
|
10731
|
+
if (err.code === "ENOENT") {
|
|
10732
|
+
return /* @__PURE__ */ new Map();
|
|
10733
|
+
}
|
|
10734
|
+
try {
|
|
10735
|
+
await fs4.unlink(this.secretFilePath);
|
|
10736
|
+
} catch {
|
|
10737
|
+
}
|
|
10738
|
+
return /* @__PURE__ */ new Map();
|
|
10739
|
+
}
|
|
10740
|
+
}
|
|
10741
|
+
async saveSecrets(secrets) {
|
|
10742
|
+
await this.ensureDirectoryExists();
|
|
10743
|
+
const data = Object.fromEntries(secrets);
|
|
10744
|
+
const json = JSON.stringify(data, null, 2);
|
|
10745
|
+
await fs4.writeFile(this.secretFilePath, json, { mode: 384 });
|
|
10746
|
+
}
|
|
10747
|
+
async getSecret(key) {
|
|
10748
|
+
const secrets = await this.loadSecrets();
|
|
10749
|
+
return secrets.get(key) ?? null;
|
|
10750
|
+
}
|
|
10751
|
+
async setSecret(key, value) {
|
|
10752
|
+
const secrets = await this.loadSecrets();
|
|
10753
|
+
secrets.set(key, value);
|
|
10754
|
+
await this.saveSecrets(secrets);
|
|
10755
|
+
}
|
|
10756
|
+
async deleteSecret(key) {
|
|
10757
|
+
const secrets = await this.loadSecrets();
|
|
10758
|
+
if (!secrets.has(key)) {
|
|
10759
|
+
return false;
|
|
10760
|
+
}
|
|
10761
|
+
secrets.delete(key);
|
|
10762
|
+
if (secrets.size === 0) {
|
|
10763
|
+
try {
|
|
10764
|
+
await fs4.unlink(this.secretFilePath);
|
|
10765
|
+
} catch (error) {
|
|
10766
|
+
const err = error;
|
|
10767
|
+
if (err.code !== "ENOENT") {
|
|
10768
|
+
throw error;
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10771
|
+
} else {
|
|
10772
|
+
await this.saveSecrets(secrets);
|
|
10773
|
+
}
|
|
10774
|
+
return true;
|
|
10775
|
+
}
|
|
10776
|
+
async listSecrets() {
|
|
10777
|
+
const secrets = await this.loadSecrets();
|
|
10778
|
+
return Array.from(secrets.keys());
|
|
10779
|
+
}
|
|
10780
|
+
};
|
|
10781
|
+
storage = new FileSecretStorage();
|
|
10782
|
+
}
|
|
10783
|
+
});
|
|
10784
|
+
|
|
10785
|
+
// src/utils/claude-max.ts
|
|
10786
|
+
async function isClaudeMaxAvailable() {
|
|
10787
|
+
logger.debug("[claude-max] Checking Supatest OAuth credentials");
|
|
9575
10788
|
try {
|
|
9576
|
-
const
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
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;
|
|
9580
10795
|
}
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
9584
|
-
logger.debug("[claude-max] Credentials file check", {
|
|
9585
|
-
path: credentialsPath,
|
|
9586
|
-
hasOauth,
|
|
9587
|
-
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
9588
|
-
});
|
|
9589
|
-
return hasOauth;
|
|
10796
|
+
logger.debug("[claude-max] No Supatest OAuth credentials found");
|
|
10797
|
+
return false;
|
|
9590
10798
|
} catch (error) {
|
|
9591
|
-
logger.debug("[claude-max] Error checking
|
|
10799
|
+
logger.debug("[claude-max] Error checking Supatest OAuth storage", {
|
|
9592
10800
|
error: error instanceof Error ? error.message : String(error)
|
|
9593
10801
|
});
|
|
9594
10802
|
return false;
|
|
@@ -9597,12 +10805,14 @@ function checkCredentialsFile() {
|
|
|
9597
10805
|
var init_claude_max = __esm({
|
|
9598
10806
|
"src/utils/claude-max.ts"() {
|
|
9599
10807
|
"use strict";
|
|
10808
|
+
init_claude_oauth();
|
|
9600
10809
|
init_logger();
|
|
10810
|
+
init_secret_storage();
|
|
9601
10811
|
}
|
|
9602
10812
|
});
|
|
9603
10813
|
|
|
9604
10814
|
// src/utils/mcp-manager.ts
|
|
9605
|
-
import { existsSync as
|
|
10815
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
9606
10816
|
import { homedir as homedir7 } from "os";
|
|
9607
10817
|
import { join as join9 } from "path";
|
|
9608
10818
|
function getGlobalMcpPath() {
|
|
@@ -9612,11 +10822,11 @@ function getProjectMcpPath(cwd) {
|
|
|
9612
10822
|
return join9(cwd, ".supatest", "mcp.json");
|
|
9613
10823
|
}
|
|
9614
10824
|
function loadMcpConfigFromFile(mcpPath, scope) {
|
|
9615
|
-
if (!
|
|
10825
|
+
if (!existsSync6(mcpPath)) {
|
|
9616
10826
|
return {};
|
|
9617
10827
|
}
|
|
9618
10828
|
try {
|
|
9619
|
-
const content =
|
|
10829
|
+
const content = readFileSync5(mcpPath, "utf-8");
|
|
9620
10830
|
const config2 = JSON.parse(content);
|
|
9621
10831
|
if (!config2.mcpServers) {
|
|
9622
10832
|
return {};
|
|
@@ -9650,7 +10860,7 @@ function loadMcpConfig(cwd) {
|
|
|
9650
10860
|
}
|
|
9651
10861
|
function saveMcpConfigToFile(mcpPath, servers) {
|
|
9652
10862
|
const mcpDir = join9(mcpPath, "..");
|
|
9653
|
-
if (!
|
|
10863
|
+
if (!existsSync6(mcpDir)) {
|
|
9654
10864
|
mkdirSync3(mcpDir, { recursive: true });
|
|
9655
10865
|
}
|
|
9656
10866
|
const config2 = {
|
|
@@ -9834,15 +11044,15 @@ var init_mcp_manager = __esm({
|
|
|
9834
11044
|
});
|
|
9835
11045
|
|
|
9836
11046
|
// src/utils/settings-loader.ts
|
|
9837
|
-
import { existsSync as
|
|
11047
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
9838
11048
|
import { join as join10 } from "path";
|
|
9839
11049
|
function loadSupatestSettings(cwd) {
|
|
9840
11050
|
const settingsPath = join10(cwd, ".supatest", "settings.json");
|
|
9841
|
-
if (!
|
|
11051
|
+
if (!existsSync7(settingsPath)) {
|
|
9842
11052
|
return {};
|
|
9843
11053
|
}
|
|
9844
11054
|
try {
|
|
9845
|
-
const content =
|
|
11055
|
+
const content = readFileSync6(settingsPath, "utf-8");
|
|
9846
11056
|
return JSON.parse(content);
|
|
9847
11057
|
} catch (error) {
|
|
9848
11058
|
console.warn(
|
|
@@ -9856,7 +11066,7 @@ function saveSupatestSettings(cwd, settings) {
|
|
|
9856
11066
|
const settingsDir = join10(cwd, ".supatest");
|
|
9857
11067
|
const settingsPath = join10(settingsDir, "settings.json");
|
|
9858
11068
|
try {
|
|
9859
|
-
if (!
|
|
11069
|
+
if (!existsSync7(settingsDir)) {
|
|
9860
11070
|
mkdirSync4(settingsDir, { recursive: true });
|
|
9861
11071
|
}
|
|
9862
11072
|
const existingSettings = loadSupatestSettings(cwd);
|
|
@@ -11035,18 +12245,37 @@ var init_TestSelector = __esm({
|
|
|
11035
12245
|
apiClient,
|
|
11036
12246
|
run,
|
|
11037
12247
|
onSelect,
|
|
11038
|
-
onCancel
|
|
12248
|
+
onCancel,
|
|
12249
|
+
assignments = []
|
|
11039
12250
|
}) => {
|
|
11040
12251
|
const [allTests, setAllTests] = useState7([]);
|
|
11041
12252
|
const [selectedTests, setSelectedTests] = useState7(/* @__PURE__ */ new Set());
|
|
11042
12253
|
const [cursorIndex, setCursorIndex] = useState7(0);
|
|
11043
12254
|
const [isLoading, setIsLoading] = useState7(false);
|
|
12255
|
+
const [isLoadingAssignments, setIsLoadingAssignments] = useState7(true);
|
|
11044
12256
|
const [hasMore, setHasMore] = useState7(true);
|
|
11045
12257
|
const [totalTests, setTotalTests] = useState7(0);
|
|
11046
12258
|
const [error, setError] = useState7(null);
|
|
12259
|
+
const [showAvailableOnly, setShowAvailableOnly] = useState7(true);
|
|
12260
|
+
const [groupByFile, setGroupByFile] = useState7(false);
|
|
12261
|
+
const assignedTestMap = new Map(assignments.map((a) => [a.testRunId, a]));
|
|
12262
|
+
const assignedTestIds = new Set(assignments.map((a) => a.testRunId));
|
|
11047
12263
|
useEffect7(() => {
|
|
11048
12264
|
loadMoreTests();
|
|
11049
12265
|
}, []);
|
|
12266
|
+
useEffect7(() => {
|
|
12267
|
+
fetchAssignments();
|
|
12268
|
+
}, [run.id]);
|
|
12269
|
+
const fetchAssignments = async () => {
|
|
12270
|
+
setIsLoadingAssignments(true);
|
|
12271
|
+
try {
|
|
12272
|
+
const result = await apiClient.getRunAssignments(run.id);
|
|
12273
|
+
} catch (err) {
|
|
12274
|
+
console.error("Failed to load assignments:", err);
|
|
12275
|
+
} finally {
|
|
12276
|
+
setIsLoadingAssignments(false);
|
|
12277
|
+
}
|
|
12278
|
+
};
|
|
11050
12279
|
const loadMoreTests = async () => {
|
|
11051
12280
|
if (isLoading || !hasMore) {
|
|
11052
12281
|
return;
|
|
@@ -11072,8 +12301,34 @@ var init_TestSelector = __esm({
|
|
|
11072
12301
|
setIsLoading(false);
|
|
11073
12302
|
}
|
|
11074
12303
|
};
|
|
11075
|
-
const
|
|
11076
|
-
|
|
12304
|
+
const getFilteredTests = () => {
|
|
12305
|
+
if (showAvailableOnly) {
|
|
12306
|
+
return allTests.filter((t) => !assignedTestIds.has(t.id));
|
|
12307
|
+
}
|
|
12308
|
+
return allTests;
|
|
12309
|
+
};
|
|
12310
|
+
const filteredTests = getFilteredTests();
|
|
12311
|
+
const availableCount = allTests.filter((t) => !assignedTestIds.has(t.id)).length;
|
|
12312
|
+
const assignedCount = assignedTestIds.size;
|
|
12313
|
+
const getTestsByFile = () => {
|
|
12314
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12315
|
+
for (const test of filteredTests) {
|
|
12316
|
+
const file = test.file;
|
|
12317
|
+
if (!groups.has(file)) {
|
|
12318
|
+
groups.set(file, []);
|
|
12319
|
+
}
|
|
12320
|
+
groups.get(file).push(test);
|
|
12321
|
+
}
|
|
12322
|
+
return Array.from(groups.entries()).map(([file, tests]) => ({
|
|
12323
|
+
file,
|
|
12324
|
+
tests,
|
|
12325
|
+
availableCount: tests.filter((t) => !assignedTestIds.has(t.id)).length,
|
|
12326
|
+
assignedCount: tests.filter((t) => assignedTestIds.has(t.id)).length
|
|
12327
|
+
}));
|
|
12328
|
+
};
|
|
12329
|
+
const fileGroups = getTestsByFile();
|
|
12330
|
+
const totalItems = groupByFile ? fileGroups.length + 1 : filteredTests.length + 1;
|
|
12331
|
+
const isOnFixNext10 = cursorIndex === 0;
|
|
11077
12332
|
const toggleTest = (testId) => {
|
|
11078
12333
|
setSelectedTests((prev) => {
|
|
11079
12334
|
const next = new Set(prev);
|
|
@@ -11085,50 +12340,92 @@ var init_TestSelector = __esm({
|
|
|
11085
12340
|
return next;
|
|
11086
12341
|
});
|
|
11087
12342
|
};
|
|
11088
|
-
const selectAll = () => {
|
|
11089
|
-
setSelectedTests(new Set(allTests.map((t) => t.id)));
|
|
11090
|
-
};
|
|
11091
12343
|
const selectNone = () => {
|
|
11092
12344
|
setSelectedTests(/* @__PURE__ */ new Set());
|
|
11093
12345
|
};
|
|
12346
|
+
const selectAvailable = (tests) => {
|
|
12347
|
+
const availableTests = tests.filter((t) => !assignedTestIds.has(t.id));
|
|
12348
|
+
setSelectedTests(new Set(availableTests.map((t) => t.id)));
|
|
12349
|
+
};
|
|
12350
|
+
const selectFileTests = (fileTests) => {
|
|
12351
|
+
const available = fileTests.filter((t) => !assignedTestIds.has(t.id));
|
|
12352
|
+
const newSelected = new Set(selectedTests);
|
|
12353
|
+
const allSelected = available.every((t) => newSelected.has(t.id));
|
|
12354
|
+
if (allSelected) {
|
|
12355
|
+
for (const test of available) {
|
|
12356
|
+
newSelected.delete(test.id);
|
|
12357
|
+
}
|
|
12358
|
+
} else {
|
|
12359
|
+
for (const test of available) {
|
|
12360
|
+
newSelected.add(test.id);
|
|
12361
|
+
}
|
|
12362
|
+
}
|
|
12363
|
+
setSelectedTests(newSelected);
|
|
12364
|
+
};
|
|
11094
12365
|
useInput3((input, key) => {
|
|
11095
|
-
if (allTests.length === 0 && !isLoading) {
|
|
12366
|
+
if (allTests.length === 0 && !isLoading && !isLoadingAssignments) {
|
|
11096
12367
|
if (key.escape || input === "q") {
|
|
11097
12368
|
onCancel();
|
|
11098
12369
|
}
|
|
11099
12370
|
return;
|
|
11100
12371
|
}
|
|
12372
|
+
if (input === "f") {
|
|
12373
|
+
setGroupByFile((prev) => !prev);
|
|
12374
|
+
setCursorIndex(0);
|
|
12375
|
+
return;
|
|
12376
|
+
}
|
|
12377
|
+
if (input === "t") {
|
|
12378
|
+
setShowAvailableOnly((prev) => !prev);
|
|
12379
|
+
setCursorIndex(0);
|
|
12380
|
+
return;
|
|
12381
|
+
}
|
|
11101
12382
|
if (key.upArrow) {
|
|
11102
12383
|
setCursorIndex((prev) => prev > 0 ? prev - 1 : totalItems - 1);
|
|
11103
12384
|
} else if (key.downArrow) {
|
|
11104
12385
|
const newIndex = cursorIndex < totalItems - 1 ? cursorIndex + 1 : 0;
|
|
11105
12386
|
setCursorIndex(newIndex);
|
|
11106
|
-
if (newIndex >= allTests.length - 2 && hasMore && !isLoading) {
|
|
12387
|
+
if (!groupByFile && newIndex >= allTests.length - 2 && hasMore && !isLoading) {
|
|
11107
12388
|
loadMoreTests();
|
|
11108
12389
|
}
|
|
11109
12390
|
} else if (input === " ") {
|
|
11110
|
-
if (
|
|
11111
|
-
|
|
12391
|
+
if (isOnFixNext10) {
|
|
12392
|
+
const availableTests = getFilteredTests();
|
|
12393
|
+
const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
|
|
12394
|
+
selectAvailable(next10);
|
|
12395
|
+
} else if (groupByFile) {
|
|
12396
|
+
const fileGroups2 = getTestsByFile();
|
|
12397
|
+
const fileIndex = cursorIndex - 1;
|
|
12398
|
+
if (fileGroups2[fileIndex]) {
|
|
12399
|
+
selectFileTests(fileGroups2[fileIndex].tests);
|
|
12400
|
+
}
|
|
11112
12401
|
} else {
|
|
11113
12402
|
const testIndex = cursorIndex - 1;
|
|
11114
|
-
|
|
11115
|
-
|
|
12403
|
+
const filteredTests2 = getFilteredTests();
|
|
12404
|
+
if (filteredTests2[testIndex]) {
|
|
12405
|
+
const test = filteredTests2[testIndex];
|
|
12406
|
+
if (!assignedTestIds.has(test.id)) {
|
|
12407
|
+
toggleTest(test.id);
|
|
12408
|
+
}
|
|
11116
12409
|
}
|
|
11117
12410
|
}
|
|
11118
|
-
} else if (input === "a") {
|
|
11119
|
-
selectAll();
|
|
11120
12411
|
} else if (input === "n") {
|
|
11121
12412
|
selectNone();
|
|
12413
|
+
} else if (input === "s") {
|
|
12414
|
+
const availableTests = getFilteredTests();
|
|
12415
|
+
const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
|
|
12416
|
+
selectAvailable(next10);
|
|
11122
12417
|
} else if (key.return) {
|
|
11123
|
-
|
|
11124
|
-
|
|
12418
|
+
const availableTests = getFilteredTests();
|
|
12419
|
+
if (isOnFixNext10) {
|
|
12420
|
+
const next10 = availableTests.slice(0, Math.min(10, availableTests.length));
|
|
12421
|
+
onSelect(next10);
|
|
11125
12422
|
} else if (selectedTests.size > 0) {
|
|
11126
|
-
const testsToFix =
|
|
12423
|
+
const testsToFix = availableTests.filter((t) => selectedTests.has(t.id));
|
|
11127
12424
|
onSelect(testsToFix);
|
|
11128
|
-
} else {
|
|
12425
|
+
} else if (!groupByFile) {
|
|
11129
12426
|
const testIndex = cursorIndex - 1;
|
|
11130
|
-
if (
|
|
11131
|
-
onSelect([
|
|
12427
|
+
if (availableTests[testIndex] && !assignedTestIds.has(availableTests[testIndex].id)) {
|
|
12428
|
+
onSelect([availableTests[testIndex]]);
|
|
11132
12429
|
}
|
|
11133
12430
|
}
|
|
11134
12431
|
} else if (key.escape || input === "q") {
|
|
@@ -11145,44 +12442,66 @@ var init_TestSelector = __esm({
|
|
|
11145
12442
|
return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "green" }, "No Failed Tests"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "All tests passed in this run. Nothing to fix!"), /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "ESC"), " to go back")));
|
|
11146
12443
|
}
|
|
11147
12444
|
const testStartIndex = Math.max(0, cursorIndex - 1);
|
|
11148
|
-
const adjustedStart =
|
|
11149
|
-
const adjustedEnd = Math.min(
|
|
11150
|
-
const visibleTests =
|
|
12445
|
+
const adjustedStart = isOnFixNext10 ? 0 : Math.max(0, testStartIndex - Math.floor(VISIBLE_ITEMS2 / 2));
|
|
12446
|
+
const adjustedEnd = Math.min(filteredTests.length, adjustedStart + VISIBLE_ITEMS2);
|
|
12447
|
+
const visibleTests = filteredTests.slice(adjustedStart, adjustedEnd);
|
|
11151
12448
|
const branch = run.git?.branch || "unknown";
|
|
11152
12449
|
const commit = run.git?.commit?.slice(0, 7) || "";
|
|
11153
|
-
return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "cyan" }, "Run: ", branch, commit && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " @ ", commit), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "red" }, allTests.length, " failed
|
|
12450
|
+
return /* @__PURE__ */ React21.createElement(Box18, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { bold: true, color: "cyan" }, "Run: ", branch, commit && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " @ ", commit), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "red" }, allTests.length, " failed"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, availableCount, " avail"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, assignedCount, " working"))), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "[", showAvailableOnly ? "x" : " ", "] ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " avail only", " ", "[", groupByFile ? "x" : " ", "] ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files")), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
|
|
11154
12451
|
Text16,
|
|
11155
12452
|
{
|
|
11156
|
-
backgroundColor:
|
|
11157
|
-
bold:
|
|
11158
|
-
color:
|
|
12453
|
+
backgroundColor: isOnFixNext10 ? theme.text.accent : void 0,
|
|
12454
|
+
bold: isOnFixNext10,
|
|
12455
|
+
color: isOnFixNext10 ? "black" : theme.text.primary
|
|
11159
12456
|
},
|
|
11160
|
-
|
|
11161
|
-
"[Fix
|
|
11162
|
-
|
|
11163
|
-
" Failed Test",
|
|
11164
|
-
allTests.length !== 1 ? "s" : "",
|
|
12457
|
+
isOnFixNext10 ? "\u25B6 " : " ",
|
|
12458
|
+
"[Fix Next 10 Available Test",
|
|
12459
|
+
Math.min(10, filteredTests.length) !== 1 ? "s" : "",
|
|
11165
12460
|
"]"
|
|
11166
|
-
)), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
12461
|
+
)), /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")), groupByFile ? (
|
|
12462
|
+
// File Grouping Mode
|
|
12463
|
+
fileGroups.map((group, index) => {
|
|
12464
|
+
const itemIndex = index + 1;
|
|
12465
|
+
const isSelected = itemIndex === cursorIndex;
|
|
12466
|
+
const bgColor = isSelected ? theme.text.accent : void 0;
|
|
12467
|
+
const indicator = isSelected ? "\u25B6 " : " ";
|
|
12468
|
+
const fileName = group.file.split("/").pop() || group.file;
|
|
12469
|
+
const allSelected = group.availableCount > 0 && group.tests.filter((t) => !assignedTestIds.has(t.id)).every((t) => selectedTests.has(t.id));
|
|
12470
|
+
const someSelected = group.tests.filter((t) => !assignedTestIds.has(t.id)).some((t) => selectedTests.has(t.id));
|
|
12471
|
+
return /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", key: group.file, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: allSelected ? "green" : someSelected ? "yellow" : isSelected ? "black" : theme.text.dim }, "[", allSelected ? "\u2713" : someSelected ? "\u2297" : " ", "]"), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, fileName), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, group.availableCount, " available"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, group.assignedCount, " working"), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, group.tests.length, " total")), isSelected && group.tests.length <= 5 && /* @__PURE__ */ React21.createElement(Box18, { marginLeft: 2 }, group.tests.map((test) => {
|
|
12472
|
+
const line = test.location?.line || "";
|
|
12473
|
+
const title = test.title;
|
|
12474
|
+
const isAssigned = assignedTestIds.has(test.id);
|
|
12475
|
+
return /* @__PURE__ */ React21.createElement(Box18, { key: test.id }, /* @__PURE__ */ React21.createElement(Text16, { color: isAssigned ? theme.text.dim : theme.text.primary }, isAssigned ? "\u{1F504} " : " ", line && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, line, ": "), title));
|
|
12476
|
+
})));
|
|
12477
|
+
})
|
|
12478
|
+
) : (
|
|
12479
|
+
// Individual Tests Mode
|
|
12480
|
+
visibleTests.map((test, index) => {
|
|
12481
|
+
const actualIndex = adjustedStart + index;
|
|
12482
|
+
const itemIndex = actualIndex + 1;
|
|
12483
|
+
const isSelected = itemIndex === cursorIndex;
|
|
12484
|
+
const isChecked = selectedTests.has(test.id);
|
|
12485
|
+
const isAssigned = assignedTestIds.has(test.id);
|
|
12486
|
+
const assignment = assignedTestMap.get(test.id);
|
|
12487
|
+
const checkbox = isChecked ? "[x]" : "[ ]";
|
|
12488
|
+
const file = test.file.split("/").pop() || test.file;
|
|
12489
|
+
const line = test.location?.line || "";
|
|
12490
|
+
const title = test.title;
|
|
12491
|
+
const indicator = isSelected ? "\u25B6 " : " ";
|
|
12492
|
+
const bgColor = isSelected ? theme.text.accent : void 0;
|
|
12493
|
+
const assignee = assignment?.assignedTo || "";
|
|
12494
|
+
const displayAssignee = assignee.startsWith("cli:") ? assignee.slice(4) : assignee;
|
|
12495
|
+
return /* @__PURE__ */ React21.createElement(Box18, { key: test.id, marginBottom: 0 }, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: isAssigned ? theme.text.dim : isChecked ? "green" : isSelected ? "black" : theme.text.dim }, isAssigned ? "\u{1F504}" : checkbox), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, file, line && /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, ":", line), isAssigned && /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, displayAssignee)), /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, " - "), title));
|
|
12496
|
+
})
|
|
12497
|
+
)), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, !groupByFile && filteredTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", filteredTests.length, " ", showAvailableOnly ? "available" : "", " tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), groupByFile && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", fileGroups.length, " file", fileGroups.length !== 1 ? "s" : "", " \u2022 ", filteredTests.length, " total test", filteredTests.length !== 1 ? "s" : "")), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "s"), " next 10 \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " toggle filter \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), (isLoading || isLoadingAssignments) && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, isLoading ? "Loading more tests..." : "Loading assignments..."))));
|
|
11179
12498
|
};
|
|
11180
12499
|
}
|
|
11181
12500
|
});
|
|
11182
12501
|
|
|
11183
12502
|
// src/ui/components/FixFlow.tsx
|
|
11184
12503
|
import { Box as Box19, Text as Text17, useInput as useInput4 } from "ink";
|
|
11185
|
-
import React22, { useState as useState8 } from "react";
|
|
12504
|
+
import React22, { useEffect as useEffect8, useState as useState8 } from "react";
|
|
11186
12505
|
var FixFlow;
|
|
11187
12506
|
var init_FixFlow = __esm({
|
|
11188
12507
|
"src/ui/components/FixFlow.tsx"() {
|
|
@@ -11201,6 +12520,8 @@ var init_FixFlow = __esm({
|
|
|
11201
12520
|
const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
|
|
11202
12521
|
const [selectedRun, setSelectedRun] = useState8(null);
|
|
11203
12522
|
const [selectedTests, setSelectedTests] = useState8([]);
|
|
12523
|
+
const [assignments, setAssignments] = useState8([]);
|
|
12524
|
+
const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
|
|
11204
12525
|
const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
|
|
11205
12526
|
const [loadError, setLoadError] = useState8(null);
|
|
11206
12527
|
useInput4((input, key) => {
|
|
@@ -11210,7 +12531,7 @@ var init_FixFlow = __esm({
|
|
|
11210
12531
|
setStep("select-run");
|
|
11211
12532
|
} else if (step === "select-run") {
|
|
11212
12533
|
onCancel();
|
|
11213
|
-
} else if (step === "loading-details" && loadError) {
|
|
12534
|
+
} else if ((step === "loading-details" || step === "claiming-tests" || step === "error") && loadError) {
|
|
11214
12535
|
setLoadError(null);
|
|
11215
12536
|
setStep("select-tests");
|
|
11216
12537
|
}
|
|
@@ -11219,13 +12540,47 @@ var init_FixFlow = __esm({
|
|
|
11219
12540
|
const handleRunSelect = (run) => {
|
|
11220
12541
|
setSelectedRun(run);
|
|
11221
12542
|
setStep("select-tests");
|
|
12543
|
+
fetchAssignments(run.id);
|
|
12544
|
+
};
|
|
12545
|
+
const fetchAssignments = async (runId) => {
|
|
12546
|
+
try {
|
|
12547
|
+
const result = await apiClient.getRunAssignments(runId);
|
|
12548
|
+
setAssignments(result.assignments);
|
|
12549
|
+
} catch (err) {
|
|
12550
|
+
console.error("Failed to load assignments:", err);
|
|
12551
|
+
}
|
|
11222
12552
|
};
|
|
11223
12553
|
const handleTestsSelect = async (tests) => {
|
|
11224
12554
|
setSelectedTests(tests);
|
|
11225
|
-
setStep("
|
|
11226
|
-
setLoadingProgress({ current: 0, total: tests.length });
|
|
12555
|
+
setStep("claiming-tests");
|
|
11227
12556
|
setLoadError(null);
|
|
11228
12557
|
try {
|
|
12558
|
+
if (selectedRun) {
|
|
12559
|
+
const claimResult = await apiClient.assignTests({
|
|
12560
|
+
runId: selectedRun.id,
|
|
12561
|
+
testRunIds: tests.map((t) => t.id)
|
|
12562
|
+
});
|
|
12563
|
+
if (claimResult.conflicts.length > 0) {
|
|
12564
|
+
const conflictList = claimResult.conflicts.slice(0, 5).map((c) => `${c.file}: ${c.title} (claimed by ${c.currentAssignee})`).join("\n\u2022 ");
|
|
12565
|
+
const moreCount = claimResult.conflicts.length - 5;
|
|
12566
|
+
setLoadError(
|
|
12567
|
+
`${claimResult.conflicts.length} test${claimResult.conflicts.length > 1 ? "s were" : " was"} already claimed by others:
|
|
12568
|
+
\u2022 ${conflictList}${moreCount > 0 ? `
|
|
12569
|
+
\u2022 ... and ${moreCount} more` : ""}
|
|
12570
|
+
|
|
12571
|
+
Please select different tests.`
|
|
12572
|
+
);
|
|
12573
|
+
setStep("error");
|
|
12574
|
+
return;
|
|
12575
|
+
}
|
|
12576
|
+
const assignmentMap = /* @__PURE__ */ new Map();
|
|
12577
|
+
claimResult.assigned.forEach((assignment) => {
|
|
12578
|
+
assignmentMap.set(assignment.testRunId, assignment.id);
|
|
12579
|
+
});
|
|
12580
|
+
setAssignmentIds(assignmentMap);
|
|
12581
|
+
}
|
|
12582
|
+
setStep("loading-details");
|
|
12583
|
+
setLoadingProgress({ current: 0, total: tests.length });
|
|
11229
12584
|
const testDetails = [];
|
|
11230
12585
|
const batchSize = 5;
|
|
11231
12586
|
for (let i = 0; i < tests.length; i += batchSize) {
|
|
@@ -11241,7 +12596,25 @@ var init_FixFlow = __esm({
|
|
|
11241
12596
|
setStep("fixing");
|
|
11242
12597
|
onStartFix(prompt, tests);
|
|
11243
12598
|
} catch (err) {
|
|
11244
|
-
|
|
12599
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
12600
|
+
if (errorMessage.includes("network") || errorMessage.includes("fetch")) {
|
|
12601
|
+
setLoadError(`Network error: Unable to connect to the server.
|
|
12602
|
+
|
|
12603
|
+
Please check your internet connection and try again.
|
|
12604
|
+
|
|
12605
|
+
Error: ${errorMessage}`);
|
|
12606
|
+
} else if (errorMessage.includes("auth") || errorMessage.includes("unauthorized")) {
|
|
12607
|
+
setLoadError(`Authentication error: Please run 'supatest auth' to log in.
|
|
12608
|
+
|
|
12609
|
+
Error: ${errorMessage}`);
|
|
12610
|
+
} else {
|
|
12611
|
+
setLoadError(`Failed to load test details:
|
|
12612
|
+
|
|
12613
|
+
${errorMessage}
|
|
12614
|
+
|
|
12615
|
+
Press ESC to go back and try again.`);
|
|
12616
|
+
}
|
|
12617
|
+
setStep("error");
|
|
11245
12618
|
}
|
|
11246
12619
|
};
|
|
11247
12620
|
const handleRunCancel = () => {
|
|
@@ -11249,8 +12622,47 @@ var init_FixFlow = __esm({
|
|
|
11249
12622
|
};
|
|
11250
12623
|
const handleTestCancel = () => {
|
|
11251
12624
|
setSelectedRun(null);
|
|
12625
|
+
setAssignments([]);
|
|
11252
12626
|
setStep("select-run");
|
|
11253
12627
|
};
|
|
12628
|
+
const markAssignmentsComplete = async (fixSessionId) => {
|
|
12629
|
+
if (assignmentIds.size === 0) {
|
|
12630
|
+
return;
|
|
12631
|
+
}
|
|
12632
|
+
try {
|
|
12633
|
+
await Promise.all(
|
|
12634
|
+
Array.from(assignmentIds.values()).map(
|
|
12635
|
+
(assignmentId) => apiClient.completeAssignment({
|
|
12636
|
+
assignmentId,
|
|
12637
|
+
status: "completed",
|
|
12638
|
+
fixSessionId
|
|
12639
|
+
})
|
|
12640
|
+
)
|
|
12641
|
+
);
|
|
12642
|
+
} catch (err) {
|
|
12643
|
+
console.error("Failed to mark assignments as complete:", err);
|
|
12644
|
+
}
|
|
12645
|
+
};
|
|
12646
|
+
const releaseAssignments = async () => {
|
|
12647
|
+
if (assignmentIds.size === 0) {
|
|
12648
|
+
return;
|
|
12649
|
+
}
|
|
12650
|
+
try {
|
|
12651
|
+
await Promise.all(
|
|
12652
|
+
Array.from(assignmentIds.values()).map(
|
|
12653
|
+
(assignmentId) => apiClient.releaseAssignment(assignmentId)
|
|
12654
|
+
)
|
|
12655
|
+
);
|
|
12656
|
+
setAssignmentIds(/* @__PURE__ */ new Map());
|
|
12657
|
+
} catch (err) {
|
|
12658
|
+
console.error("Failed to release assignments:", err);
|
|
12659
|
+
}
|
|
12660
|
+
};
|
|
12661
|
+
useEffect8(() => {
|
|
12662
|
+
if (step === "complete") {
|
|
12663
|
+
markAssignmentsComplete();
|
|
12664
|
+
}
|
|
12665
|
+
}, [step]);
|
|
11254
12666
|
switch (step) {
|
|
11255
12667
|
case "select-run":
|
|
11256
12668
|
return /* @__PURE__ */ React22.createElement(
|
|
@@ -11270,20 +12682,25 @@ var init_FixFlow = __esm({
|
|
|
11270
12682
|
TestSelector,
|
|
11271
12683
|
{
|
|
11272
12684
|
apiClient,
|
|
12685
|
+
assignments,
|
|
11273
12686
|
onCancel: handleTestCancel,
|
|
11274
12687
|
onSelect: handleTestsSelect,
|
|
11275
12688
|
run: selectedRun
|
|
11276
12689
|
}
|
|
11277
12690
|
);
|
|
12691
|
+
case "claiming-tests":
|
|
12692
|
+
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Claiming Tests..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Assigning ", selectedTests.length, " test", selectedTests.length !== 1 ? "s" : "", " to you...")));
|
|
11278
12693
|
case "loading-details":
|
|
11279
12694
|
if (loadError) {
|
|
11280
12695
|
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "red" }, "Error Loading Test Details"), /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, loadError), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
|
|
11281
12696
|
}
|
|
11282
12697
|
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Loading Test Details..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Fetching error messages, stack traces, and execution steps...")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: "yellow" }, loadingProgress.current, " / ", loadingProgress.total, " tests loaded")));
|
|
12698
|
+
case "error":
|
|
12699
|
+
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "red" }, "Error"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, loadError)), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React22.createElement(Text17, { bold: true }, "ESC"), " to go back")));
|
|
11283
12700
|
case "fixing":
|
|
11284
12701
|
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "cyan" }, "Fixing ", selectedTests.length, " Test", selectedTests.length !== 1 ? "s" : "", "..."), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, selectedTests.map((test, index) => /* @__PURE__ */ React22.createElement(Box19, { key: test.id }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, index + 1, ". ", test.file.split("/").pop(), ":", test.location?.line || "", " - ", test.title)))), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "The agent will now analyze and fix each test...")));
|
|
11285
12702
|
case "complete":
|
|
11286
|
-
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "
|
|
12703
|
+
return /* @__PURE__ */ React22.createElement(Box19, { borderColor: "green", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React22.createElement(Text17, { bold: true, color: "green" }, "Tests Marked as Complete"), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "\u2713 ", selectedTests.length, " test", selectedTests.length !== 1 ? "s have" : " has", " been marked as fixed")), /* @__PURE__ */ React22.createElement(Box19, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(Text17, { color: theme.text.dim }, "Press any key to continue...")));
|
|
11287
12704
|
default:
|
|
11288
12705
|
return null;
|
|
11289
12706
|
}
|
|
@@ -11291,91 +12708,144 @@ var init_FixFlow = __esm({
|
|
|
11291
12708
|
}
|
|
11292
12709
|
});
|
|
11293
12710
|
|
|
11294
|
-
// src/ui/utils/file-
|
|
11295
|
-
import fs4 from "fs";
|
|
12711
|
+
// src/ui/utils/file-search.ts
|
|
11296
12712
|
import path4 from "path";
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
const
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
if (
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
const relativePath = path4.relative(rootDir, fullPath);
|
|
11313
|
-
if (entry.isDirectory()) {
|
|
11314
|
-
scan(fullPath, depth + 1);
|
|
11315
|
-
} else {
|
|
11316
|
-
files.push(relativePath);
|
|
11317
|
-
}
|
|
11318
|
-
}
|
|
11319
|
-
} catch (error) {
|
|
12713
|
+
import { glob } from "glob";
|
|
12714
|
+
function fuzzyMatch(text, query2) {
|
|
12715
|
+
const textLower = text.toLowerCase();
|
|
12716
|
+
const queryLower = query2.toLowerCase();
|
|
12717
|
+
if (!queryLower) return 1;
|
|
12718
|
+
let queryIdx = 0;
|
|
12719
|
+
let score = 0;
|
|
12720
|
+
let consecutiveMatches = 0;
|
|
12721
|
+
for (let i = 0; i < textLower.length && queryIdx < queryLower.length; i++) {
|
|
12722
|
+
if (textLower[i] === queryLower[queryIdx]) {
|
|
12723
|
+
queryIdx++;
|
|
12724
|
+
score += 1 + consecutiveMatches * 0.5;
|
|
12725
|
+
consecutiveMatches++;
|
|
12726
|
+
} else {
|
|
12727
|
+
consecutiveMatches = 0;
|
|
11320
12728
|
}
|
|
11321
12729
|
}
|
|
11322
|
-
|
|
11323
|
-
|
|
12730
|
+
if (queryIdx < queryLower.length) {
|
|
12731
|
+
return 0;
|
|
12732
|
+
}
|
|
12733
|
+
const segments = textLower.split(path4.sep);
|
|
12734
|
+
for (const segment of segments) {
|
|
12735
|
+
if (segment.startsWith(queryLower[0])) {
|
|
12736
|
+
score += 0.5;
|
|
12737
|
+
}
|
|
12738
|
+
}
|
|
12739
|
+
if (textLower === queryLower) {
|
|
12740
|
+
score += 2;
|
|
12741
|
+
}
|
|
12742
|
+
return score;
|
|
12743
|
+
}
|
|
12744
|
+
async function searchFiles(query2, options = {}) {
|
|
12745
|
+
const { signal, maxResults = 20, maxDepth = 5, cwd = process.cwd() } = options;
|
|
12746
|
+
const pattern = "**/*";
|
|
12747
|
+
const files = await glob(pattern, {
|
|
12748
|
+
cwd,
|
|
12749
|
+
dot: false,
|
|
12750
|
+
// Don't include dotfiles by default
|
|
12751
|
+
signal,
|
|
12752
|
+
nodir: true,
|
|
12753
|
+
maxDepth,
|
|
12754
|
+
absolute: false
|
|
12755
|
+
});
|
|
12756
|
+
const safeFiles = files || [];
|
|
12757
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
12758
|
+
if (!query2) {
|
|
12759
|
+
return safeFiles.slice(0, maxResults);
|
|
12760
|
+
}
|
|
12761
|
+
const results = [];
|
|
12762
|
+
for (const file of safeFiles) {
|
|
12763
|
+
const score = fuzzyMatch(file, query2);
|
|
12764
|
+
if (score > 0) {
|
|
12765
|
+
results.push({ file, score });
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12768
|
+
results.sort((a, b) => b.score - a.score);
|
|
12769
|
+
return results.slice(0, maxResults).map((r) => r.file);
|
|
11324
12770
|
}
|
|
11325
|
-
var
|
|
11326
|
-
|
|
11327
|
-
"src/ui/utils/file-completion.ts"() {
|
|
12771
|
+
var init_file_search = __esm({
|
|
12772
|
+
"src/ui/utils/file-search.ts"() {
|
|
11328
12773
|
"use strict";
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
12774
|
+
}
|
|
12775
|
+
});
|
|
12776
|
+
|
|
12777
|
+
// src/ui/hooks/useFileCompletion.ts
|
|
12778
|
+
import { useEffect as useEffect9, useRef as useRef6, useState as useState9 } from "react";
|
|
12779
|
+
function useFileCompletion(enabled, query2, cwd, maxResults = 20) {
|
|
12780
|
+
const [suggestions, setSuggestions] = useState9([]);
|
|
12781
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
12782
|
+
const abortControllerRef = useRef6(null);
|
|
12783
|
+
const debounceTimerRef = useRef6(null);
|
|
12784
|
+
const loadingTimerRef = useRef6(null);
|
|
12785
|
+
useEffect9(() => {
|
|
12786
|
+
if (abortControllerRef.current) {
|
|
12787
|
+
abortControllerRef.current.abort();
|
|
12788
|
+
}
|
|
12789
|
+
if (debounceTimerRef.current) {
|
|
12790
|
+
clearTimeout(debounceTimerRef.current);
|
|
12791
|
+
}
|
|
12792
|
+
if (loadingTimerRef.current) {
|
|
12793
|
+
clearTimeout(loadingTimerRef.current);
|
|
12794
|
+
}
|
|
12795
|
+
if (!enabled || !query2 || query2.length === 0) {
|
|
12796
|
+
setSuggestions([]);
|
|
12797
|
+
setIsLoading(false);
|
|
12798
|
+
return;
|
|
12799
|
+
}
|
|
12800
|
+
loadingTimerRef.current = setTimeout(() => {
|
|
12801
|
+
setIsLoading(true);
|
|
12802
|
+
}, 200);
|
|
12803
|
+
debounceTimerRef.current = setTimeout(async () => {
|
|
12804
|
+
const controller = new AbortController();
|
|
12805
|
+
abortControllerRef.current = controller;
|
|
12806
|
+
try {
|
|
12807
|
+
const results = await searchFiles(query2, {
|
|
12808
|
+
signal: controller.signal,
|
|
12809
|
+
maxResults,
|
|
12810
|
+
cwd
|
|
12811
|
+
});
|
|
12812
|
+
if (!controller.signal.aborted) {
|
|
12813
|
+
setSuggestions(Array.isArray(results) ? results : []);
|
|
12814
|
+
}
|
|
12815
|
+
} catch (error) {
|
|
12816
|
+
if (error instanceof Error && error.name !== "AbortError") {
|
|
12817
|
+
console.error("File search failed:", error);
|
|
12818
|
+
}
|
|
12819
|
+
setSuggestions([]);
|
|
12820
|
+
} finally {
|
|
12821
|
+
if (loadingTimerRef.current) {
|
|
12822
|
+
clearTimeout(loadingTimerRef.current);
|
|
12823
|
+
}
|
|
12824
|
+
setIsLoading(false);
|
|
12825
|
+
}
|
|
12826
|
+
}, 150);
|
|
12827
|
+
return () => {
|
|
12828
|
+
abortControllerRef.current?.abort();
|
|
12829
|
+
if (debounceTimerRef.current) {
|
|
12830
|
+
clearTimeout(debounceTimerRef.current);
|
|
12831
|
+
}
|
|
12832
|
+
if (loadingTimerRef.current) {
|
|
12833
|
+
clearTimeout(loadingTimerRef.current);
|
|
12834
|
+
}
|
|
12835
|
+
};
|
|
12836
|
+
}, [enabled, query2, cwd, maxResults]);
|
|
12837
|
+
return { suggestions, isLoading };
|
|
12838
|
+
}
|
|
12839
|
+
var init_useFileCompletion = __esm({
|
|
12840
|
+
"src/ui/hooks/useFileCompletion.ts"() {
|
|
12841
|
+
"use strict";
|
|
12842
|
+
init_file_search();
|
|
11373
12843
|
}
|
|
11374
12844
|
});
|
|
11375
12845
|
|
|
11376
12846
|
// src/ui/components/ModelSelector.tsx
|
|
11377
12847
|
import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
|
|
11378
|
-
import React23, { useState as
|
|
12848
|
+
import React23, { useState as useState10 } from "react";
|
|
11379
12849
|
function getAvailableModels(isClaudeMax) {
|
|
11380
12850
|
if (isClaudeMax) {
|
|
11381
12851
|
return AVAILABLE_MODELS.map((m) => ({
|
|
@@ -11410,7 +12880,7 @@ var init_ModelSelector = __esm({
|
|
|
11410
12880
|
}) => {
|
|
11411
12881
|
const models = getAvailableModels(isClaudeMax);
|
|
11412
12882
|
const currentIndex = models.findIndex((m) => m.id === currentModel);
|
|
11413
|
-
const [selectedIndex, setSelectedIndex] =
|
|
12883
|
+
const [selectedIndex, setSelectedIndex] = useState10(currentIndex >= 0 ? currentIndex : 0);
|
|
11414
12884
|
useInput5((input, key) => {
|
|
11415
12885
|
if (key.upArrow) {
|
|
11416
12886
|
setSelectedIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
|
|
@@ -11452,7 +12922,7 @@ var init_ModelSelector = __esm({
|
|
|
11452
12922
|
import path5 from "path";
|
|
11453
12923
|
import chalk4 from "chalk";
|
|
11454
12924
|
import { Box as Box21, Text as Text19 } from "ink";
|
|
11455
|
-
import React24, { forwardRef, memo as memo3, useEffect as
|
|
12925
|
+
import React24, { forwardRef, memo as memo3, useEffect as useEffect10, useImperativeHandle, useState as useState11 } from "react";
|
|
11456
12926
|
var InputPromptInner, InputPrompt;
|
|
11457
12927
|
var init_InputPrompt = __esm({
|
|
11458
12928
|
"src/ui/components/InputPrompt.tsx"() {
|
|
@@ -11460,13 +12930,13 @@ var init_InputPrompt = __esm({
|
|
|
11460
12930
|
init_shared_es();
|
|
11461
12931
|
init_command_discovery();
|
|
11462
12932
|
init_SessionContext();
|
|
12933
|
+
init_useFileCompletion();
|
|
11463
12934
|
init_useKeypress();
|
|
11464
|
-
init_file_completion();
|
|
11465
12935
|
init_theme();
|
|
11466
12936
|
init_ModelSelector();
|
|
11467
12937
|
InputPromptInner = forwardRef(({
|
|
11468
12938
|
onSubmit,
|
|
11469
|
-
placeholder = "Enter your task (
|
|
12939
|
+
placeholder = "Enter your task... (Ctrl+W=del word, Alt+\u2190/\u2192=word nav, Ctrl+U/K=clear)",
|
|
11470
12940
|
disabled = false,
|
|
11471
12941
|
onHelpToggle,
|
|
11472
12942
|
cwd,
|
|
@@ -11477,13 +12947,38 @@ var init_InputPrompt = __esm({
|
|
|
11477
12947
|
}, ref) => {
|
|
11478
12948
|
const { agentMode, selectedModel, setSelectedModel, isAgentRunning } = useSession();
|
|
11479
12949
|
const { usageStats } = useUsageStats();
|
|
11480
|
-
const [value, setValue] =
|
|
11481
|
-
const [
|
|
11482
|
-
const
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
12950
|
+
const [value, setValue] = useState11("");
|
|
12951
|
+
const [cursorPos, setCursorPos] = useState11([0, 0]);
|
|
12952
|
+
const getCursorOffset = () => {
|
|
12953
|
+
const lines2 = value.split("\n");
|
|
12954
|
+
let offset = 0;
|
|
12955
|
+
for (let i = 0; i < cursorPos[0]; i++) {
|
|
12956
|
+
offset += lines2[i]?.length ?? 0;
|
|
12957
|
+
offset += 1;
|
|
12958
|
+
}
|
|
12959
|
+
offset += cursorPos[1];
|
|
12960
|
+
return offset;
|
|
12961
|
+
};
|
|
12962
|
+
const offsetToCursorPos = (offset, text) => {
|
|
12963
|
+
const lines2 = text.split("\n");
|
|
12964
|
+
let currentOffset = 0;
|
|
12965
|
+
for (let i = 0; i < lines2.length; i++) {
|
|
12966
|
+
const lineLength = lines2[i]?.length ?? 0;
|
|
12967
|
+
if (currentOffset + lineLength >= offset) {
|
|
12968
|
+
return [i, offset - currentOffset];
|
|
12969
|
+
}
|
|
12970
|
+
currentOffset += lineLength + 1;
|
|
12971
|
+
}
|
|
12972
|
+
return [lines2.length - 1, lines2[lines2.length - 1]?.length ?? 0];
|
|
12973
|
+
};
|
|
12974
|
+
const setCursorFromOffset = (offset, text) => {
|
|
12975
|
+
setCursorPos(offsetToCursorPos(offset, text ?? value));
|
|
12976
|
+
};
|
|
12977
|
+
const [suggestions, setSuggestions] = useState11([]);
|
|
12978
|
+
const [activeSuggestion, setActiveSuggestion] = useState11(0);
|
|
12979
|
+
const [showSuggestions, setShowSuggestions] = useState11(false);
|
|
12980
|
+
const [mentionStartIndex, setMentionStartIndex] = useState11(null);
|
|
12981
|
+
const [mentionQuery, setMentionQuery] = useState11("");
|
|
11487
12982
|
const BUILTIN_SLASH_COMMANDS = [
|
|
11488
12983
|
{ name: "/help", desc: "Show help" },
|
|
11489
12984
|
{ name: "/resume", desc: "Resume session" },
|
|
@@ -11499,9 +12994,9 @@ var init_InputPrompt = __esm({
|
|
|
11499
12994
|
{ name: "/logout", desc: "Log out" },
|
|
11500
12995
|
{ name: "/exit", desc: "Exit CLI" }
|
|
11501
12996
|
];
|
|
11502
|
-
const [customCommands, setCustomCommands] =
|
|
11503
|
-
const [isSlashCommand, setIsSlashCommand] =
|
|
11504
|
-
|
|
12997
|
+
const [customCommands, setCustomCommands] = useState11([]);
|
|
12998
|
+
const [isSlashCommand, setIsSlashCommand] = useState11(false);
|
|
12999
|
+
useEffect10(() => {
|
|
11505
13000
|
try {
|
|
11506
13001
|
const projectDir = cwd || process.cwd();
|
|
11507
13002
|
const discovered = discoverCommands(projectDir);
|
|
@@ -11517,29 +13012,55 @@ var init_InputPrompt = __esm({
|
|
|
11517
13012
|
useImperativeHandle(ref, () => ({
|
|
11518
13013
|
clear: () => {
|
|
11519
13014
|
setValue("");
|
|
11520
|
-
|
|
13015
|
+
setCursorPos([0, 0]);
|
|
11521
13016
|
setShowSuggestions(false);
|
|
11522
13017
|
onInputChange?.("");
|
|
11523
13018
|
}
|
|
11524
13019
|
}));
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
13020
|
+
const { suggestions: fileSuggestions, isLoading: isLoadingFiles } = useFileCompletion(
|
|
13021
|
+
showSuggestions && !isSlashCommand && mentionQuery.length > 0,
|
|
13022
|
+
mentionQuery,
|
|
13023
|
+
cwd || process.cwd(),
|
|
13024
|
+
20
|
|
13025
|
+
// maxResults
|
|
13026
|
+
);
|
|
13027
|
+
useEffect10(() => {
|
|
13028
|
+
if (!isSlashCommand && showSuggestions) {
|
|
13029
|
+
setSuggestions(fileSuggestions);
|
|
13030
|
+
}
|
|
13031
|
+
}, [fileSuggestions, isSlashCommand, showSuggestions]);
|
|
11534
13032
|
const updateValue = (newValue, newCursor) => {
|
|
11535
13033
|
setValue(newValue);
|
|
11536
|
-
|
|
13034
|
+
if (typeof newCursor === "number") {
|
|
13035
|
+
setCursorFromOffset(newCursor, newValue);
|
|
13036
|
+
} else {
|
|
13037
|
+
setCursorPos(newCursor);
|
|
13038
|
+
}
|
|
13039
|
+
const newOffset = typeof newCursor === "number" ? newCursor : (() => {
|
|
13040
|
+
const lines2 = newValue.split("\n");
|
|
13041
|
+
let offset = 0;
|
|
13042
|
+
for (let i = 0; i < newCursor[0]; i++) {
|
|
13043
|
+
offset += lines2[i]?.length ?? 0;
|
|
13044
|
+
offset += 1;
|
|
13045
|
+
}
|
|
13046
|
+
offset += newCursor[1];
|
|
13047
|
+
return offset;
|
|
13048
|
+
})();
|
|
11537
13049
|
checkSuggestions(newValue, newCursor);
|
|
11538
13050
|
onInputChange?.(newValue);
|
|
11539
13051
|
};
|
|
11540
13052
|
const checkSuggestions = (text, cursor) => {
|
|
11541
|
-
|
|
11542
|
-
|
|
13053
|
+
let cursorRow;
|
|
13054
|
+
let cursorCol2;
|
|
13055
|
+
if (typeof cursor === "number") {
|
|
13056
|
+
[cursorRow, cursorCol2] = offsetToCursorPos(cursor, text);
|
|
13057
|
+
} else {
|
|
13058
|
+
[cursorRow, cursorCol2] = cursor;
|
|
13059
|
+
}
|
|
13060
|
+
const lines2 = text.split("\n");
|
|
13061
|
+
const currentLine = lines2[cursorRow] || "";
|
|
13062
|
+
if (cursorRow === 0 && text.startsWith("/") && cursorCol2 <= currentLine.length && !currentLine.includes(" ", 1)) {
|
|
13063
|
+
const query2 = currentLine.slice(1);
|
|
11543
13064
|
const builtinMatches = BUILTIN_SLASH_COMMANDS.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
|
|
11544
13065
|
const customMatches = customCommands.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
|
|
11545
13066
|
const matches = [];
|
|
@@ -11561,25 +13082,26 @@ var init_InputPrompt = __esm({
|
|
|
11561
13082
|
}
|
|
11562
13083
|
}
|
|
11563
13084
|
setIsSlashCommand(false);
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
}
|
|
11579
|
-
}
|
|
13085
|
+
let foundMention = false;
|
|
13086
|
+
for (let i = cursorCol2 - 1; i >= 0; i--) {
|
|
13087
|
+
const char = currentLine[i];
|
|
13088
|
+
if (char === " ") {
|
|
13089
|
+
break;
|
|
13090
|
+
}
|
|
13091
|
+
if (char === "@") {
|
|
13092
|
+
const query2 = currentLine.slice(i + 1, cursorCol2);
|
|
13093
|
+
setMentionQuery(query2);
|
|
13094
|
+
setShowSuggestions(true);
|
|
13095
|
+
setActiveSuggestion(0);
|
|
13096
|
+
setMentionStartIndex({ row: cursorRow, col: i });
|
|
13097
|
+
foundMention = true;
|
|
13098
|
+
break;
|
|
11580
13099
|
}
|
|
11581
13100
|
}
|
|
11582
|
-
|
|
13101
|
+
if (!foundMention) {
|
|
13102
|
+
setMentionQuery("");
|
|
13103
|
+
setShowSuggestions(false);
|
|
13104
|
+
}
|
|
11583
13105
|
};
|
|
11584
13106
|
const completeSuggestion = (submit = false) => {
|
|
11585
13107
|
if (!showSuggestions || suggestions.length === 0) return;
|
|
@@ -11588,27 +13110,35 @@ var init_InputPrompt = __esm({
|
|
|
11588
13110
|
if (submit) {
|
|
11589
13111
|
onSubmit(selectedCmd);
|
|
11590
13112
|
setValue("");
|
|
11591
|
-
|
|
13113
|
+
setCursorPos([0, 0]);
|
|
11592
13114
|
setShowSuggestions(false);
|
|
11593
13115
|
onInputChange?.("");
|
|
11594
13116
|
return;
|
|
11595
13117
|
}
|
|
11596
|
-
updateValue(selectedCmd, selectedCmd.length);
|
|
13118
|
+
updateValue(selectedCmd, [0, selectedCmd.length]);
|
|
11597
13119
|
setShowSuggestions(false);
|
|
11598
13120
|
return;
|
|
11599
13121
|
}
|
|
11600
13122
|
const selectedFile = suggestions[activeSuggestion];
|
|
11601
|
-
const
|
|
11602
|
-
const
|
|
11603
|
-
|
|
11604
|
-
const
|
|
11605
|
-
|
|
13123
|
+
const mentionStart = mentionStartIndex;
|
|
13124
|
+
const [currentRow, currentCol] = cursorPos;
|
|
13125
|
+
if (!mentionStart) return;
|
|
13126
|
+
const lines2 = value.split("\n");
|
|
13127
|
+
const beforeMention = lines2[mentionStart.row].slice(0, mentionStart.col);
|
|
13128
|
+
const afterCursor = lines2[mentionStart.row].slice(currentCol);
|
|
13129
|
+
const newLine = beforeMention + "@" + selectedFile + " " + afterCursor;
|
|
13130
|
+
const newLines = [...lines2];
|
|
13131
|
+
newLines[mentionStart.row] = newLine;
|
|
13132
|
+
const newCursorCol = mentionStart.col + 1 + selectedFile.length + 1;
|
|
13133
|
+
const newCursorPos = [mentionStart.row, newCursorCol];
|
|
13134
|
+
updateValue(newLines.join("\n"), newCursorPos);
|
|
11606
13135
|
setShowSuggestions(false);
|
|
11607
13136
|
};
|
|
11608
13137
|
useKeypress(
|
|
11609
13138
|
(key) => {
|
|
11610
13139
|
if (disabled) return;
|
|
11611
13140
|
const input = key.sequence;
|
|
13141
|
+
const cursorOffset = getCursorOffset();
|
|
11612
13142
|
if (input.length > 1 && (input.includes("/") || input.includes("\\"))) {
|
|
11613
13143
|
let cleanPath = input.trim();
|
|
11614
13144
|
if (cleanPath.startsWith('"') && cleanPath.endsWith('"') || cleanPath.startsWith("'") && cleanPath.endsWith("'")) {
|
|
@@ -11632,7 +13162,7 @@ var init_InputPrompt = __esm({
|
|
|
11632
13162
|
return;
|
|
11633
13163
|
}
|
|
11634
13164
|
}
|
|
11635
|
-
if (key.shift && key.name === "m" && !isAgentRunning) {
|
|
13165
|
+
if ((key.ctrl || key.meta) && key.shift && key.name === "m" && !isAgentRunning) {
|
|
11636
13166
|
setSelectedModel(getNextModel(selectedModel, isClaudeMax));
|
|
11637
13167
|
return;
|
|
11638
13168
|
}
|
|
@@ -11681,7 +13211,7 @@ var init_InputPrompt = __esm({
|
|
|
11681
13211
|
if (value.trim()) {
|
|
11682
13212
|
onSubmit(value.trim());
|
|
11683
13213
|
setValue("");
|
|
11684
|
-
|
|
13214
|
+
setCursorPos([0, 0]);
|
|
11685
13215
|
setShowSuggestions(false);
|
|
11686
13216
|
onInputChange?.("");
|
|
11687
13217
|
}
|
|
@@ -11692,11 +13222,55 @@ var init_InputPrompt = __esm({
|
|
|
11692
13222
|
updateValue(newValue, cursorOffset - 1);
|
|
11693
13223
|
}
|
|
11694
13224
|
} else if (key.name === "left") {
|
|
11695
|
-
|
|
13225
|
+
if (key.meta && !key.shift && !key.ctrl) {
|
|
13226
|
+
const textBefore = value.slice(0, cursorOffset);
|
|
13227
|
+
const match = textBefore.match(/\S+\s*$/);
|
|
13228
|
+
const wordStart = match ? textBefore.length - match[0].length : 0;
|
|
13229
|
+
setCursorFromOffset(wordStart);
|
|
13230
|
+
} else {
|
|
13231
|
+
setCursorFromOffset(Math.max(0, cursorOffset - 1));
|
|
13232
|
+
}
|
|
11696
13233
|
} else if (key.name === "right") {
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
13234
|
+
if (key.meta && !key.shift && !key.ctrl) {
|
|
13235
|
+
const textAfter = value.slice(cursorOffset);
|
|
13236
|
+
const match = textAfter.match(/^\s*\S+/);
|
|
13237
|
+
const wordEnd = match ? cursorOffset + match[0].length : value.length;
|
|
13238
|
+
setCursorFromOffset(wordEnd);
|
|
13239
|
+
} else {
|
|
13240
|
+
setCursorFromOffset(Math.min(value.length, cursorOffset + 1));
|
|
13241
|
+
}
|
|
13242
|
+
} else if (key.ctrl && key.name === "u") {
|
|
13243
|
+
const newValue = value.slice(cursorOffset);
|
|
13244
|
+
updateValue(newValue, 0);
|
|
13245
|
+
} else if (key.ctrl && key.name === "k") {
|
|
13246
|
+
const newValue = value.slice(0, cursorOffset);
|
|
13247
|
+
updateValue(newValue, cursorOffset);
|
|
13248
|
+
} else if (key.ctrl && key.name === "w") {
|
|
13249
|
+
const textBefore = value.slice(0, cursorOffset);
|
|
13250
|
+
const match = textBefore.match(/\S+\s*$/);
|
|
13251
|
+
const wordStart = match ? textBefore.length - match[0].length : 0;
|
|
13252
|
+
const newValue = value.slice(0, wordStart) + value.slice(cursorOffset);
|
|
13253
|
+
updateValue(newValue, wordStart);
|
|
13254
|
+
} else if (key.meta && key.name === "backspace") {
|
|
13255
|
+
const textBefore = value.slice(0, cursorOffset);
|
|
13256
|
+
const match = textBefore.match(/\S+\s*$/);
|
|
13257
|
+
const wordStart = match ? textBefore.length - match[0].length : 0;
|
|
13258
|
+
const newValue = value.slice(0, wordStart) + value.slice(cursorOffset);
|
|
13259
|
+
updateValue(newValue, wordStart);
|
|
13260
|
+
} else if (key.meta && key.name === "d") {
|
|
13261
|
+
const textAfter = value.slice(cursorOffset);
|
|
13262
|
+
const match = textAfter.match(/^\s*\S+/);
|
|
13263
|
+
const wordEnd = match ? cursorOffset + match[0].length : value.length;
|
|
13264
|
+
const newValue = value.slice(0, cursorOffset) + value.slice(wordEnd);
|
|
13265
|
+
updateValue(newValue, cursorOffset);
|
|
13266
|
+
} else if (key.ctrl && key.name === "a") {
|
|
13267
|
+
setCursorFromOffset(0);
|
|
13268
|
+
} else if (key.ctrl && key.name === "e") {
|
|
13269
|
+
setCursorFromOffset(value.length);
|
|
13270
|
+
} else if (key.name === "home") {
|
|
13271
|
+
setCursorFromOffset(0);
|
|
13272
|
+
} else if (key.name === "end") {
|
|
13273
|
+
setCursorFromOffset(value.length);
|
|
11700
13274
|
} else if (key.name === "tab" && !showSuggestions) {
|
|
11701
13275
|
} else if (key.paste) {
|
|
11702
13276
|
const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
|
|
@@ -11710,18 +13284,7 @@ var init_InputPrompt = __esm({
|
|
|
11710
13284
|
);
|
|
11711
13285
|
const lines = value ? value.split("\n") : [];
|
|
11712
13286
|
const hasContent = value.trim().length > 0;
|
|
11713
|
-
|
|
11714
|
-
let cursorCol = cursorOffset;
|
|
11715
|
-
let charCount = 0;
|
|
11716
|
-
for (let i = 0; i < lines.length; i++) {
|
|
11717
|
-
const lineLength = lines[i].length;
|
|
11718
|
-
if (charCount + lineLength >= cursorOffset) {
|
|
11719
|
-
cursorLine = i;
|
|
11720
|
-
cursorCol = cursorOffset - charCount;
|
|
11721
|
-
break;
|
|
11722
|
-
}
|
|
11723
|
-
charCount += lineLength + 1;
|
|
11724
|
-
}
|
|
13287
|
+
const [cursorLine, cursorCol] = cursorPos;
|
|
11725
13288
|
return /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React24.createElement(
|
|
11726
13289
|
Box21,
|
|
11727
13290
|
{
|
|
@@ -11731,13 +13294,13 @@ var init_InputPrompt = __esm({
|
|
|
11731
13294
|
marginBottom: 0,
|
|
11732
13295
|
paddingX: 1
|
|
11733
13296
|
},
|
|
11734
|
-
suggestions.map((item, idx) => {
|
|
13297
|
+
isLoadingFiles && !isSlashCommand ? /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "Searching files...") : suggestions.length > 0 ? suggestions.map((item, idx) => {
|
|
11735
13298
|
const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
|
|
11736
13299
|
if (isSeparator) {
|
|
11737
13300
|
return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, key: item }, " ", item);
|
|
11738
13301
|
}
|
|
11739
13302
|
return /* @__PURE__ */ React24.createElement(Text19, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
|
|
11740
|
-
})
|
|
13303
|
+
}) : /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "No matches")
|
|
11741
13304
|
), /* @__PURE__ */ React24.createElement(
|
|
11742
13305
|
Box21,
|
|
11743
13306
|
{
|
|
@@ -11758,7 +13321,7 @@ var init_InputPrompt = __esm({
|
|
|
11758
13321
|
}
|
|
11759
13322
|
return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.primary, key: idx }, line);
|
|
11760
13323
|
})), !hasContent && disabled && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
|
|
11761
|
-
), /* @__PURE__ */ React24.createElement(Box21, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box21, { gap: 2 }, /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, ") (shift+m)")))
|
|
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)")))));
|
|
11762
13325
|
});
|
|
11763
13326
|
InputPromptInner.displayName = "InputPromptInner";
|
|
11764
13327
|
InputPrompt = memo3(InputPromptInner);
|
|
@@ -11768,19 +13331,19 @@ var init_InputPrompt = __esm({
|
|
|
11768
13331
|
|
|
11769
13332
|
// src/ui/components/McpAddDialog.tsx
|
|
11770
13333
|
import { Box as Box22, Text as Text20, useInput as useInput6 } from "ink";
|
|
11771
|
-
import React25, { useState as
|
|
13334
|
+
import React25, { useState as useState12 } from "react";
|
|
11772
13335
|
var McpAddDialog;
|
|
11773
13336
|
var init_McpAddDialog = __esm({
|
|
11774
13337
|
"src/ui/components/McpAddDialog.tsx"() {
|
|
11775
13338
|
"use strict";
|
|
11776
13339
|
init_theme();
|
|
11777
13340
|
McpAddDialog = ({ onConfirm, onCancel }) => {
|
|
11778
|
-
const [mode, setMode] =
|
|
11779
|
-
const [serverName, setServerName] =
|
|
11780
|
-
const [command, setCommand] =
|
|
11781
|
-
const [args, setArgs] =
|
|
11782
|
-
const [description, setDescription] =
|
|
11783
|
-
const [scope, setScope] =
|
|
13341
|
+
const [mode, setMode] = useState12("name" /* Name */);
|
|
13342
|
+
const [serverName, setServerName] = useState12("");
|
|
13343
|
+
const [command, setCommand] = useState12("");
|
|
13344
|
+
const [args, setArgs] = useState12("");
|
|
13345
|
+
const [description, setDescription] = useState12("");
|
|
13346
|
+
const [scope, setScope] = useState12("project");
|
|
11784
13347
|
useInput6((input, key) => {
|
|
11785
13348
|
if (key.escape) {
|
|
11786
13349
|
onCancel();
|
|
@@ -11875,7 +13438,7 @@ var init_McpAddDialog = __esm({
|
|
|
11875
13438
|
|
|
11876
13439
|
// src/ui/components/McpServerSelector.tsx
|
|
11877
13440
|
import { Box as Box23, Text as Text21, useInput as useInput7 } from "ink";
|
|
11878
|
-
import React26, { useState as
|
|
13441
|
+
import React26, { useState as useState13 } from "react";
|
|
11879
13442
|
var McpServerSelector;
|
|
11880
13443
|
var init_McpServerSelector = __esm({
|
|
11881
13444
|
"src/ui/components/McpServerSelector.tsx"() {
|
|
@@ -11887,7 +13450,7 @@ var init_McpServerSelector = __esm({
|
|
|
11887
13450
|
onSelect,
|
|
11888
13451
|
onCancel
|
|
11889
13452
|
}) => {
|
|
11890
|
-
const [selectedIndex, setSelectedIndex] =
|
|
13453
|
+
const [selectedIndex, setSelectedIndex] = useState13(0);
|
|
11891
13454
|
useInput7((input, key) => {
|
|
11892
13455
|
if (key.escape) {
|
|
11893
13456
|
onCancel();
|
|
@@ -11946,7 +13509,7 @@ var init_McpServerSelector = __esm({
|
|
|
11946
13509
|
// src/ui/components/McpServersDisplay.tsx
|
|
11947
13510
|
import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
|
|
11948
13511
|
import Spinner3 from "ink-spinner";
|
|
11949
|
-
import React27, { useEffect as
|
|
13512
|
+
import React27, { useEffect as useEffect11, useState as useState14 } from "react";
|
|
11950
13513
|
var McpServersDisplay;
|
|
11951
13514
|
var init_McpServersDisplay = __esm({
|
|
11952
13515
|
"src/ui/components/McpServersDisplay.tsx"() {
|
|
@@ -11960,9 +13523,9 @@ var init_McpServersDisplay = __esm({
|
|
|
11960
13523
|
onTest,
|
|
11961
13524
|
cwd
|
|
11962
13525
|
}) => {
|
|
11963
|
-
const [servers, setServers] =
|
|
11964
|
-
const [isTestingAll, setIsTestingAll] =
|
|
11965
|
-
|
|
13526
|
+
const [servers, setServers] = useState14([]);
|
|
13527
|
+
const [isTestingAll, setIsTestingAll] = useState14(false);
|
|
13528
|
+
useEffect11(() => {
|
|
11966
13529
|
const projectDir = cwd || process.cwd();
|
|
11967
13530
|
const loadedServers = loadMcpConfig(projectDir);
|
|
11968
13531
|
const serversArray = Object.values(loadedServers).map((server) => ({
|
|
@@ -12032,7 +13595,7 @@ var init_McpServersDisplay = __esm({
|
|
|
12032
13595
|
|
|
12033
13596
|
// src/ui/components/ProviderSelector.tsx
|
|
12034
13597
|
import { Box as Box25, Text as Text23, useInput as useInput9 } from "ink";
|
|
12035
|
-
import React28, { useState as
|
|
13598
|
+
import React28, { useState as useState15 } from "react";
|
|
12036
13599
|
var PROVIDERS, ProviderSelector;
|
|
12037
13600
|
var init_ProviderSelector = __esm({
|
|
12038
13601
|
"src/ui/components/ProviderSelector.tsx"() {
|
|
@@ -12047,22 +13610,19 @@ var init_ProviderSelector = __esm({
|
|
|
12047
13610
|
{
|
|
12048
13611
|
id: "claude-max",
|
|
12049
13612
|
name: "Claude Max",
|
|
12050
|
-
description: "Direct Claude Max subscription (requires
|
|
13613
|
+
description: "Direct Claude Max subscription (requires OAuth login)"
|
|
12051
13614
|
}
|
|
12052
13615
|
];
|
|
12053
13616
|
ProviderSelector = ({
|
|
12054
13617
|
currentProvider,
|
|
12055
13618
|
onSelect,
|
|
12056
|
-
onCancel
|
|
12057
|
-
claudeMaxAvailable
|
|
13619
|
+
onCancel
|
|
12058
13620
|
}) => {
|
|
12059
|
-
const availableProviders = PROVIDERS
|
|
12060
|
-
(p) => p.id !== "claude-max" || claudeMaxAvailable
|
|
12061
|
-
);
|
|
13621
|
+
const availableProviders = PROVIDERS;
|
|
12062
13622
|
const currentIndex = availableProviders.findIndex(
|
|
12063
13623
|
(p) => p.id === currentProvider
|
|
12064
13624
|
);
|
|
12065
|
-
const [selectedIndex, setSelectedIndex] =
|
|
13625
|
+
const [selectedIndex, setSelectedIndex] = useState15(
|
|
12066
13626
|
currentIndex >= 0 ? currentIndex : 0
|
|
12067
13627
|
);
|
|
12068
13628
|
useInput9((input, key) => {
|
|
@@ -12102,9 +13662,125 @@ var init_ProviderSelector = __esm({
|
|
|
12102
13662
|
}
|
|
12103
13663
|
});
|
|
12104
13664
|
|
|
12105
|
-
// src/ui/components/
|
|
13665
|
+
// src/ui/components/QuestionSelector.tsx
|
|
12106
13666
|
import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
|
|
12107
|
-
import React29, {
|
|
13667
|
+
import React29, { useState as useState16 } from "react";
|
|
13668
|
+
var QuestionSelector;
|
|
13669
|
+
var init_QuestionSelector = __esm({
|
|
13670
|
+
"src/ui/components/QuestionSelector.tsx"() {
|
|
13671
|
+
"use strict";
|
|
13672
|
+
init_theme();
|
|
13673
|
+
QuestionSelector = ({
|
|
13674
|
+
question,
|
|
13675
|
+
options,
|
|
13676
|
+
multiSelect = false,
|
|
13677
|
+
allowCustomAnswer = true,
|
|
13678
|
+
// Default to true - always show "Other" option
|
|
13679
|
+
onSubmit,
|
|
13680
|
+
onCancel
|
|
13681
|
+
}) => {
|
|
13682
|
+
const hasOptions = options && options.length > 0;
|
|
13683
|
+
const allOptions = hasOptions && allowCustomAnswer ? [...options, { label: "Other", description: "Provide a custom response" }] : options;
|
|
13684
|
+
const [selectedIndex, setSelectedIndex] = useState16(0);
|
|
13685
|
+
const [selectedItems, setSelectedItems] = useState16(/* @__PURE__ */ new Set());
|
|
13686
|
+
const [customInputMode, setCustomInputMode] = useState16(!hasOptions);
|
|
13687
|
+
const [customAnswer, setCustomAnswer] = useState16("");
|
|
13688
|
+
useInput10((input, key) => {
|
|
13689
|
+
if (customInputMode) {
|
|
13690
|
+
if (key.return) {
|
|
13691
|
+
if (customAnswer.trim()) {
|
|
13692
|
+
onSubmit([customAnswer.trim()]);
|
|
13693
|
+
}
|
|
13694
|
+
} else if (key.escape) {
|
|
13695
|
+
if (!hasOptions) {
|
|
13696
|
+
onCancel();
|
|
13697
|
+
} else {
|
|
13698
|
+
setCustomInputMode(false);
|
|
13699
|
+
setCustomAnswer("");
|
|
13700
|
+
}
|
|
13701
|
+
} else if (key.backspace || key.delete) {
|
|
13702
|
+
setCustomAnswer((prev) => prev.slice(0, -1));
|
|
13703
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
13704
|
+
setCustomAnswer((prev) => prev + input);
|
|
13705
|
+
}
|
|
13706
|
+
return;
|
|
13707
|
+
}
|
|
13708
|
+
if (key.upArrow) {
|
|
13709
|
+
setSelectedIndex((prev) => prev > 0 ? prev - 1 : allOptions.length - 1);
|
|
13710
|
+
} else if (key.downArrow) {
|
|
13711
|
+
setSelectedIndex((prev) => prev < allOptions.length - 1 ? prev + 1 : 0);
|
|
13712
|
+
} else if (key.return) {
|
|
13713
|
+
const selectedOption = allOptions[selectedIndex];
|
|
13714
|
+
if (selectedOption.label === "Other" && allowCustomAnswer) {
|
|
13715
|
+
setCustomInputMode(true);
|
|
13716
|
+
return;
|
|
13717
|
+
}
|
|
13718
|
+
if (multiSelect) {
|
|
13719
|
+
setSelectedItems((prev) => {
|
|
13720
|
+
const newSet = new Set(prev);
|
|
13721
|
+
if (newSet.has(selectedIndex)) {
|
|
13722
|
+
newSet.delete(selectedIndex);
|
|
13723
|
+
} else {
|
|
13724
|
+
newSet.add(selectedIndex);
|
|
13725
|
+
}
|
|
13726
|
+
return newSet;
|
|
13727
|
+
});
|
|
13728
|
+
} else {
|
|
13729
|
+
onSubmit([selectedOption.label]);
|
|
13730
|
+
}
|
|
13731
|
+
} else if (input === " " && multiSelect) {
|
|
13732
|
+
setSelectedItems((prev) => {
|
|
13733
|
+
const newSet = new Set(prev);
|
|
13734
|
+
if (newSet.has(selectedIndex)) {
|
|
13735
|
+
newSet.delete(selectedIndex);
|
|
13736
|
+
} else {
|
|
13737
|
+
newSet.add(selectedIndex);
|
|
13738
|
+
}
|
|
13739
|
+
return newSet;
|
|
13740
|
+
});
|
|
13741
|
+
} else if (key.escape || input === "q") {
|
|
13742
|
+
onCancel();
|
|
13743
|
+
} else if (input === "s" && multiSelect && selectedItems.size > 0) {
|
|
13744
|
+
const answers = Array.from(selectedItems).sort((a, b) => a - b).map((idx) => allOptions[idx].label);
|
|
13745
|
+
onSubmit(answers);
|
|
13746
|
+
} else if ((input === "o" || input === "O") && allowCustomAnswer) {
|
|
13747
|
+
setCustomInputMode(true);
|
|
13748
|
+
}
|
|
13749
|
+
});
|
|
13750
|
+
if (customInputMode) {
|
|
13751
|
+
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")));
|
|
13752
|
+
}
|
|
13753
|
+
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) => {
|
|
13754
|
+
const isSelected = index === selectedIndex;
|
|
13755
|
+
const isChosen = selectedItems.has(index);
|
|
13756
|
+
const indicator = isSelected ? "\u25B6 " : " ";
|
|
13757
|
+
const checkbox = multiSelect ? isChosen ? "[\u2713] " : "[ ] " : "";
|
|
13758
|
+
return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", gap: 0, key: `${option.label}-${index}` }, /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(
|
|
13759
|
+
Text24,
|
|
13760
|
+
{
|
|
13761
|
+
backgroundColor: isSelected ? theme.text.accent : void 0,
|
|
13762
|
+
bold: isSelected,
|
|
13763
|
+
color: isSelected ? "black" : theme.text.primary
|
|
13764
|
+
},
|
|
13765
|
+
indicator,
|
|
13766
|
+
checkbox,
|
|
13767
|
+
option.label
|
|
13768
|
+
)), /* @__PURE__ */ React29.createElement(Box26, { marginLeft: multiSelect ? 6 : 4 }, /* @__PURE__ */ React29.createElement(
|
|
13769
|
+
Text24,
|
|
13770
|
+
{
|
|
13771
|
+
color: isSelected ? theme.text.primary : theme.text.dim,
|
|
13772
|
+
dimColor: !isSelected
|
|
13773
|
+
},
|
|
13774
|
+
option.description
|
|
13775
|
+
)));
|
|
13776
|
+
})), /* @__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")));
|
|
13777
|
+
};
|
|
13778
|
+
}
|
|
13779
|
+
});
|
|
13780
|
+
|
|
13781
|
+
// src/ui/components/SessionSelector.tsx
|
|
13782
|
+
import { Box as Box27, Text as Text25, useInput as useInput11 } from "ink";
|
|
13783
|
+
import React30, { useEffect as useEffect12, useState as useState17 } from "react";
|
|
12108
13784
|
function getSessionPrefix(authMethod) {
|
|
12109
13785
|
return authMethod === "api-key" ? "[Team]" : "[Me]";
|
|
12110
13786
|
}
|
|
@@ -12119,13 +13795,13 @@ var init_SessionSelector = __esm({
|
|
|
12119
13795
|
onSelect,
|
|
12120
13796
|
onCancel
|
|
12121
13797
|
}) => {
|
|
12122
|
-
const [allSessions, setAllSessions] =
|
|
12123
|
-
const [selectedIndex, setSelectedIndex] =
|
|
12124
|
-
const [isLoading, setIsLoading] =
|
|
12125
|
-
const [hasMore, setHasMore] =
|
|
12126
|
-
const [totalSessions, setTotalSessions] =
|
|
12127
|
-
const [error, setError] =
|
|
12128
|
-
|
|
13798
|
+
const [allSessions, setAllSessions] = useState17([]);
|
|
13799
|
+
const [selectedIndex, setSelectedIndex] = useState17(0);
|
|
13800
|
+
const [isLoading, setIsLoading] = useState17(false);
|
|
13801
|
+
const [hasMore, setHasMore] = useState17(true);
|
|
13802
|
+
const [totalSessions, setTotalSessions] = useState17(0);
|
|
13803
|
+
const [error, setError] = useState17(null);
|
|
13804
|
+
useEffect12(() => {
|
|
12129
13805
|
loadMoreSessions();
|
|
12130
13806
|
}, []);
|
|
12131
13807
|
const loadMoreSessions = async () => {
|
|
@@ -12151,7 +13827,7 @@ var init_SessionSelector = __esm({
|
|
|
12151
13827
|
setIsLoading(false);
|
|
12152
13828
|
}
|
|
12153
13829
|
};
|
|
12154
|
-
|
|
13830
|
+
useInput11((input, key) => {
|
|
12155
13831
|
if (allSessions.length === 0) {
|
|
12156
13832
|
if (key.escape || input === "q") {
|
|
12157
13833
|
onCancel();
|
|
@@ -12175,13 +13851,13 @@ var init_SessionSelector = __esm({
|
|
|
12175
13851
|
}
|
|
12176
13852
|
});
|
|
12177
13853
|
if (error) {
|
|
12178
|
-
return /* @__PURE__ */
|
|
13854
|
+
return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, error), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
|
|
12179
13855
|
}
|
|
12180
13856
|
if (allSessions.length === 0 && isLoading) {
|
|
12181
|
-
return /* @__PURE__ */
|
|
13857
|
+
return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Fetching your sessions from the server"));
|
|
12182
13858
|
}
|
|
12183
13859
|
if (allSessions.length === 0 && !isLoading) {
|
|
12184
|
-
return /* @__PURE__ */
|
|
13860
|
+
return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "No previous sessions available. Start a new session!"), /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")));
|
|
12185
13861
|
}
|
|
12186
13862
|
const VISIBLE_ITEMS3 = 10;
|
|
12187
13863
|
let startIndex;
|
|
@@ -12201,7 +13877,7 @@ var init_SessionSelector = __esm({
|
|
|
12201
13877
|
const visibleSessions = allSessions.slice(startIndex, endIndex);
|
|
12202
13878
|
const MAX_TITLE_WIDTH = 50;
|
|
12203
13879
|
const PREFIX_WIDTH = 6;
|
|
12204
|
-
return /* @__PURE__ */
|
|
13880
|
+
return /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column" }, visibleSessions.map((item, index) => {
|
|
12205
13881
|
const actualIndex = startIndex + index;
|
|
12206
13882
|
const isSelected = actualIndex === selectedIndex;
|
|
12207
13883
|
const title = item.session.title || "Untitled session";
|
|
@@ -12225,18 +13901,18 @@ var init_SessionSelector = __esm({
|
|
|
12225
13901
|
const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
|
|
12226
13902
|
const indicator = isSelected ? "\u25B6 " : " ";
|
|
12227
13903
|
const bgColor = isSelected ? theme.text.accent : void 0;
|
|
12228
|
-
return /* @__PURE__ */
|
|
12229
|
-
})), /* @__PURE__ */
|
|
13904
|
+
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, ")"));
|
|
13905
|
+
})), /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React30.createElement(Box27, null, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React30.createElement(Text25, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React30.createElement(Box27, { marginTop: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "cyan" }, "Loading more sessions..."))));
|
|
12230
13906
|
};
|
|
12231
13907
|
}
|
|
12232
13908
|
});
|
|
12233
13909
|
|
|
12234
13910
|
// src/ui/hooks/useModeToggle.ts
|
|
12235
|
-
import { useEffect as
|
|
13911
|
+
import { useEffect as useEffect13 } from "react";
|
|
12236
13912
|
function useModeToggle() {
|
|
12237
13913
|
const { subscribe, unsubscribe } = useKeypressContext();
|
|
12238
13914
|
const { agentMode, setAgentMode, isAgentRunning } = useSession();
|
|
12239
|
-
|
|
13915
|
+
useEffect13(() => {
|
|
12240
13916
|
const handleKeypress = (key) => {
|
|
12241
13917
|
if (key.name === "tab" && key.shift && !isAgentRunning) {
|
|
12242
13918
|
const newMode = agentMode === "plan" ? "build" : "plan";
|
|
@@ -12257,13 +13933,13 @@ var init_useModeToggle = __esm({
|
|
|
12257
13933
|
});
|
|
12258
13934
|
|
|
12259
13935
|
// src/ui/hooks/useOverlayEscapeGuard.ts
|
|
12260
|
-
import { useCallback as useCallback4, useMemo as useMemo4, useRef as
|
|
13936
|
+
import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef7 } from "react";
|
|
12261
13937
|
var useOverlayEscapeGuard;
|
|
12262
13938
|
var init_useOverlayEscapeGuard = __esm({
|
|
12263
13939
|
"src/ui/hooks/useOverlayEscapeGuard.ts"() {
|
|
12264
13940
|
"use strict";
|
|
12265
13941
|
useOverlayEscapeGuard = ({ overlays, suppressionMs = 250 }) => {
|
|
12266
|
-
const suppressUntilRef =
|
|
13942
|
+
const suppressUntilRef = useRef7(0);
|
|
12267
13943
|
const markOverlayClosed = useCallback4(() => {
|
|
12268
13944
|
suppressUntilRef.current = Date.now() + suppressionMs;
|
|
12269
13945
|
}, [suppressionMs]);
|
|
@@ -12275,11 +13951,11 @@ var init_useOverlayEscapeGuard = __esm({
|
|
|
12275
13951
|
});
|
|
12276
13952
|
|
|
12277
13953
|
// src/ui/App.tsx
|
|
12278
|
-
import { execSync as
|
|
13954
|
+
import { execSync as execSync5 } from "child_process";
|
|
12279
13955
|
import { homedir as homedir8 } from "os";
|
|
12280
|
-
import { Box as
|
|
13956
|
+
import { Box as Box28, Text as Text26, useApp as useApp2, useStdout as useStdout2 } from "ink";
|
|
12281
13957
|
import Spinner4 from "ink-spinner";
|
|
12282
|
-
import
|
|
13958
|
+
import React31, { useCallback as useCallback5, useEffect as useEffect14, useRef as useRef8, useState as useState18 } from "react";
|
|
12283
13959
|
var getGitBranch2, getCurrentFolder2, AppContent, App;
|
|
12284
13960
|
var init_App = __esm({
|
|
12285
13961
|
"src/ui/App.tsx"() {
|
|
@@ -12306,6 +13982,7 @@ var init_App = __esm({
|
|
|
12306
13982
|
init_MessageList();
|
|
12307
13983
|
init_ModelSelector();
|
|
12308
13984
|
init_ProviderSelector();
|
|
13985
|
+
init_QuestionSelector();
|
|
12309
13986
|
init_SessionSelector();
|
|
12310
13987
|
init_SessionContext();
|
|
12311
13988
|
init_useKeypress();
|
|
@@ -12315,7 +13992,7 @@ var init_App = __esm({
|
|
|
12315
13992
|
init_theme();
|
|
12316
13993
|
getGitBranch2 = () => {
|
|
12317
13994
|
try {
|
|
12318
|
-
return
|
|
13995
|
+
return execSync5("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
|
|
12319
13996
|
} catch {
|
|
12320
13997
|
return "";
|
|
12321
13998
|
}
|
|
@@ -12328,36 +14005,37 @@ var init_App = __esm({
|
|
|
12328
14005
|
}
|
|
12329
14006
|
return cwd;
|
|
12330
14007
|
};
|
|
12331
|
-
AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
|
|
14008
|
+
AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession, onQuestionAnswer }) => {
|
|
12332
14009
|
const { exit } = useApp2();
|
|
12333
14010
|
const { stdout } = useStdout2();
|
|
12334
|
-
const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
|
|
14011
|
+
const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider, pendingQuestion, setPendingQuestion } = useSession();
|
|
12335
14012
|
useModeToggle();
|
|
12336
|
-
const [terminalWidth, setTerminalWidth] =
|
|
12337
|
-
const [showInput, setShowInput] =
|
|
12338
|
-
const [gitBranch] =
|
|
12339
|
-
const [currentFolder] =
|
|
12340
|
-
const hasInputContentRef =
|
|
12341
|
-
const [exitWarning, setExitWarning] =
|
|
12342
|
-
const inputPromptRef =
|
|
12343
|
-
const [showSessionSelector, setShowSessionSelector] =
|
|
12344
|
-
const [showModelSelector, setShowModelSelector] =
|
|
12345
|
-
const [showProviderSelector, setShowProviderSelector] =
|
|
12346
|
-
const [
|
|
12347
|
-
const [
|
|
12348
|
-
const [
|
|
12349
|
-
const [
|
|
12350
|
-
const [
|
|
12351
|
-
const [
|
|
12352
|
-
const [
|
|
12353
|
-
const [
|
|
14013
|
+
const [terminalWidth, setTerminalWidth] = useState18(process.stdout.columns || 80);
|
|
14014
|
+
const [showInput, setShowInput] = useState18(true);
|
|
14015
|
+
const [gitBranch] = useState18(() => getGitBranch2());
|
|
14016
|
+
const [currentFolder] = useState18(() => getCurrentFolder2(config2.cwd));
|
|
14017
|
+
const hasInputContentRef = useRef8(false);
|
|
14018
|
+
const [exitWarning, setExitWarning] = useState18(null);
|
|
14019
|
+
const inputPromptRef = useRef8(null);
|
|
14020
|
+
const [showSessionSelector, setShowSessionSelector] = useState18(false);
|
|
14021
|
+
const [showModelSelector, setShowModelSelector] = useState18(false);
|
|
14022
|
+
const [showProviderSelector, setShowProviderSelector] = useState18(false);
|
|
14023
|
+
const [claudeMaxAvailable, setClaudeMaxAvailable] = useState18(false);
|
|
14024
|
+
const [showFeedbackDialog, setShowFeedbackDialog] = useState18(false);
|
|
14025
|
+
const [isLoadingSession, setIsLoadingSession] = useState18(false);
|
|
14026
|
+
const [showFixFlow, setShowFixFlow] = useState18(false);
|
|
14027
|
+
const [fixRunId, setFixRunId] = useState18(void 0);
|
|
14028
|
+
const [showMcpServers, setShowMcpServers] = useState18(false);
|
|
14029
|
+
const [showMcpAdd, setShowMcpAdd] = useState18(false);
|
|
14030
|
+
const [showMcpSelector, setShowMcpSelector] = useState18(false);
|
|
14031
|
+
const [mcpSelectorAction, setMcpSelectorAction] = useState18(
|
|
12354
14032
|
"remove"
|
|
12355
14033
|
);
|
|
12356
|
-
const [mcpServers, setMcpServers] =
|
|
12357
|
-
const [authState, setAuthState] =
|
|
14034
|
+
const [mcpServers, setMcpServers] = useState18([]);
|
|
14035
|
+
const [authState, setAuthState] = useState18(
|
|
12358
14036
|
() => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
|
|
12359
14037
|
);
|
|
12360
|
-
const [showAuthDialog, setShowAuthDialog] =
|
|
14038
|
+
const [showAuthDialog, setShowAuthDialog] = useState18(false);
|
|
12361
14039
|
const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
|
|
12362
14040
|
overlays: [
|
|
12363
14041
|
showSessionSelector,
|
|
@@ -12368,15 +14046,16 @@ var init_App = __esm({
|
|
|
12368
14046
|
showFixFlow,
|
|
12369
14047
|
showMcpServers,
|
|
12370
14048
|
showMcpAdd,
|
|
12371
|
-
showMcpSelector
|
|
14049
|
+
showMcpSelector,
|
|
14050
|
+
!!pendingQuestion
|
|
12372
14051
|
]
|
|
12373
14052
|
});
|
|
12374
|
-
|
|
14053
|
+
useEffect14(() => {
|
|
12375
14054
|
if (!config2.supatestApiKey) {
|
|
12376
14055
|
setShowAuthDialog(true);
|
|
12377
14056
|
}
|
|
12378
14057
|
}, [config2.supatestApiKey]);
|
|
12379
|
-
|
|
14058
|
+
useEffect14(() => {
|
|
12380
14059
|
if (sessionId) {
|
|
12381
14060
|
setSessionId(sessionId);
|
|
12382
14061
|
}
|
|
@@ -12391,6 +14070,7 @@ var init_App = __esm({
|
|
|
12391
14070
|
type: "assistant",
|
|
12392
14071
|
content: "Opening browser for authentication..."
|
|
12393
14072
|
});
|
|
14073
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
12394
14074
|
try {
|
|
12395
14075
|
const result = await loginCommand();
|
|
12396
14076
|
saveToken(result.token, result.expiresAt);
|
|
@@ -12537,6 +14217,7 @@ var init_App = __esm({
|
|
|
12537
14217
|
type: "assistant",
|
|
12538
14218
|
content: "Running setup..."
|
|
12539
14219
|
});
|
|
14220
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
12540
14221
|
try {
|
|
12541
14222
|
const result = await setupCommand({ cwd: config2.cwd || process.cwd() });
|
|
12542
14223
|
addMessage({
|
|
@@ -12630,9 +14311,45 @@ var init_App = __esm({
|
|
|
12630
14311
|
markOverlayClosed();
|
|
12631
14312
|
setShowModelSelector(false);
|
|
12632
14313
|
};
|
|
12633
|
-
const handleProviderSelect = (provider) => {
|
|
14314
|
+
const handleProviderSelect = async (provider) => {
|
|
12634
14315
|
setShowProviderSelector(false);
|
|
12635
14316
|
markOverlayClosed();
|
|
14317
|
+
if (provider === "claude-max") {
|
|
14318
|
+
const hasAuth = await isClaudeMaxAvailable();
|
|
14319
|
+
if (!hasAuth) {
|
|
14320
|
+
addMessage({
|
|
14321
|
+
type: "assistant",
|
|
14322
|
+
content: "Claude Max requires OAuth authentication. Starting authentication flow..."
|
|
14323
|
+
});
|
|
14324
|
+
try {
|
|
14325
|
+
const { ClaudeOAuthService: ClaudeOAuthService2 } = await Promise.resolve().then(() => (init_claude_oauth(), claude_oauth_exports));
|
|
14326
|
+
const { getSecretStorage: getSecretStorage2 } = await Promise.resolve().then(() => (init_secret_storage(), secret_storage_exports));
|
|
14327
|
+
const secretStorage = getSecretStorage2();
|
|
14328
|
+
const oauthService = new ClaudeOAuthService2(secretStorage);
|
|
14329
|
+
const result = await oauthService.authorize();
|
|
14330
|
+
if (!result.success) {
|
|
14331
|
+
addMessage({
|
|
14332
|
+
type: "error",
|
|
14333
|
+
content: `Authentication failed: ${result.error || "Unknown error"}`,
|
|
14334
|
+
errorType: "error"
|
|
14335
|
+
});
|
|
14336
|
+
return;
|
|
14337
|
+
}
|
|
14338
|
+
addMessage({
|
|
14339
|
+
type: "assistant",
|
|
14340
|
+
content: "\u2705 Successfully authenticated with Claude! Switching to Claude Max provider..."
|
|
14341
|
+
});
|
|
14342
|
+
setClaudeMaxAvailable(true);
|
|
14343
|
+
} catch (error) {
|
|
14344
|
+
addMessage({
|
|
14345
|
+
type: "error",
|
|
14346
|
+
content: `Authentication failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
14347
|
+
errorType: "error"
|
|
14348
|
+
});
|
|
14349
|
+
return;
|
|
14350
|
+
}
|
|
14351
|
+
}
|
|
14352
|
+
}
|
|
12636
14353
|
setLlmProvider(provider);
|
|
12637
14354
|
saveSupatestSettings(config2.cwd || process.cwd(), { llmProvider: provider });
|
|
12638
14355
|
addMessage({
|
|
@@ -12683,6 +14400,38 @@ var init_App = __esm({
|
|
|
12683
14400
|
markOverlayClosed();
|
|
12684
14401
|
setShowFeedbackDialog(false);
|
|
12685
14402
|
};
|
|
14403
|
+
const handleQuestionSubmit = (answers) => {
|
|
14404
|
+
if (!pendingQuestion) return;
|
|
14405
|
+
const formattedAnswer = answers.length === 1 ? answers[0] : answers.join(", ");
|
|
14406
|
+
addMessage({
|
|
14407
|
+
type: "user",
|
|
14408
|
+
content: formattedAnswer
|
|
14409
|
+
});
|
|
14410
|
+
const answerRecord = {
|
|
14411
|
+
"answer": formattedAnswer
|
|
14412
|
+
};
|
|
14413
|
+
if (onQuestionAnswer) {
|
|
14414
|
+
const success = onQuestionAnswer(pendingQuestion.toolUseId, answerRecord);
|
|
14415
|
+
if (!success) {
|
|
14416
|
+
console.warn("[App] Failed to submit answer - no pending question found");
|
|
14417
|
+
}
|
|
14418
|
+
}
|
|
14419
|
+
setPendingQuestion(null);
|
|
14420
|
+
markOverlayClosed();
|
|
14421
|
+
};
|
|
14422
|
+
const handleQuestionCancel = () => {
|
|
14423
|
+
if (!pendingQuestion) return;
|
|
14424
|
+
if (onQuestionAnswer) {
|
|
14425
|
+
onQuestionAnswer(pendingQuestion.toolUseId, null);
|
|
14426
|
+
}
|
|
14427
|
+
const cancelMsg = "I'd prefer not to answer this question right now.";
|
|
14428
|
+
addMessage({
|
|
14429
|
+
type: "user",
|
|
14430
|
+
content: cancelMsg
|
|
14431
|
+
});
|
|
14432
|
+
setPendingQuestion(null);
|
|
14433
|
+
markOverlayClosed();
|
|
14434
|
+
};
|
|
12686
14435
|
const handleFixFlowCancel = () => {
|
|
12687
14436
|
markOverlayClosed();
|
|
12688
14437
|
setShowFixFlow(false);
|
|
@@ -12822,8 +14571,8 @@ var init_App = __esm({
|
|
|
12822
14571
|
markOverlayClosed();
|
|
12823
14572
|
setShowMcpServers(true);
|
|
12824
14573
|
};
|
|
12825
|
-
const isInitialMount =
|
|
12826
|
-
|
|
14574
|
+
const isInitialMount = useRef8(true);
|
|
14575
|
+
useEffect14(() => {
|
|
12827
14576
|
const handleResize = () => {
|
|
12828
14577
|
setTerminalWidth(process.stdout.columns || 80);
|
|
12829
14578
|
};
|
|
@@ -12832,7 +14581,7 @@ var init_App = __esm({
|
|
|
12832
14581
|
process.stdout.off("resize", handleResize);
|
|
12833
14582
|
};
|
|
12834
14583
|
}, []);
|
|
12835
|
-
|
|
14584
|
+
useEffect14(() => {
|
|
12836
14585
|
if (isInitialMount.current) {
|
|
12837
14586
|
isInitialMount.current = false;
|
|
12838
14587
|
return;
|
|
@@ -12894,7 +14643,7 @@ var init_App = __esm({
|
|
|
12894
14643
|
},
|
|
12895
14644
|
{ isActive: !isOverlayOpen }
|
|
12896
14645
|
);
|
|
12897
|
-
|
|
14646
|
+
useEffect14(() => {
|
|
12898
14647
|
if (config2.task) {
|
|
12899
14648
|
addMessage({
|
|
12900
14649
|
type: "user",
|
|
@@ -12902,14 +14651,14 @@ var init_App = __esm({
|
|
|
12902
14651
|
});
|
|
12903
14652
|
}
|
|
12904
14653
|
}, []);
|
|
12905
|
-
return /* @__PURE__ */
|
|
12906
|
-
|
|
14654
|
+
return /* @__PURE__ */ React31.createElement(
|
|
14655
|
+
Box28,
|
|
12907
14656
|
{
|
|
12908
14657
|
flexDirection: "column",
|
|
12909
14658
|
paddingX: 1
|
|
12910
14659
|
},
|
|
12911
|
-
/* @__PURE__ */
|
|
12912
|
-
showSessionSelector && apiClient && /* @__PURE__ */
|
|
14660
|
+
/* @__PURE__ */ React31.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
|
|
14661
|
+
showSessionSelector && apiClient && /* @__PURE__ */ React31.createElement(
|
|
12913
14662
|
SessionSelector,
|
|
12914
14663
|
{
|
|
12915
14664
|
apiClient,
|
|
@@ -12917,8 +14666,8 @@ var init_App = __esm({
|
|
|
12917
14666
|
onSelect: handleSessionSelect
|
|
12918
14667
|
}
|
|
12919
14668
|
),
|
|
12920
|
-
isLoadingSession && /* @__PURE__ */
|
|
12921
|
-
showModelSelector && /* @__PURE__ */
|
|
14669
|
+
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")),
|
|
14670
|
+
showModelSelector && /* @__PURE__ */ React31.createElement(
|
|
12922
14671
|
ModelSelector,
|
|
12923
14672
|
{
|
|
12924
14673
|
currentModel: selectedModel,
|
|
@@ -12927,29 +14676,28 @@ var init_App = __esm({
|
|
|
12927
14676
|
onSelect: handleModelSelect
|
|
12928
14677
|
}
|
|
12929
14678
|
),
|
|
12930
|
-
showProviderSelector && /* @__PURE__ */
|
|
14679
|
+
showProviderSelector && /* @__PURE__ */ React31.createElement(
|
|
12931
14680
|
ProviderSelector,
|
|
12932
14681
|
{
|
|
12933
|
-
claudeMaxAvailable: isClaudeMaxAvailable(),
|
|
12934
14682
|
currentProvider: llmProvider,
|
|
12935
14683
|
onCancel: handleProviderSelectorCancel,
|
|
12936
14684
|
onSelect: handleProviderSelect
|
|
12937
14685
|
}
|
|
12938
14686
|
),
|
|
12939
|
-
showAuthDialog && /* @__PURE__ */
|
|
14687
|
+
showAuthDialog && /* @__PURE__ */ React31.createElement(
|
|
12940
14688
|
AuthDialog,
|
|
12941
14689
|
{
|
|
12942
14690
|
onLogin: handleLogin
|
|
12943
14691
|
}
|
|
12944
14692
|
),
|
|
12945
|
-
showFeedbackDialog && /* @__PURE__ */
|
|
14693
|
+
showFeedbackDialog && /* @__PURE__ */ React31.createElement(
|
|
12946
14694
|
FeedbackDialog,
|
|
12947
14695
|
{
|
|
12948
14696
|
onCancel: handleFeedbackCancel,
|
|
12949
14697
|
onSubmit: handleFeedbackSubmit
|
|
12950
14698
|
}
|
|
12951
14699
|
),
|
|
12952
|
-
showFixFlow && apiClient && /* @__PURE__ */
|
|
14700
|
+
showFixFlow && apiClient && /* @__PURE__ */ React31.createElement(
|
|
12953
14701
|
FixFlow,
|
|
12954
14702
|
{
|
|
12955
14703
|
apiClient,
|
|
@@ -12959,7 +14707,7 @@ var init_App = __esm({
|
|
|
12959
14707
|
onStartFix: handleFixStart
|
|
12960
14708
|
}
|
|
12961
14709
|
),
|
|
12962
|
-
showMcpServers && /* @__PURE__ */
|
|
14710
|
+
showMcpServers && /* @__PURE__ */ React31.createElement(
|
|
12963
14711
|
McpServersDisplay,
|
|
12964
14712
|
{
|
|
12965
14713
|
cwd: config2.cwd,
|
|
@@ -12969,8 +14717,8 @@ var init_App = __esm({
|
|
|
12969
14717
|
onTest: handleMcpTest
|
|
12970
14718
|
}
|
|
12971
14719
|
),
|
|
12972
|
-
showMcpAdd && /* @__PURE__ */
|
|
12973
|
-
showMcpSelector && /* @__PURE__ */
|
|
14720
|
+
showMcpAdd && /* @__PURE__ */ React31.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
|
|
14721
|
+
showMcpSelector && /* @__PURE__ */ React31.createElement(
|
|
12974
14722
|
McpServerSelector,
|
|
12975
14723
|
{
|
|
12976
14724
|
action: mcpSelectorAction,
|
|
@@ -12979,7 +14727,17 @@ var init_App = __esm({
|
|
|
12979
14727
|
servers: mcpServers
|
|
12980
14728
|
}
|
|
12981
14729
|
),
|
|
12982
|
-
|
|
14730
|
+
pendingQuestion && /* @__PURE__ */ React31.createElement(
|
|
14731
|
+
QuestionSelector,
|
|
14732
|
+
{
|
|
14733
|
+
multiSelect: pendingQuestion.multiSelect,
|
|
14734
|
+
onCancel: handleQuestionCancel,
|
|
14735
|
+
onSubmit: handleQuestionSubmit,
|
|
14736
|
+
options: pendingQuestion.options || [],
|
|
14737
|
+
question: pendingQuestion.question
|
|
14738
|
+
}
|
|
14739
|
+
),
|
|
14740
|
+
/* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React31.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !pendingQuestion && !isLoadingSession && /* @__PURE__ */ React31.createElement(Box28, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React31.createElement(Box28, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React31.createElement(Text26, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React31.createElement(
|
|
12983
14741
|
InputPrompt,
|
|
12984
14742
|
{
|
|
12985
14743
|
currentFolder,
|
|
@@ -12995,13 +14753,13 @@ var init_App = __esm({
|
|
|
12995
14753
|
);
|
|
12996
14754
|
};
|
|
12997
14755
|
App = (props) => {
|
|
12998
|
-
return /* @__PURE__ */
|
|
14756
|
+
return /* @__PURE__ */ React31.createElement(AppContent, { ...props });
|
|
12999
14757
|
};
|
|
13000
14758
|
}
|
|
13001
14759
|
});
|
|
13002
14760
|
|
|
13003
14761
|
// src/ui/hooks/useBracketedPaste.ts
|
|
13004
|
-
import { useEffect as
|
|
14762
|
+
import { useEffect as useEffect15 } from "react";
|
|
13005
14763
|
var ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, useBracketedPaste;
|
|
13006
14764
|
var init_useBracketedPaste = __esm({
|
|
13007
14765
|
"src/ui/hooks/useBracketedPaste.ts"() {
|
|
@@ -13010,7 +14768,7 @@ var init_useBracketedPaste = __esm({
|
|
|
13010
14768
|
ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
|
|
13011
14769
|
DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
|
|
13012
14770
|
useBracketedPaste = () => {
|
|
13013
|
-
|
|
14771
|
+
useEffect15(() => {
|
|
13014
14772
|
writeToStdout(ENABLE_BRACKETED_PASTE);
|
|
13015
14773
|
const cleanup = () => {
|
|
13016
14774
|
writeToStdout(DISABLE_BRACKETED_PASTE);
|
|
@@ -13031,7 +14789,7 @@ __export(interactive_exports, {
|
|
|
13031
14789
|
runInteractive: () => runInteractive
|
|
13032
14790
|
});
|
|
13033
14791
|
import { render as render2 } from "ink";
|
|
13034
|
-
import
|
|
14792
|
+
import React32, { useEffect as useEffect16, useRef as useRef9 } from "react";
|
|
13035
14793
|
function getToolDescription2(toolName, input) {
|
|
13036
14794
|
switch (toolName) {
|
|
13037
14795
|
case "Read":
|
|
@@ -13164,7 +14922,7 @@ async function runInteractive(config2) {
|
|
|
13164
14922
|
webUrl = session.webUrl;
|
|
13165
14923
|
}
|
|
13166
14924
|
const { unmount, waitUntilExit } = render2(
|
|
13167
|
-
/* @__PURE__ */
|
|
14925
|
+
/* @__PURE__ */ React32.createElement(
|
|
13168
14926
|
InteractiveApp,
|
|
13169
14927
|
{
|
|
13170
14928
|
apiClient,
|
|
@@ -13229,7 +14987,7 @@ var init_interactive = __esm({
|
|
|
13229
14987
|
init_settings_loader();
|
|
13230
14988
|
init_stdio();
|
|
13231
14989
|
init_version();
|
|
13232
|
-
AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete }) => {
|
|
14990
|
+
AgentRunner = ({ config: config2, sessionId, apiClient, messageBridge, onComplete, onTurnComplete, onAgentCreated }) => {
|
|
13233
14991
|
const {
|
|
13234
14992
|
addMessage,
|
|
13235
14993
|
updateLastMessage,
|
|
@@ -13243,17 +15001,18 @@ var init_interactive = __esm({
|
|
|
13243
15001
|
setAgentMode,
|
|
13244
15002
|
planFilePath,
|
|
13245
15003
|
selectedModel,
|
|
13246
|
-
llmProvider
|
|
15004
|
+
llmProvider,
|
|
15005
|
+
setPendingQuestion
|
|
13247
15006
|
} = useSession();
|
|
13248
15007
|
const { setUsageStats } = useUsageStats();
|
|
13249
|
-
const agentRef =
|
|
13250
|
-
|
|
15008
|
+
const agentRef = useRef9(null);
|
|
15009
|
+
useEffect16(() => {
|
|
13251
15010
|
if (shouldInterruptAgent && agentRef.current) {
|
|
13252
15011
|
agentRef.current.abort();
|
|
13253
15012
|
setShouldInterruptAgent(false);
|
|
13254
15013
|
}
|
|
13255
15014
|
}, [shouldInterruptAgent, setShouldInterruptAgent]);
|
|
13256
|
-
|
|
15015
|
+
useEffect16(() => {
|
|
13257
15016
|
let isMounted = true;
|
|
13258
15017
|
const runAgent2 = async () => {
|
|
13259
15018
|
setIsAgentRunning(true);
|
|
@@ -13295,6 +15054,16 @@ var init_interactive = __esm({
|
|
|
13295
15054
|
},
|
|
13296
15055
|
onTurnComplete: () => {
|
|
13297
15056
|
if (isMounted) onTurnComplete?.();
|
|
15057
|
+
},
|
|
15058
|
+
onAskUserQuestion: (toolId, question, options, multiSelect) => {
|
|
15059
|
+
if (isMounted) {
|
|
15060
|
+
setPendingQuestion({
|
|
15061
|
+
toolUseId: toolId,
|
|
15062
|
+
question,
|
|
15063
|
+
options,
|
|
15064
|
+
multiSelect
|
|
15065
|
+
});
|
|
15066
|
+
}
|
|
13298
15067
|
}
|
|
13299
15068
|
},
|
|
13300
15069
|
apiClient,
|
|
@@ -13303,9 +15072,18 @@ var init_interactive = __esm({
|
|
|
13303
15072
|
);
|
|
13304
15073
|
let oauthToken;
|
|
13305
15074
|
if (llmProvider === "claude-max") {
|
|
13306
|
-
if (isClaudeMaxAvailable()) {
|
|
13307
|
-
|
|
13308
|
-
|
|
15075
|
+
if (await isClaudeMaxAvailable()) {
|
|
15076
|
+
const { ClaudeOAuthService: ClaudeOAuthService2 } = await Promise.resolve().then(() => (init_claude_oauth(), claude_oauth_exports));
|
|
15077
|
+
const { getSecretStorage: getSecretStorage2 } = await Promise.resolve().then(() => (init_secret_storage(), secret_storage_exports));
|
|
15078
|
+
const secretStorage = getSecretStorage2();
|
|
15079
|
+
const oauthService = new ClaudeOAuthService2(secretStorage);
|
|
15080
|
+
const token = await oauthService.getAccessToken();
|
|
15081
|
+
if (token) {
|
|
15082
|
+
oauthToken = token;
|
|
15083
|
+
logger.info("Using Claude Max subscription for LLM calls");
|
|
15084
|
+
} else {
|
|
15085
|
+
logger.warn("Claude Max OAuth token not available. Falling back to Supatest Managed.");
|
|
15086
|
+
}
|
|
13309
15087
|
} else {
|
|
13310
15088
|
logger.warn("Claude Max selected but not available. Falling back to Supatest Managed.");
|
|
13311
15089
|
}
|
|
@@ -13321,6 +15099,9 @@ var init_interactive = __esm({
|
|
|
13321
15099
|
};
|
|
13322
15100
|
const agent2 = new CoreAgent(presenter, messageBridge);
|
|
13323
15101
|
agentRef.current = agent2;
|
|
15102
|
+
if (onAgentCreated) {
|
|
15103
|
+
onAgentCreated(agent2);
|
|
15104
|
+
}
|
|
13324
15105
|
const result = await agent2.run(runConfig);
|
|
13325
15106
|
if (isMounted) {
|
|
13326
15107
|
onComplete(result.success, result.providerSessionId);
|
|
@@ -13361,17 +15142,18 @@ var init_interactive = __esm({
|
|
|
13361
15142
|
setIsAgentRunning
|
|
13362
15143
|
} = useSession();
|
|
13363
15144
|
const { setUsageStats } = useUsageStats();
|
|
13364
|
-
const [sessionId, setSessionId] =
|
|
13365
|
-
const [currentTask, setCurrentTask] =
|
|
13366
|
-
const [taskId, setTaskId] =
|
|
13367
|
-
const [shouldRunAgent, setShouldRunAgent] =
|
|
13368
|
-
const [taskQueue, setTaskQueue] =
|
|
13369
|
-
const [providerSessionId, setProviderSessionId] =
|
|
13370
|
-
const messageBridgeRef =
|
|
13371
|
-
const
|
|
13372
|
-
const
|
|
13373
|
-
const
|
|
13374
|
-
|
|
15145
|
+
const [sessionId, setSessionId] = React32.useState(initialSessionId);
|
|
15146
|
+
const [currentTask, setCurrentTask] = React32.useState(config2.task);
|
|
15147
|
+
const [taskId, setTaskId] = React32.useState(0);
|
|
15148
|
+
const [shouldRunAgent, setShouldRunAgent] = React32.useState(!!config2.task);
|
|
15149
|
+
const [taskQueue, setTaskQueue] = React32.useState([]);
|
|
15150
|
+
const [providerSessionId, setProviderSessionId] = React32.useState();
|
|
15151
|
+
const messageBridgeRef = React32.useRef(null);
|
|
15152
|
+
const agentRef = React32.useRef(null);
|
|
15153
|
+
const lastSubmitRef = React32.useRef(null);
|
|
15154
|
+
const [pendingInjected, setPendingInjected] = React32.useState([]);
|
|
15155
|
+
const pendingInjectedRef = React32.useRef([]);
|
|
15156
|
+
React32.useEffect(() => {
|
|
13375
15157
|
pendingInjectedRef.current = pendingInjected;
|
|
13376
15158
|
}, [pendingInjected]);
|
|
13377
15159
|
const handleSubmitTask = async (task) => {
|
|
@@ -13389,6 +15171,7 @@ var init_interactive = __esm({
|
|
|
13389
15171
|
});
|
|
13390
15172
|
setSessionId(session.sessionId);
|
|
13391
15173
|
setContextSessionId(session.sessionId);
|
|
15174
|
+
setProviderSessionId(void 0);
|
|
13392
15175
|
} catch (error) {
|
|
13393
15176
|
const errorMessage = error instanceof ApiError ? error.message : `Failed to create session: ${error instanceof Error ? error.message : String(error)}`;
|
|
13394
15177
|
addMessage({
|
|
@@ -13434,7 +15217,7 @@ var init_interactive = __esm({
|
|
|
13434
15217
|
if (shouldRunAgent && !messageBridgeRef.current) {
|
|
13435
15218
|
messageBridgeRef.current = new MessageBridge(providerSessionId || "");
|
|
13436
15219
|
}
|
|
13437
|
-
|
|
15220
|
+
React32.useEffect(() => {
|
|
13438
15221
|
if (!shouldRunAgent && taskQueue.length > 0) {
|
|
13439
15222
|
const [nextTask, ...remaining] = taskQueue;
|
|
13440
15223
|
setTaskQueue(remaining);
|
|
@@ -13448,20 +15231,27 @@ var init_interactive = __esm({
|
|
|
13448
15231
|
setShouldRunAgent(true);
|
|
13449
15232
|
}
|
|
13450
15233
|
}, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
|
|
13451
|
-
const handleClearSession =
|
|
15234
|
+
const handleClearSession = React32.useCallback(() => {
|
|
13452
15235
|
setSessionId(void 0);
|
|
13453
15236
|
setContextSessionId(void 0);
|
|
13454
15237
|
setProviderSessionId(void 0);
|
|
13455
15238
|
setTaskQueue([]);
|
|
13456
15239
|
setPendingInjected([]);
|
|
13457
15240
|
}, [setContextSessionId]);
|
|
13458
|
-
|
|
15241
|
+
const handleQuestionAnswer = React32.useCallback((questionId, answers) => {
|
|
15242
|
+
if (agentRef.current) {
|
|
15243
|
+
return agentRef.current.submitAnswer(questionId, answers);
|
|
15244
|
+
}
|
|
15245
|
+
return false;
|
|
15246
|
+
}, []);
|
|
15247
|
+
return /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(
|
|
13459
15248
|
App,
|
|
13460
15249
|
{
|
|
13461
15250
|
apiClient,
|
|
13462
15251
|
config: { ...config2, task: currentTask },
|
|
13463
15252
|
onClearSession: handleClearSession,
|
|
13464
15253
|
onExit,
|
|
15254
|
+
onQuestionAnswer: handleQuestionAnswer,
|
|
13465
15255
|
onResumeSession: async (session) => {
|
|
13466
15256
|
try {
|
|
13467
15257
|
if (!apiClient) {
|
|
@@ -13477,7 +15267,9 @@ var init_interactive = __esm({
|
|
|
13477
15267
|
const uiMessages = convertQueriesToUIMessages(queries);
|
|
13478
15268
|
setSessionId(session.id);
|
|
13479
15269
|
setContextSessionId(session.id);
|
|
13480
|
-
|
|
15270
|
+
const resumeProviderSessionId = session.providerSessionId || void 0;
|
|
15271
|
+
logger.debug(`Resume session: ${session.id}, providerSessionId: ${resumeProviderSessionId || "NOT SET"}`);
|
|
15272
|
+
setProviderSessionId(resumeProviderSessionId);
|
|
13481
15273
|
const contextData = await apiClient.getSessionContext(session.id);
|
|
13482
15274
|
if (contextData.contextTokens > 0) {
|
|
13483
15275
|
const cacheRead = contextData.cacheReadTokens || 0;
|
|
@@ -13518,13 +15310,16 @@ var init_interactive = __esm({
|
|
|
13518
15310
|
sessionId,
|
|
13519
15311
|
webUrl
|
|
13520
15312
|
}
|
|
13521
|
-
), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */
|
|
15313
|
+
), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React32.createElement(
|
|
13522
15314
|
AgentRunner,
|
|
13523
15315
|
{
|
|
13524
15316
|
apiClient,
|
|
13525
15317
|
config: { ...config2, task: currentTask, providerSessionId },
|
|
13526
15318
|
key: `${taskId}`,
|
|
13527
15319
|
messageBridge: messageBridgeRef.current,
|
|
15320
|
+
onAgentCreated: (agent2) => {
|
|
15321
|
+
agentRef.current = agent2;
|
|
15322
|
+
},
|
|
13528
15323
|
onComplete: handleAgentComplete,
|
|
13529
15324
|
onTurnComplete: () => {
|
|
13530
15325
|
const pending = pendingInjectedRef.current;
|
|
@@ -13543,7 +15338,7 @@ var init_interactive = __esm({
|
|
|
13543
15338
|
useBracketedPaste();
|
|
13544
15339
|
const settings = loadSupatestSettings(props.config.cwd || process.cwd());
|
|
13545
15340
|
const initialProvider = settings.llmProvider || "supatest-managed";
|
|
13546
|
-
return /* @__PURE__ */
|
|
15341
|
+
return /* @__PURE__ */ React32.createElement(KeypressProvider, null, /* @__PURE__ */ React32.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React32.createElement(InteractiveAppContent, { ...props })));
|
|
13547
15342
|
};
|
|
13548
15343
|
}
|
|
13549
15344
|
});
|
|
@@ -13826,7 +15621,7 @@ async function runAgent(config2) {
|
|
|
13826
15621
|
// src/utils/auto-update.ts
|
|
13827
15622
|
init_version();
|
|
13828
15623
|
init_error_logger();
|
|
13829
|
-
import { execSync as execSync3, spawn } from "child_process";
|
|
15624
|
+
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
13830
15625
|
import latestVersion from "latest-version";
|
|
13831
15626
|
import { gt } from "semver";
|
|
13832
15627
|
var UPDATE_CHECK_TIMEOUT = 3e3;
|
|
@@ -13867,8 +15662,24 @@ Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
|
|
|
13867
15662
|
timeout: INSTALL_TIMEOUT
|
|
13868
15663
|
});
|
|
13869
15664
|
}
|
|
15665
|
+
let updateVerified = false;
|
|
15666
|
+
try {
|
|
15667
|
+
const installedVersion = execSync3("npm ls -g @supatest/cli --json 2>/dev/null", {
|
|
15668
|
+
encoding: "utf-8"
|
|
15669
|
+
});
|
|
15670
|
+
const parsed = JSON.parse(installedVersion);
|
|
15671
|
+
const newVersion = parsed.dependencies?.["@supatest/cli"]?.version;
|
|
15672
|
+
if (newVersion && gt(newVersion, CLI_VERSION)) {
|
|
15673
|
+
updateVerified = true;
|
|
15674
|
+
}
|
|
15675
|
+
} catch {
|
|
15676
|
+
}
|
|
15677
|
+
if (!updateVerified) {
|
|
15678
|
+
console.log("Update completed but could not verify new version. Continuing with current version...\n");
|
|
15679
|
+
return;
|
|
15680
|
+
}
|
|
13870
15681
|
console.log("\u2713 Updated successfully\n");
|
|
13871
|
-
const child =
|
|
15682
|
+
const child = spawn2(process.argv[0], process.argv.slice(1), {
|
|
13872
15683
|
stdio: "inherit",
|
|
13873
15684
|
detached: false
|
|
13874
15685
|
});
|