From 4df68c49f29b9e229da5a3352e534a742f50e2b0 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Mon, 19 Oct 2026 19:35:58 -0700 Subject: [PATCH] refactor(app): consolidate handwritten application runtime --- .github/workflows/ci.yml | 4 +- .github/workflows/help-compatibility.yml | 18 +++--- AGENTS.md | 3 +- CONTRIBUTING.md | 12 ++-- cmd/openai/error_coverage_test.go | 5 +- cmd/openai/kitty_output_helper_test.go | 4 +- cmd/openai/main.go | 3 +- cmd/openai/main_readable_errors_test.go | 4 +- docs/architecture/repository-layout.md | 8 +++ docs/architecture/security-model.md | 19 +++--- docs/image-generation-saving.md | 4 +- .../app}/admin_credentials_test.go | 2 +- {pkg/custom => internal/app}/admin_policy.go | 2 +- {pkg/custom => internal/app}/audio_policy.go | 2 +- .../app}/base_url_security_test.go | 2 +- {pkg/custom => internal/app}/batch_policy.go | 2 +- .../app}/batch_transport_test.go | 2 +- .../app}/binaryresponse_automatic_test.go | 2 +- .../app}/binaryresponse_test.go | 2 +- .../app}/certificate_compatibility.go | 2 +- .../app}/certificate_compatibility_test.go | 2 +- {pkg/custom => internal/app}/cmdutil.go | 2 +- .../app}/cmdutil_format_test.go | 2 +- .../app}/cmdutil_output_errors_test.go | 2 +- .../app}/cmdutil_output_errors_unix_test.go | 2 +- .../app}/cmdutil_pretty_color_test.go | 2 +- {pkg/custom => internal/app}/cmdutil_test.go | 2 +- {pkg/custom => internal/app}/command.go | 2 +- {pkg/custom => internal/app}/command_error.go | 2 +- .../app}/command_error_test.go | 2 +- {pkg/custom => internal/app}/command_test.go | 2 +- .../app}/deterministic_stream_cleanup_test.go | 2 +- .../app}/download_file_windows_test.go | 2 +- .../app}/download_filename_test.go | 2 +- .../app}/download_terminal_test.go | 2 +- .../app}/finite_json_list.go | 2 +- .../app}/finite_json_list_test.go | 2 +- {pkg/custom => internal/app}/flagoptions.go | 2 +- internal/app/help_test.go | 7 +-- {pkg/custom => internal/app}/images_policy.go | 2 +- .../app}/input_file_errors.go | 2 +- .../app}/input_file_errors_test.go | 2 +- .../app}/iteratorlimit_test.go | 2 +- .../app}/kitty_output_helper_test.go | 2 +- .../app}/list_navigation.go | 2 +- .../app}/list_navigation_test.go | 2 +- .../app}/list_table_test.go | 2 +- {pkg/custom => internal/app}/local_errors.go | 2 +- .../app}/local_errors_test.go | 2 +- .../app}/local_utilities.go | 2 +- .../app}/local_utilities_test.go | 2 +- .../app}/local_utility_output_test.go | 2 +- .../app}/local_utility_routing_test.go | 2 +- .../app}/local_utility_test.go | 2 +- .../app}/models_list_filter_test.go | 2 +- .../app}/models_list_interrupt_test.go | 2 +- .../app}/models_list_loading_test.go | 2 +- .../app}/models_list_options_test.go | 2 +- .../app}/models_list_print_test.go | 2 +- .../app}/models_list_table_test.go | 2 +- .../app}/models_list_viewer_test.go | 2 +- .../app}/models_list_viewport_test.go | 2 +- {pkg/custom => internal/app}/models_policy.go | 2 +- .../app}/mtls_client_flag_test.go | 2 +- {pkg/custom => internal/app}/mtls_test.go | 2 +- .../app}/multipart_input_test.go | 2 +- .../custom => internal/app}/multipart_test.go | 2 +- .../app}/nested_error_guidance_test.go | 2 +- .../app}/nullablefile_test.go | 2 +- .../app}/output_invocation_test.go | 2 +- .../app}/output_invocation_unix_test.go | 2 +- {pkg/custom => internal/app}/output_policy.go | 2 +- .../app}/output_policy_test.go | 2 +- .../app}/output_transform.go | 2 +- .../app}/output_transform_test.go | 2 +- .../app}/parser_error_guidance_test.go | 2 +- .../app}/project_api_key_update.go | 2 +- .../app}/readable_errors.go | 2 +- .../app}/readable_errors_test.go | 2 +- .../app}/readable_output.go | 2 +- .../app}/readable_output_test.go | 2 +- .../app}/readable_request_id_test.go | 2 +- .../app}/readable_speech_test.go | 2 +- .../app}/report_save_receipt.go | 2 +- .../app}/report_save_receipt_test.go | 2 +- .../custom => internal/app}/requestheaders.go | 2 +- .../app}/resource_summaries_test.go | 2 +- .../app}/root_request_flags_test.go | 2 +- internal/app/run.go | 19 +++--- .../app}/save_download_symlink_test.go | 2 +- .../app}/save_download_symlink_unix_test.go | 2 +- .../app}/save_download_test.go | 2 +- .../app}/shell_suggestions.go | 2 +- .../app}/shell_suggestions_test.go | 2 +- .../app}/speech_context_test.go | 2 +- .../app}/stdin_errors_test.go | 2 +- .../app}/stdinsecurity_test.go | 2 +- .../app}/streaming_text_test.go | 2 +- .../app}/tokenizer_socket_test.go | 2 +- .../app}/unavailable_option.go | 2 +- .../app}/unavailable_option_test.go | 2 +- {pkg/custom => internal/app}/version.go | 2 +- .../admin/admin_setup_cancellation_test.go | 6 +- .../admin/admin_setup_interactive_test.go | 8 +-- .../commands/admin/admin_setup_origin_test.go | 4 +- internal/commands/admin/assembly_test.go | 6 +- .../audio/generation_assembly_test.go | 4 +- .../audio/readable_audio_assembly_test.go | 12 ++-- .../images/image_models_integration_test.go | 8 +-- ...age_picker_shell_setup_integration_test.go | 8 +-- .../image_preferences_integration_test.go | 18 +++--- ...eractive_image_preview_integration_test.go | 4 +- .../images/options_integration_test.go | 6 +- .../prepare_image_upload_integration_test.go | 4 +- ...save_generated_images_cancellation_test.go | 26 ++++---- .../commands/images/stream_cleanup_test.go | 4 +- internal/commands/manpages/command_test.go | 8 +-- internal/commands/subgroups_test.go | 6 +- internal/commands/tasks_test.go | 6 +- .../output/transform/select_transformer.go | 2 +- pkg/custom/runtime.go | 59 +++++++++++++++++++ release-please-config.json | 2 +- scripts/demos/models-list-viewer/record.sh | 4 +- .../demos/record-image-save-cancellation.py | 2 +- tests/cli/audiovoice_auth_contract_test.go | 4 +- tests/cli/mocktest_main_test.go | 4 +- 126 files changed, 285 insertions(+), 218 deletions(-) rename {pkg/custom => internal/app}/admin_credentials_test.go (99%) rename {pkg/custom => internal/app}/admin_policy.go (97%) rename {pkg/custom => internal/app}/audio_policy.go (99%) rename {pkg/custom => internal/app}/base_url_security_test.go (98%) rename {pkg/custom => internal/app}/batch_policy.go (99%) rename {pkg/custom => internal/app}/batch_transport_test.go (99%) rename {pkg/custom => internal/app}/binaryresponse_automatic_test.go (99%) rename {pkg/custom => internal/app}/binaryresponse_test.go (99%) rename {pkg/custom => internal/app}/certificate_compatibility.go (98%) rename {pkg/custom => internal/app}/certificate_compatibility_test.go (98%) rename {pkg/custom => internal/app}/cmdutil.go (99%) rename {pkg/custom => internal/app}/cmdutil_format_test.go (99%) rename {pkg/custom => internal/app}/cmdutil_output_errors_test.go (99%) rename {pkg/custom => internal/app}/cmdutil_output_errors_unix_test.go (99%) rename {pkg/custom => internal/app}/cmdutil_pretty_color_test.go (99%) rename {pkg/custom => internal/app}/cmdutil_test.go (99%) rename {pkg/custom => internal/app}/command.go (99%) rename {pkg/custom => internal/app}/command_error.go (99%) rename {pkg/custom => internal/app}/command_error_test.go (99%) rename {pkg/custom => internal/app}/command_test.go (99%) rename {pkg/custom => internal/app}/deterministic_stream_cleanup_test.go (99%) rename {pkg/custom => internal/app}/download_file_windows_test.go (99%) rename {pkg/custom => internal/app}/download_filename_test.go (99%) rename {pkg/custom => internal/app}/download_terminal_test.go (99%) rename {pkg/custom => internal/app}/finite_json_list.go (98%) rename {pkg/custom => internal/app}/finite_json_list_test.go (99%) rename {pkg/custom => internal/app}/flagoptions.go (99%) rename {pkg/custom => internal/app}/images_policy.go (99%) rename {pkg/custom => internal/app}/input_file_errors.go (98%) rename {pkg/custom => internal/app}/input_file_errors_test.go (99%) rename {pkg/custom => internal/app}/iteratorlimit_test.go (98%) rename {pkg/custom => internal/app}/kitty_output_helper_test.go (97%) rename {pkg/custom => internal/app}/list_navigation.go (99%) rename {pkg/custom => internal/app}/list_navigation_test.go (99%) rename {pkg/custom => internal/app}/list_table_test.go (99%) rename {pkg/custom => internal/app}/local_errors.go (99%) rename {pkg/custom => internal/app}/local_errors_test.go (99%) rename {pkg/custom => internal/app}/local_utilities.go (97%) rename {pkg/custom => internal/app}/local_utilities_test.go (99%) rename {pkg/custom => internal/app}/local_utility_output_test.go (99%) rename {pkg/custom => internal/app}/local_utility_routing_test.go (99%) rename {pkg/custom => internal/app}/local_utility_test.go (99%) rename {pkg/custom => internal/app}/models_list_filter_test.go (99%) rename {pkg/custom => internal/app}/models_list_interrupt_test.go (99%) rename {pkg/custom => internal/app}/models_list_loading_test.go (99%) rename {pkg/custom => internal/app}/models_list_options_test.go (99%) rename {pkg/custom => internal/app}/models_list_print_test.go (98%) rename {pkg/custom => internal/app}/models_list_table_test.go (98%) rename {pkg/custom => internal/app}/models_list_viewer_test.go (99%) rename {pkg/custom => internal/app}/models_list_viewport_test.go (99%) rename {pkg/custom => internal/app}/models_policy.go (99%) rename {pkg/custom => internal/app}/mtls_client_flag_test.go (98%) rename {pkg/custom => internal/app}/mtls_test.go (99%) rename {pkg/custom => internal/app}/multipart_input_test.go (99%) rename {pkg/custom => internal/app}/multipart_test.go (93%) rename {pkg/custom => internal/app}/nested_error_guidance_test.go (99%) rename {pkg/custom => internal/app}/nullablefile_test.go (99%) rename {pkg/custom => internal/app}/output_invocation_test.go (99%) rename {pkg/custom => internal/app}/output_invocation_unix_test.go (98%) rename {pkg/custom => internal/app}/output_policy.go (99%) rename {pkg/custom => internal/app}/output_policy_test.go (99%) rename {pkg/custom => internal/app}/output_transform.go (98%) rename {pkg/custom => internal/app}/output_transform_test.go (99%) rename {pkg/custom => internal/app}/parser_error_guidance_test.go (99%) rename {pkg/custom => internal/app}/project_api_key_update.go (98%) rename {pkg/custom => internal/app}/readable_errors.go (99%) rename {pkg/custom => internal/app}/readable_errors_test.go (99%) rename {pkg/custom => internal/app}/readable_output.go (99%) rename {pkg/custom => internal/app}/readable_output_test.go (99%) rename {pkg/custom => internal/app}/readable_request_id_test.go (99%) rename {pkg/custom => internal/app}/readable_speech_test.go (99%) rename {pkg/custom => internal/app}/report_save_receipt.go (99%) rename {pkg/custom => internal/app}/report_save_receipt_test.go (99%) rename {pkg/custom => internal/app}/requestheaders.go (96%) rename {pkg/custom => internal/app}/resource_summaries_test.go (99%) rename {pkg/custom => internal/app}/root_request_flags_test.go (99%) rename {pkg/custom => internal/app}/save_download_symlink_test.go (99%) rename {pkg/custom => internal/app}/save_download_symlink_unix_test.go (99%) rename {pkg/custom => internal/app}/save_download_test.go (99%) rename {pkg/custom => internal/app}/shell_suggestions.go (99%) rename {pkg/custom => internal/app}/shell_suggestions_test.go (99%) rename {pkg/custom => internal/app}/speech_context_test.go (98%) rename {pkg/custom => internal/app}/stdin_errors_test.go (99%) rename {pkg/custom => internal/app}/stdinsecurity_test.go (99%) rename {pkg/custom => internal/app}/streaming_text_test.go (99%) rename {pkg/custom => internal/app}/tokenizer_socket_test.go (99%) rename {pkg/custom => internal/app}/unavailable_option.go (98%) rename {pkg/custom => internal/app}/unavailable_option_test.go (99%) rename {pkg/custom => internal/app}/version.go (78%) create mode 100644 pkg/custom/runtime.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e7238cc..b9ead05a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: - name: Check native macOS picker metadata and profile transactions run: go test ./internal/autocomplete -run '^TestPicker' -count=1 -v - name: Check native macOS shell targeting and setup decisions - run: go test ./internal/commands/images ./pkg/custom -run '^TestImagePicker(Shell|FirstRun|TabChoice|MacOS)' -count=1 -v + run: go test ./internal/commands/images ./internal/app -run '^TestImagePicker(Shell|FirstRun|TabChoice|MacOS)' -count=1 -v minimum-unix-shells: name: Minimum Unix executable suggestions @@ -880,7 +880,7 @@ jobs: OPENAI_CLI_REQUIRE_NATIVE_SHELLS: cmd,powershell,pwsh - name: Check native Windows download completion and cleanup if: matrix.existing-checks - run: go test -p 2 ./pkg/custom ./internal/output -run '^(TestWindowsDownload|TestManagedDownload|TestDownload|TestAutomaticDownload|TestBinaryResponsePreserves|TestWriteBinaryResponse|TestReportSaveReceipt|TestSaveReceiptContext|TestResponseSaveReceipt)' -count=1 -v + run: go test -p 2 ./internal/app ./internal/output -run '^(TestWindowsDownload|TestManagedDownload|TestDownload|TestAutomaticDownload|TestBinaryResponsePreserves|TestWriteBinaryResponse|TestReportSaveReceipt|TestSaveReceiptContext|TestResponseSaveReceipt)' -count=1 -v env: GOMAXPROCS: '2' - name: Check native Windows Batch stdin and private staging diff --git a/.github/workflows/help-compatibility.yml b/.github/workflows/help-compatibility.yml index 984f7ff2..a6890f97 100644 --- a/.github/workflows/help-compatibility.yml +++ b/.github/workflows/help-compatibility.yml @@ -48,7 +48,7 @@ jobs: GOMAXPROCS: '2' run: | set -euo pipefail - go test -p 1 ./cmd/openai ./internal/... ./pkg/custom -run '^$' + go test -p 1 ./cmd/openai ./internal/... -run '^$' go build -p 1 -o "$RUNNER_TEMP/openai.exe" ./cmd/openai go mod verify @@ -503,7 +503,7 @@ jobs: shell: bash run: | set -euo pipefail - go test -p 2 ./pkg/custom ./internal/output/listview -count=1 -timeout=2m -v -json -run '^(TestListPageFetcherWaitsAndPreservesPageSize|TestShowJSONPagesZeroRetainsRequestErrors|TestListPageFetcherStopsEmptyAndStalledCursors|TestListPageFetcherCancellationPreventsRequests|TestListNavigationUsesLoadedRowsBeforeOneRequest|TestListNavigationOutputPreservesFailureAndRestoresModes|TestListNavigationOutputFailureDiagnostic|TestListNavigationQuitRetainsRealFailures|TestListNavigationLargeFieldPreservedAndPrewrapped|TestListNavigationPrintPageCancelsWithoutFetching|TestListNavigationLabelsPreserveLongIDs|TestListNavigationNarrowFooterKeepsQuitAndPrintVisible|TestListNavigationErrorPresentation|TestListNavigationCompleteFirstPageFitsScreen|TestListNavigationContentFitsDisplayCells|TestListNavigationShortResultPreservesErrorsAndHistory|TestRenderListNavigationPageHintPolicy|TestRenderListNavigationPageCancelsProjection|TestRenderListTablePage|TestRenderListTablePageEmptyAndUnsupported)$' \ + go test -p 2 ./internal/app ./internal/output/listview -count=1 -timeout=2m -v -json -run '^(TestListPageFetcherWaitsAndPreservesPageSize|TestShowJSONPagesZeroRetainsRequestErrors|TestListPageFetcherStopsEmptyAndStalledCursors|TestListPageFetcherCancellationPreventsRequests|TestListNavigationUsesLoadedRowsBeforeOneRequest|TestListNavigationOutputPreservesFailureAndRestoresModes|TestListNavigationOutputFailureDiagnostic|TestListNavigationQuitRetainsRealFailures|TestListNavigationLargeFieldPreservedAndPrewrapped|TestListNavigationPrintPageCancelsWithoutFetching|TestListNavigationLabelsPreserveLongIDs|TestListNavigationNarrowFooterKeepsQuitAndPrintVisible|TestListNavigationErrorPresentation|TestListNavigationCompleteFirstPageFitsScreen|TestListNavigationContentFitsDisplayCells|TestListNavigationShortResultPreservesErrorsAndHistory|TestRenderListNavigationPageHintPolicy|TestRenderListNavigationPageCancelsProjection|TestRenderListTablePage|TestRenderListTablePageEmptyAndUnsupported)$' \ | tee "$RUNNER_TEMP/list-navigation-helper-tests.jsonl" go test -p 2 ./internal/output/transform -count=1 -timeout=2m -v -json -run '^(TestProjectListTableCanceledContext|TestListTableCountsCancellation|TestProjectListTableKnownResources|TestProjectListTableMissingOptionalFields|TestProjectListTableEmptyPagesAndRoutes|TestProjectListTablePreservesOriginalStrings|TestProjectListTableFallsBackForEntirePage|TestProjectListTablePreservesErrorsAndMalformedFields|TestProjectListTableByteSizes)$' \ | tee "$RUNNER_TEMP/list-projection-helper-tests.jsonl" @@ -514,7 +514,7 @@ jobs: shell: pwsh run: | $ErrorActionPreference = 'Stop' - go test -p 2 ./pkg/custom ./internal/output/listview -count=1 -timeout=2m -v -json -run '^(TestListPageFetcherWaitsAndPreservesPageSize|TestShowJSONPagesZeroRetainsRequestErrors|TestListPageFetcherStopsEmptyAndStalledCursors|TestListPageFetcherCancellationPreventsRequests|TestListNavigationUsesLoadedRowsBeforeOneRequest|TestListNavigationOutputPreservesFailureAndRestoresModes|TestListNavigationOutputFailureDiagnostic|TestListNavigationQuitRetainsRealFailures|TestListNavigationLargeFieldPreservedAndPrewrapped|TestListNavigationPrintPageCancelsWithoutFetching|TestListNavigationLabelsPreserveLongIDs|TestListNavigationNarrowFooterKeepsQuitAndPrintVisible|TestListNavigationErrorPresentation|TestListNavigationCompleteFirstPageFitsScreen|TestListNavigationContentFitsDisplayCells|TestListNavigationShortResultPreservesErrorsAndHistory|TestRenderListNavigationPageHintPolicy|TestRenderListNavigationPageCancelsProjection|TestRenderListTablePage|TestRenderListTablePageEmptyAndUnsupported)$' | + go test -p 2 ./internal/app ./internal/output/listview -count=1 -timeout=2m -v -json -run '^(TestListPageFetcherWaitsAndPreservesPageSize|TestShowJSONPagesZeroRetainsRequestErrors|TestListPageFetcherStopsEmptyAndStalledCursors|TestListPageFetcherCancellationPreventsRequests|TestListNavigationUsesLoadedRowsBeforeOneRequest|TestListNavigationOutputPreservesFailureAndRestoresModes|TestListNavigationOutputFailureDiagnostic|TestListNavigationQuitRetainsRealFailures|TestListNavigationLargeFieldPreservedAndPrewrapped|TestListNavigationPrintPageCancelsWithoutFetching|TestListNavigationLabelsPreserveLongIDs|TestListNavigationNarrowFooterKeepsQuitAndPrintVisible|TestListNavigationErrorPresentation|TestListNavigationCompleteFirstPageFitsScreen|TestListNavigationContentFitsDisplayCells|TestListNavigationShortResultPreservesErrorsAndHistory|TestRenderListNavigationPageHintPolicy|TestRenderListNavigationPageCancelsProjection|TestRenderListTablePage|TestRenderListTablePageEmptyAndUnsupported)$' | Tee-Object -FilePath (Join-Path $env:RUNNER_TEMP 'list-navigation-helper-tests.jsonl') -Encoding utf8NoBOM -ErrorAction Stop if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } go test -p 2 ./internal/output/transform -count=1 -timeout=2m -v -json -run '^(TestProjectListTableCanceledContext|TestListTableCountsCancellation|TestProjectListTableKnownResources|TestProjectListTableMissingOptionalFields|TestProjectListTableEmptyPagesAndRoutes|TestProjectListTablePreservesOriginalStrings|TestProjectListTableFallsBackForEntirePage|TestProjectListTablePreservesErrorsAndMalformedFields|TestProjectListTableByteSizes)$' | @@ -556,7 +556,7 @@ jobs: set -euo pipefail go test -p 2 ./cmd/openai -count=1 -timeout=3m -v -json -run '^(TestMainGlobalFlagsPlacementHeadersAndOutput|TestMainGlobalFlagsPrecedence|TestMainGlobalFlagsHeaderAliasesAndAuthorization|TestMainGlobalFlagsKeepEndpointFlagsAndLiterals|TestMainGlobalFlagsProjectBodyKeepsRootHeader|TestMainGlobalFlagsDeepAdminAndCustomCommands|TestMainGlobalFlagsHelpAndCompletionStayLocal|TestMainGlobalFlagsErrorOutputPlacement|TestMainGlobalFlagBaseURLOverridesMalformedEnvironment|TestMainGlobalFlagInvalidPercentBaseURLStillFails|TestMainGlobalFlagBaseURLOverridePreservesEnvironmentHeaders|TestMainGlobalFlagInvalidPercentBaseURLErrorsAreSafe|TestMainGlobalFlagEmptyBaseURLKeepsEnvironmentFallback|TestMainGlobalFlagEmptyMTLSOverridesEnvironment|TestMainGlobalFlagMTLSRejectsExplicitDuplicates|TestMainGlobalFlagCompletionScriptsIgnoreRequestConfiguration|TestMainGlobalFlagCompletionWordsRemainRequestValues|TestMainGlobalFlagRequestConfigurationStillValidates|TestMainGlobalFlagURLConfigurationPreservesOfflineHelp)$' \ | tee "$RUNNER_TEMP/request-configuration-public-tests.jsonl" - go test -p 2 ./pkg/custom -count=1 -timeout=2m -v -json -run '^(TestRootRequestFlagsPreserveOwnershipAndMetadata|TestRootRequestFlagsPreserveMTLSPlacement|TestRootRequestFlagsRetainSourcesAliasesAndValidation|TestMTLSClientFlagEnvironmentDoesNotConsumeExplicitValue)$' \ + go test -p 2 ./internal/app -count=1 -timeout=2m -v -json -run '^(TestRootRequestFlagsPreserveOwnershipAndMetadata|TestRootRequestFlagsPreserveMTLSPlacement|TestRootRequestFlagsRetainSourcesAliasesAndValidation|TestMTLSClientFlagEnvironmentDoesNotConsumeExplicitValue)$' \ | tee "$RUNNER_TEMP/request-configuration-helper-tests.jsonl" - name: Test Windows request configuration if: matrix.request-configuration && runner.os == 'Windows' && matrix.shard != 'dispatch' @@ -566,7 +566,7 @@ jobs: go test -p 2 ./cmd/openai -count=1 -timeout=3m -v -json -run '^(TestMainGlobalFlagsPlacementHeadersAndOutput|TestMainGlobalFlagsPrecedence|TestMainGlobalFlagsHeaderAliasesAndAuthorization|TestMainGlobalFlagsKeepEndpointFlagsAndLiterals|TestMainGlobalFlagsProjectBodyKeepsRootHeader|TestMainGlobalFlagsDeepAdminAndCustomCommands|TestMainGlobalFlagsHelpAndCompletionStayLocal|TestMainGlobalFlagsErrorOutputPlacement|TestMainGlobalFlagBaseURLOverridesMalformedEnvironment|TestMainGlobalFlagInvalidPercentBaseURLStillFails|TestMainGlobalFlagBaseURLOverridePreservesEnvironmentHeaders|TestMainGlobalFlagInvalidPercentBaseURLErrorsAreSafe|TestMainGlobalFlagEmptyBaseURLKeepsEnvironmentFallback|TestMainGlobalFlagEmptyMTLSOverridesEnvironment|TestMainGlobalFlagMTLSRejectsExplicitDuplicates|TestMainGlobalFlagCompletionScriptsIgnoreRequestConfiguration|TestMainGlobalFlagCompletionWordsRemainRequestValues|TestMainGlobalFlagRequestConfigurationStillValidates|TestMainGlobalFlagURLConfigurationPreservesOfflineHelp)$' | Tee-Object -FilePath (Join-Path $env:RUNNER_TEMP 'request-configuration-public-tests.jsonl') -Encoding utf8NoBOM -ErrorAction Stop if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - go test -p 2 ./pkg/custom -count=1 -timeout=2m -v -json -run '^(TestRootRequestFlagsPreserveOwnershipAndMetadata|TestRootRequestFlagsPreserveMTLSPlacement|TestRootRequestFlagsRetainSourcesAliasesAndValidation|TestMTLSClientFlagEnvironmentDoesNotConsumeExplicitValue)$' | + go test -p 2 ./internal/app -count=1 -timeout=2m -v -json -run '^(TestRootRequestFlagsPreserveOwnershipAndMetadata|TestRootRequestFlagsPreserveMTLSPlacement|TestRootRequestFlagsRetainSourcesAliasesAndValidation|TestMTLSClientFlagEnvironmentDoesNotConsumeExplicitValue)$' | Tee-Object -FilePath (Join-Path $env:RUNNER_TEMP 'request-configuration-helper-tests.jsonl') -Encoding utf8NoBOM -ErrorAction Stop exit $LASTEXITCODE - name: Test Windows native shell and download contracts @@ -580,7 +580,7 @@ jobs: go test -p 2 ./cmd/openai -count=1 -timeout=3m -v -json -run '^TestMainShellWindowsNative$' | Tee-Object -FilePath (Join-Path $env:RUNNER_TEMP 'windows-native-shell-tests.jsonl') -Encoding utf8NoBOM -ErrorAction Stop if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - go test -p 2 ./pkg/custom -count=1 -timeout=2m -v -json -run '^TestWindowsDownload(AutomaticConsole|PrivateCreation|PreservesExistingDACL)$' | + go test -p 2 ./internal/app -count=1 -timeout=2m -v -json -run '^TestWindowsDownload(AutomaticConsole|PrivateCreation|PreservesExistingDACL)$' | Tee-Object -FilePath (Join-Path $env:RUNNER_TEMP 'windows-download-tests.jsonl') -Encoding utf8NoBOM -ErrorAction Stop if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } go test -p 2 ./internal/output -count=1 -timeout=2m -v -json -run '^TestWindowsDownloadPrivatePublication$' | @@ -1415,7 +1415,7 @@ jobs: suites["request-configuration-helper-tests.jsonl"] = set().union(*( required_cases(root, children) for root, children in request_helper_cases.items() )) - strict_packages["request-configuration-helper-tests.jsonl"] = "github.com/openai/openai-cli/pkg/custom" + strict_packages["request-configuration-helper-tests.jsonl"] = "github.com/openai/openai-cli/internal/app" if os.environ["RUNNER_OS"] == "Windows": request_shell_root = "TestMainShellWindowsNative" request_shell_cases = [ @@ -1452,7 +1452,7 @@ jobs: suites["windows-download-tests.jsonl"] = set().union(*( required_cases(root, children) for root, children in windows_download_cases.items() )) - strict_packages["windows-download-tests.jsonl"] = "github.com/openai/openai-cli/pkg/custom" + strict_packages["windows-download-tests.jsonl"] = "github.com/openai/openai-cli/internal/app" suites["windows-download-publication-tests.jsonl"] = required_cases( "TestWindowsDownloadPrivatePublication", ["hardlink", "exclusive_copy"] ) @@ -1674,7 +1674,7 @@ jobs: artifact: wsl-pager - name: Command contracts on Ubuntu WSL 1 arguments: --command-contracts - package: ./pkg/custom + package: ./internal/app artifact: wsl-command-contracts steps: - name: Reserve WSL cleanup time diff --git a/AGENTS.md b/AGENTS.md index 00a8b5c8..a97fdda8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,8 @@ it. Fix recurring generated defects in the authoritative schema or generator when available instead of introducing broad manual changes. Handwritten CLI features are moving into `internal/commands//` (Codex, -tokenizer, manpages, files, batches, models, admin, audio, and images are there now); remaining behavior lives in `pkg/custom/`. API response selection +tokenizer, manpages, files, batches, models, admin, audio, and images are there now); application assembly and request/output policy live in `internal/app/`. +`pkg/custom/` is only a temporary adapter for generated callers. API response selection and reshaping live in `internal/output/transform/`. Use cohesive `internal/` helpers for shared implementation, including text rendering and terminal escaping. `internal/commands/` owns command-tree routing, compatibility aliases, shortcuts, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22a3b462..0753e0e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,8 +75,10 @@ below describes the current layout as the migration proceeds. guidance and request-input help annotations. It uses declared command/flag names and escaped local invocation metadata without echoing private operands or executing suggested corrections. -- `pkg/custom/` contains remaining handwritten CLI commands, defaults, output-format - policy, error handling, and presentation orchestration. +- `internal/app/` owns command assembly, defaults, request/output policy, error + handling, and presentation orchestration. +- `pkg/custom/` is a temporary adapter for generated callers; it contains no + independent behavior and will disappear when Castiron updates its imports. - `internal/request/` owns root request inheritance, literal header and mTLS flag definitions, value-safe validation, mTLS transport construction, and client metadata setup and scalar-upload inspection/replay. Application assembly retains registration order and the local @@ -97,8 +99,8 @@ below describes the current layout as the migration proceeds. - `internal/testutil/console/` contains shared synthetic Windows console records and observer fixtures used only by native feature tests. - `internal/app/` owns invocation, request-environment scope, completion setup, - output policy, help assembly, and exit-status selection. Command assembly still uses the - existing generated tree during the layout migration. + output policy, help assembly, and exit-status selection. `cmd/openai` passes in the + generated tree; application code does not import generated commands. - `api_reference/openapi.transformed.yml` contains the OpenAPI specification used by the mock server. - `scripts/` contains the local development, build, and test commands. @@ -113,7 +115,7 @@ recurring generated problems in the authoritative schema or generator when available. Handwritten features are moving into `internal/commands//`; Codex, -tokenizer, manpages, files, batches, models, admin, audio, and images are there now. Remaining behavior belongs in `pkg/custom/`, +tokenizer, manpages, files, batches, models, admin, audio, and images are there now. Application policy belongs in `internal/app/`, `internal/output/transform/`, and focused `internal/` helpers. Keep `cmd/openai/` focused on startup and wiring. Generated code calls handwritten request/output helpers through a generated runtime bridge. diff --git a/cmd/openai/error_coverage_test.go b/cmd/openai/error_coverage_test.go index 6fc4edc9..565b13cf 100644 --- a/cmd/openai/error_coverage_test.go +++ b/cmd/openai/error_coverage_test.go @@ -23,7 +23,6 @@ import ( "github.com/openai/openai-cli/generated/commands" "github.com/openai/openai-cli/internal/app" "github.com/openai/openai-cli/internal/requestflag" - "github.com/openai/openai-cli/pkg/custom" "github.com/urfave/cli/v3" ) @@ -310,11 +309,11 @@ func TestMainErrorCoverageManifestProcess(t *testing.T) { return } root := commands.Command - root.Flags = append(root.Flags, custom.NewRequestHeaderFlag()) + root.Flags = append(root.Flags, app.NewRequestHeaderFlag()) if _, _, err := app.ConfigureHelp(root, []string{"openai", "--help"}); err != nil { t.Fatal(err) } - custom.ConfigureCommandErrors(root) + app.ConfigureCommandErrors(root) manifest, err := buildErrorCoverageManifest(root) if err != nil { t.Fatal(err) diff --git a/cmd/openai/kitty_output_helper_test.go b/cmd/openai/kitty_output_helper_test.go index c755293e..4e1ea3e9 100644 --- a/cmd/openai/kitty_output_helper_test.go +++ b/cmd/openai/kitty_output_helper_test.go @@ -7,7 +7,7 @@ import ( "github.com/openai/openai-cli/generated/commands" batchcommands "github.com/openai/openai-cli/internal/commands/batches" "github.com/openai/openai-cli/internal/terminalimage" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" ) // Match executable dispatch when tests launch private helpers. @@ -39,6 +39,6 @@ func TestMain(m *testing.M) { } os.Exit(0) } - custom.ConfigureCommand(commands.Command) + appconfig.ConfigureCommand(commands.Command) os.Exit(runShellTests(m)) } diff --git a/cmd/openai/main.go b/cmd/openai/main.go index 583cf997..857dda63 100644 --- a/cmd/openai/main.go +++ b/cmd/openai/main.go @@ -4,11 +4,12 @@ import ( "context" "os" + "github.com/openai/openai-cli/generated/commands" "github.com/openai/openai-cli/internal/app" ) func main() { - if code := app.Run(context.Background(), os.Args); code != 0 { + if code := app.Run(context.Background(), os.Args, commands.Command); code != 0 { os.Exit(code) } } diff --git a/cmd/openai/main_readable_errors_test.go b/cmd/openai/main_readable_errors_test.go index 5e8abc97..5f83b8e7 100644 --- a/cmd/openai/main_readable_errors_test.go +++ b/cmd/openai/main_readable_errors_test.go @@ -15,7 +15,7 @@ import ( "time" "github.com/openai/openai-cli/generated/commands" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -220,7 +220,7 @@ func TestMainWrappedExitCodeProcess(t *testing.T) { }, }}}}, } - custom.ConfigureCommand(commands.Command) + appconfig.ConfigureCommand(commands.Command) os.Args = []string{"openai", "synthetic", "fail"} main() os.Exit(0) diff --git a/docs/architecture/repository-layout.md b/docs/architecture/repository-layout.md index 5737f28a..d0f22436 100644 --- a/docs/architecture/repository-layout.md +++ b/docs/architecture/repository-layout.md @@ -360,3 +360,11 @@ Optional first-run shell setup admission lives in `internal/app/`, alongside invocation. Its existing terminal checks, synchronous audio command traversal, argument copy, single-worker limit, and 250 ms foreground wait are unchanged. Image shell installation mechanics remain in `internal/commands/images/`. + +Application assembly, feature request/output policy, errors, and presentation +orchestration now live in `internal/app/`, with their tests. `cmd/openai` passes +the generated command tree to `app.Run`, so application policy never imports +generated commands. Shared request/output implementations keep their existing +packages. `pkg/custom/runtime.go` contains only the adapter required by current +Castiron-generated callers; remove it through the producer migration, rather than +editing generated files. No external Go import compatibility is maintained. diff --git a/docs/architecture/security-model.md b/docs/architecture/security-model.md index 5e9256f9..bfe417bd 100644 --- a/docs/architecture/security-model.md +++ b/docs/architecture/security-model.md @@ -19,7 +19,8 @@ tree, validates an environment-selected base URL, and formats API errors Endpoint commands and request tests under `generated/commands/` are generated from the checked-in OpenAPI snapshot. Shared request plumbing remains -handwritten in `pkg/custom/`, including `flagoptions.go` and `mtls.go`. +handwritten in `internal/app/` and `internal/request/`, including request +policy wiring and mTLS transport construction. That split matters for maintenance, but both generated and handwritten tracked source are repository code once reviewed and executed @@ -29,8 +30,8 @@ code once reviewed and executed | Component | Responsibility | Evidence | | --- | --- | --- | | `cmd/openai`, `internal/app`, and generated `generated/commands` commands | Parse CLI arguments, construct SDK clients, and invoke API resources, including ordinary and organization-administration operations. | [internal/app/run.go](../../internal/app/run.go), [generated/commands/cmd.go:82-101](../../generated/commands/cmd.go#L82-L101), [generated/commands/cmd.go:103-150](../../generated/commands/cmd.go#L103-L150) | -| `internal/requestflag`, `internal/request`, and `pkg/custom/flagoptions.go` | Map flags and piped YAML/JSON into path, query, header, and body values; expand file references; serialize requests. | [internal/requestflag/requestflag.go:61-80](../../internal/requestflag/requestflag.go#L61-L80), [internal/request/flag_options.go](../../internal/request/flag_options.go), [pkg/custom/flagoptions.go](../../pkg/custom/flagoptions.go), [internal/request/piped_input.go](../../internal/request/piped_input.go), [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/encoding.go](../../internal/request/encoding.go) | -| `pkg/custom/mtls.go` | Read a caller-selected client certificate/key pair and construct the custom mTLS transport. | [pkg/custom/mtls.go:29-45](../../pkg/custom/mtls.go#L29-L45), [pkg/custom/mtls.go:78-152](../../pkg/custom/mtls.go#L78-L152) | +| `internal/requestflag`, `internal/request`, and `internal/app/flagoptions.go` | Map flags and piped YAML/JSON into path, query, header, and body values; expand file references; serialize requests. | [internal/requestflag/requestflag.go:61-80](../../internal/requestflag/requestflag.go#L61-L80), [internal/request/flag_options.go](../../internal/request/flag_options.go), [internal/app/flagoptions.go](../../internal/app/flagoptions.go), [internal/request/piped_input.go](../../internal/request/piped_input.go), [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/encoding.go](../../internal/request/encoding.go) | +| `internal/request/mtls.go` | Read a caller-selected client certificate/key pair and construct the custom mTLS transport. | [internal/request/mtls.go](../../internal/request/mtls.go), [internal/request/mtls.go](../../internal/request/mtls.go) | | `internal/debugmiddleware` and output code | Log redacted HTTP metadata when requested and display or write API responses. | [internal/debugmiddleware/debug_middleware.go:43-63](../../internal/debugmiddleware/debug_middleware.go#L43-L63), [internal/output/format.go](../../internal/output/format.go), [internal/output/iterator.go](../../internal/output/iterator.go), [generated/commands/response.go:429-481](../../generated/commands/response.go#L429-L481) | | Development and release tooling | Run local tests/mock tooling and build, validate, sign, attest, and publish official release artifacts. | [scripts/test:28-61](../../scripts/test#L28-L61), [docs/releasing.md:3-29](../releasing.md#L3-L29) | @@ -49,8 +50,8 @@ flowchart LR | --- | --- | --- | --- | --- | --- | --- | | Normal CLI invocation | API/admin credentials and tenant selectors | Global flags may source `OPENAI_API_KEY`, `OPENAI_ADMIN_KEY`, `OPENAI_ORG_ID`, `OPENAI_PROJECT_ID`, and `OPENAI_WEBHOOK_SECRET`. | Process memory and outbound SDK request options; never repository files. | Invoking process and selected API backend. | Caller chooses credentials; server remains authoritative for authentication, authorization, and tenancy. | [generated/commands/cmd.go:82-101](../../generated/commands/cmd.go#L82-L101), [internal/request/default_options.go](../../internal/request/default_options.go) | | Normal CLI invocation | Request destination | `--base-url` overrides the default; `OPENAI_BASE_URL` is validated at startup. | Default SDK HTTPS endpoint or explicit HTTPS backend; HTTP is allowed only for exact `localhost` or literal loopback IP addresses. | Selected network origin and any configured proxy. | CLI parses the effective URL and requires HTTPS outside the loopback exception before request setup; `openai-go` owns normal transport, redirects, and auth-header behavior. | [generated/commands/cmd.go:39-45](../../generated/commands/cmd.go#L39-L45), [internal/app/run.go](../../internal/app/run.go), [internal/request/default_options.go](../../internal/request/default_options.go); default SDK behavior is an external dependency. | -| mTLS invocation | Client certificate and private key | Dedicated flags or `OPENAI_MTLS_CLIENT_CERT_FILE` / `OPENAI_MTLS_CLIENT_KEY_FILE`; both are required together. | Caller-selected PEM files read locally; key bytes are cleared after use. | CLI process and same-origin HTTPS endpoint only. | Explicit HTTPS base URL, matching pair, HTTPS-proxy rejection, and same-origin redirect limit. | [pkg/custom/mtls.go:17-45](../../pkg/custom/mtls.go#L17-L45), [pkg/custom/mtls.go:78-152](../../pkg/custom/mtls.go#L78-L152) | -| Piped request input | YAML/JSON and referenced local files | Explicit flags take precedence; unset request fields can be populated from stdin. `OPENAI_UNTRUSTED_STDIN` preserves provenance. | Parsed in process; trusted `@` references may read files, while untrusted-stdin values remain literal. | CLI process, local files explicitly referenced under trusted mode, selected API backend. | Single-consumer stdin, provenance-marked literal values, and rejection of stdin-derived binary file paths in untrusted mode. | [internal/request/flag_options.go](../../internal/request/flag_options.go), [pkg/custom/flagoptions.go](../../pkg/custom/flagoptions.go), [internal/request/piped_input.go](../../internal/request/piped_input.go), [internal/request/stdin_policy.go](../../internal/request/stdin_policy.go), [internal/request/stdin_consumers.go](../../internal/request/stdin_consumers.go), [internal/request/input_values.go](../../internal/request/input_values.go), [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/stdin_reader.go](../../internal/request/stdin_reader.go) | +| mTLS invocation | Client certificate and private key | Dedicated flags or `OPENAI_MTLS_CLIENT_CERT_FILE` / `OPENAI_MTLS_CLIENT_KEY_FILE`; both are required together. | Caller-selected PEM files read locally; key bytes are cleared after use. | CLI process and same-origin HTTPS endpoint only. | Explicit HTTPS base URL, matching pair, HTTPS-proxy rejection, and same-origin redirect limit. | [internal/request/mtls.go](../../internal/request/mtls.go), [internal/request/mtls.go](../../internal/request/mtls.go) | +| Piped request input | YAML/JSON and referenced local files | Explicit flags take precedence; unset request fields can be populated from stdin. `OPENAI_UNTRUSTED_STDIN` preserves provenance. | Parsed in process; trusted `@` references may read files, while untrusted-stdin values remain literal. | CLI process, local files explicitly referenced under trusted mode, selected API backend. | Single-consumer stdin, provenance-marked literal values, and rejection of stdin-derived binary file paths in untrusted mode. | [internal/request/flag_options.go](../../internal/request/flag_options.go), [internal/app/flagoptions.go](../../internal/app/flagoptions.go), [internal/request/piped_input.go](../../internal/request/piped_input.go), [internal/request/stdin_policy.go](../../internal/request/stdin_policy.go), [internal/request/stdin_consumers.go](../../internal/request/stdin_consumers.go), [internal/request/input_values.go](../../internal/request/input_values.go), [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/stdin_reader.go](../../internal/request/stdin_reader.go) | | Integration tests | Local mock executable and OpenAPI snapshot | Checked-in spec is hash-checked by default; an explicit local path or URL is caller-selected and its content is untrusted. | Pinned fork source and checksum-verified Deno runtime; mock binds `127.0.0.1:4010`. | Local test process and any caller-selected specification origin or referenced resource. | Full Git commit, pristine source checks, HTTPS-only runtime download and redirects, runtime SHA-256 verification, and frozen Deno dependencies authenticate tooling; they do not authenticate caller-selected specification content. | [scripts/mock](../../scripts/mock), [scripts/steady/install](../../scripts/steady/install), [scripts/steady/settings](../../scripts/steady/settings) | | Ordinary test/build pull-request CI | Candidate checkout execution | A separate preparation job checks out the candidate as data; CI execution jobs restore its same-run artifact and run tracked scripts/tests. | Preparation has read-only repository credentials and executes only reviewed workflow instructions and trusted-main tooling. Candidate execution has `permissions: {}` and no repository-read or protected credentials. | Source preparation, then candidate runners and configured test dependencies. | Separate runners and immutable same-run source artifacts; no candidate helper or local action runs in preparation. | [.github/workflows/ci.yml:1-27](../../.github/workflows/ci.yml#L1-L27), [.github/workflows/ci.yml:110-127](../../.github/workflows/ci.yml#L110-L127) | | CodeQL analysis and publication | Candidate analysis; trusted result publication | `.github/workflows/codeql.yml` analyzes the candidate with autobuild and stages SARIF. `codeql-upload.yml` runs separately on `workflow_run` using trusted main tooling. | Analysis has `permissions: {}` and consumes prepared same-run source artifacts. Only the trusted publisher has `security-events: write` and `statuses: write`; neither job receives release/signing credentials. | Candidate runner writes untrusted report artifacts; trusted publisher submits them to GitHub code scanning. | Publisher executes no candidate code, reads fixed ZIP members as data, verifies the source run/attempt and current GitHub SHA/ref, and fixes the tool/language categories. This confines publication authority; it does not attest candidate report correctness. | [.github/workflows/codeql.yml](../../.github/workflows/codeql.yml), [.github/workflows/codeql-upload.yml](../../.github/workflows/codeql-upload.yml), [.github/scripts/codeql_upload.py](../../.github/scripts/codeql_upload.py) | @@ -84,7 +85,7 @@ essential. [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/file_source.go](../../internal/request/file_source.go)). - mTLS credentials must be presented only to the explicit same-origin HTTPS endpoint, not an HTTPS proxy or cross-origin redirect - ([pkg/custom/mtls.go:78-152](../../pkg/custom/mtls.go#L78-L152)). + ([internal/request/mtls.go](../../internal/request/mtls.go)). - Official release integrity, signing/notarization credentials, publication tokens, and provenance attestations must remain isolated from untrusted pull-request execution and from unverified release inputs @@ -174,12 +175,12 @@ The scenarios below are hypotheses for review, not validated findings. | Priority | Scenario and capability gain | Prerequisites | Impact | Existing controls | Mitigation | Evidence | | --- | --- | --- | --- | --- | --- | --- | -| High | A lower-trust wrapper input influences the request destination while credentials are attached, gaining credential exposure to an unintended backend. | A wrapper forwards attacker-controlled `--base-url` or `OPENAI_BASE_URL`; ordinary operator selection alone is not a bypass. | API/admin credential disclosure or misdirected sensitive requests. | Base URLs require HTTPS except for explicit localhost/loopback HTTP; mTLS additionally requires explicit HTTPS and same-origin redirects. | Wrappers should allowlist expected HTTPS origins; preserve SDK redirect/header protections and mTLS origin controls. | [generated/commands/cmd.go:39-45](../../generated/commands/cmd.go#L39-L45), [internal/app/run.go](../../internal/app/run.go), [pkg/custom/mtls.go:78-152](../../pkg/custom/mtls.go#L78-L152) | +| High | A lower-trust wrapper input influences the request destination while credentials are attached, gaining credential exposure to an unintended backend. | A wrapper forwards attacker-controlled `--base-url` or `OPENAI_BASE_URL`; ordinary operator selection alone is not a bypass. | API/admin credential disclosure or misdirected sensitive requests. | Base URLs require HTTPS except for explicit localhost/loopback HTTP; mTLS additionally requires explicit HTTPS and same-origin redirects. | Wrappers should allowlist expected HTTPS origins; preserve SDK redirect/header protections and mTLS origin controls. | [generated/commands/cmd.go:39-45](../../generated/commands/cmd.go#L39-L45), [internal/app/run.go](../../internal/app/run.go), [internal/request/mtls.go](../../internal/request/mtls.go) | | High | A lower-trust piped string crosses file-expansion logic and gains a local file read/upload. | Automation forwards untrusted YAML/JSON under trusted-stdin semantics and the process can read the target file. | Disclosure of local files to the selected API backend. | Escaped `\@` is literal; stdin is single-use; `OPENAI_UNTRUSTED_STDIN` preserves provenance and rejects stdin-derived binary paths. | Enable untrusted-stdin mode for lower-trust producers and keep explicit file flags operator-controlled. | [internal/request/file_expand.go](../../internal/request/file_expand.go), [internal/request/file_source.go](../../internal/request/file_source.go), [internal/request/stdin_policy.go](../../internal/request/stdin_policy.go), [internal/request/input_values.go](../../internal/request/input_values.go), [internal/request/stdin_reader.go](../../internal/request/stdin_reader.go) | | High | Candidate or dependency code crosses from a job without protected write/signing credentials into protected publish credentials or official artifacts. | A workflow change, mutable artifact, unverified dependency, or credential staging error defeats current separation. | Compromise of official binaries, signing/notarization material, publication tokens, or attestations. | Release-input generation without protected credentials, tag/SHA binding, fixed artifact verification, protected publish environment, verified GoReleaser, and isolated attestation job. | Preserve the separation and review workflow/dependency/release changes as security-sensitive. | [docs/releasing.md:3-29](../releasing.md#L3-L29), [.github/workflows/publish-release.yml:17-152](../../.github/workflows/publish-release.yml#L17-L152), [.github/workflows/publish-release.yml:247-273](../../.github/workflows/publish-release.yml#L247-L273) | | Medium | Hostile API/network output reaches terminal, pager, raw pipeline, debug/error logs, or an output path and gains unintended local effect or disclosure. | Response data or metadata reaches a sensitive sink; exploitability depends on selected output format and downstream consumer. | Terminal spoofing, downstream injection, overwrite, or sensitive diagnostic leakage. | Debug redacts known headers and response `Location`; the CLI uses structured rendering and explicit output modes. | Preserve sanitization, avoid shell evaluation downstream, and treat debug/raw output as sensitive. | [internal/debugmiddleware/debug_middleware.go:43-63](../../internal/debugmiddleware/debug_middleware.go#L43-L63), [internal/debugmiddleware/debug_middleware.go:91-132](../../internal/debugmiddleware/debug_middleware.go#L91-L132), [generated/commands/response.go:429-481](../../generated/commands/response.go#L429-L481) | -| Medium | Malformed or very large lower-trust YAML/JSON, file, or response data exhausts local resources or reaches parser edge cases. | A lower-trust producer controls input size/shape and a wrapper invokes the CLI with meaningful availability requirements. | Per-invocation denial of service or parser confusion. | Multipart bodies stream and close owned files; input provenance and single-use stdin avoid some ambiguous reads. | Preserve streaming/cancellation and large-payload compatibility; do not add arbitrary rejection limits. | [internal/request/flag_options.go](../../internal/request/flag_options.go), [pkg/custom/flagoptions.go](../../pkg/custom/flagoptions.go), [internal/request/multipart_body.go](../../internal/request/multipart_body.go), [AGENTS.md:100-119](../../AGENTS.md#L100-L119) | -| Low | A user already controlling `PATH`, `PAGER`, proxy/CA settings, shell, or local checkout chooses a malicious executable or endpoint. | Prior local operator/environment control. | Effects within authority the actor already possessed. | Pager execution avoids a shell; mTLS rejects HTTPS proxies. | Treat as deployment hardening unless a separate lower-trust input crosses into that configuration. | [internal/output/pager.go](../../internal/output/pager.go), [internal/output/pager_unix.go](../../internal/output/pager_unix.go), [pkg/custom/mtls.go:114-123](../../pkg/custom/mtls.go#L114-L123) | +| Medium | Malformed or very large lower-trust YAML/JSON, file, or response data exhausts local resources or reaches parser edge cases. | A lower-trust producer controls input size/shape and a wrapper invokes the CLI with meaningful availability requirements. | Per-invocation denial of service or parser confusion. | Multipart bodies stream and close owned files; input provenance and single-use stdin avoid some ambiguous reads. | Preserve streaming/cancellation and large-payload compatibility; do not add arbitrary rejection limits. | [internal/request/flag_options.go](../../internal/request/flag_options.go), [internal/app/flagoptions.go](../../internal/app/flagoptions.go), [internal/request/multipart_body.go](../../internal/request/multipart_body.go), [AGENTS.md:100-119](../../AGENTS.md#L100-L119) | +| Low | A user already controlling `PATH`, `PAGER`, proxy/CA settings, shell, or local checkout chooses a malicious executable or endpoint. | Prior local operator/environment control. | Effects within authority the actor already possessed. | Pager execution avoids a shell; mTLS rejects HTTPS proxies. | Treat as deployment hardening unless a separate lower-trust input crosses into that configuration. | [internal/output/pager.go](../../internal/output/pager.go), [internal/output/pager_unix.go](../../internal/output/pager_unix.go), [internal/request/mtls.go](../../internal/request/mtls.go) | | Not a new capability by itself | A PR author changes and ordinary test/build CI executes a checked-in test, executable fixture, build script, generated source, or mock script. | The changed tracked file is intentionally executed or imported as repository code in a candidate job without protected credentials. | No privilege gain beyond candidate repository-code execution. | Ordinary CI execution has no repository permissions; isolated source preparation uses read-only credentials without executing candidate code. This does not cover fixture bytes crossing a parser, the trusted CodeQL publisher's `security-events: write` boundary, or code reaching protected credentials. | Do not report solely on “PR can change executable checkout content”; instead trace an independently mutable lower-trust input or protected boundary crossing. | [.github/workflows/ci.yml:1-27](../../.github/workflows/ci.yml#L1-L27), [scripts/test:28-61](../../scripts/test#L28-L61), [.github/workflows/codeql.yml:7-47](../../.github/workflows/codeql.yml#L7-L47) | ## 4. Severity calibration diff --git a/docs/image-generation-saving.md b/docs/image-generation-saving.md index cde058b4..8c2a665d 100644 --- a/docs/image-generation-saving.md +++ b/docs/image-generation-saving.md @@ -304,9 +304,9 @@ An observer releases the final response only after both partial previews arrive. Build and run them without API credentials: ```sh -go test -c -o /tmp/image-progress-custom.test ./pkg/custom +go test -c -o /tmp/image-progress-images.test ./internal/commands/images go test -c -o /tmp/image-progress-main.test ./cmd/openai -python3 scripts/check-image-progress.py /tmp/image-progress-custom.test /tmp/image-progress-main.test /tmp/image-progress-evidence +python3 scripts/check-image-progress.py /tmp/image-progress-images.test /tmp/image-progress-main.test /tmp/image-progress-evidence ``` `scripts/demos/record-image-progress.sh` records a before/after comparison using diff --git a/pkg/custom/admin_credentials_test.go b/internal/app/admin_credentials_test.go similarity index 99% rename from pkg/custom/admin_credentials_test.go rename to internal/app/admin_credentials_test.go index 957cf887..6988da04 100644 --- a/pkg/custom/admin_credentials_test.go +++ b/internal/app/admin_credentials_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/admin_policy.go b/internal/app/admin_policy.go similarity index 97% rename from pkg/custom/admin_policy.go rename to internal/app/admin_policy.go index 5cc907e9..dd8519a3 100644 --- a/pkg/custom/admin_policy.go +++ b/internal/app/admin_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "github.com/openai/openai-cli/internal/commands/admin" diff --git a/pkg/custom/audio_policy.go b/internal/app/audio_policy.go similarity index 99% rename from pkg/custom/audio_policy.go rename to internal/app/audio_policy.go index 51b7e362..19afc08f 100644 --- a/pkg/custom/audio_policy.go +++ b/internal/app/audio_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/base_url_security_test.go b/internal/app/base_url_security_test.go similarity index 98% rename from pkg/custom/base_url_security_test.go rename to internal/app/base_url_security_test.go index d51e9f29..e7991488 100644 --- a/pkg/custom/base_url_security_test.go +++ b/internal/app/base_url_security_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/batch_policy.go b/internal/app/batch_policy.go similarity index 99% rename from pkg/custom/batch_policy.go rename to internal/app/batch_policy.go index f0a83537..9afbe6b0 100644 --- a/pkg/custom/batch_policy.go +++ b/internal/app/batch_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/batch_transport_test.go b/internal/app/batch_transport_test.go similarity index 99% rename from pkg/custom/batch_transport_test.go rename to internal/app/batch_transport_test.go index 13590bca..626424bb 100644 --- a/pkg/custom/batch_transport_test.go +++ b/internal/app/batch_transport_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/binaryresponse_automatic_test.go b/internal/app/binaryresponse_automatic_test.go similarity index 99% rename from pkg/custom/binaryresponse_automatic_test.go rename to internal/app/binaryresponse_automatic_test.go index d56e1439..5229117b 100644 --- a/pkg/custom/binaryresponse_automatic_test.go +++ b/internal/app/binaryresponse_automatic_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/binaryresponse_test.go b/internal/app/binaryresponse_test.go similarity index 99% rename from pkg/custom/binaryresponse_test.go rename to internal/app/binaryresponse_test.go index c82b217d..3f51291d 100644 --- a/pkg/custom/binaryresponse_test.go +++ b/internal/app/binaryresponse_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/certificate_compatibility.go b/internal/app/certificate_compatibility.go similarity index 98% rename from pkg/custom/certificate_compatibility.go rename to internal/app/certificate_compatibility.go index ca45ba9c..55a92b20 100644 --- a/pkg/custom/certificate_compatibility.go +++ b/internal/app/certificate_compatibility.go @@ -1,4 +1,4 @@ -package custom +package app import ( "slices" diff --git a/pkg/custom/certificate_compatibility_test.go b/internal/app/certificate_compatibility_test.go similarity index 98% rename from pkg/custom/certificate_compatibility_test.go rename to internal/app/certificate_compatibility_test.go index fb609eff..dea25cb0 100644 --- a/pkg/custom/certificate_compatibility_test.go +++ b/internal/app/certificate_compatibility_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "testing" diff --git a/pkg/custom/cmdutil.go b/internal/app/cmdutil.go similarity index 99% rename from pkg/custom/cmdutil.go rename to internal/app/cmdutil.go index 7de6d2ac..9e7d68e5 100644 --- a/pkg/custom/cmdutil.go +++ b/internal/app/cmdutil.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bufio" diff --git a/pkg/custom/cmdutil_format_test.go b/internal/app/cmdutil_format_test.go similarity index 99% rename from pkg/custom/cmdutil_format_test.go rename to internal/app/cmdutil_format_test.go index 5278f354..521412bd 100644 --- a/pkg/custom/cmdutil_format_test.go +++ b/internal/app/cmdutil_format_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/cmdutil_output_errors_test.go b/internal/app/cmdutil_output_errors_test.go similarity index 99% rename from pkg/custom/cmdutil_output_errors_test.go rename to internal/app/cmdutil_output_errors_test.go index d2f14333..81e9b893 100644 --- a/pkg/custom/cmdutil_output_errors_test.go +++ b/internal/app/cmdutil_output_errors_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "errors" diff --git a/pkg/custom/cmdutil_output_errors_unix_test.go b/internal/app/cmdutil_output_errors_unix_test.go similarity index 99% rename from pkg/custom/cmdutil_output_errors_unix_test.go rename to internal/app/cmdutil_output_errors_unix_test.go index b655e76b..a679ba68 100644 --- a/pkg/custom/cmdutil_output_errors_unix_test.go +++ b/internal/app/cmdutil_output_errors_unix_test.go @@ -1,6 +1,6 @@ //go:build !windows -package custom +package app import ( "fmt" diff --git a/pkg/custom/cmdutil_pretty_color_test.go b/internal/app/cmdutil_pretty_color_test.go similarity index 99% rename from pkg/custom/cmdutil_pretty_color_test.go rename to internal/app/cmdutil_pretty_color_test.go index aacae8ff..02faa95c 100644 --- a/pkg/custom/cmdutil_pretty_color_test.go +++ b/internal/app/cmdutil_pretty_color_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/cmdutil_test.go b/internal/app/cmdutil_test.go similarity index 99% rename from pkg/custom/cmdutil_test.go rename to internal/app/cmdutil_test.go index 68db65a2..9c0e9b6c 100644 --- a/pkg/custom/cmdutil_test.go +++ b/internal/app/cmdutil_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/command.go b/internal/app/command.go similarity index 99% rename from pkg/custom/command.go rename to internal/app/command.go index 45bff3d0..7daa34b8 100644 --- a/pkg/custom/command.go +++ b/internal/app/command.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/command_error.go b/internal/app/command_error.go similarity index 99% rename from pkg/custom/command_error.go rename to internal/app/command_error.go index 438b5e7a..1ff6db55 100644 --- a/pkg/custom/command_error.go +++ b/internal/app/command_error.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/command_error_test.go b/internal/app/command_error_test.go similarity index 99% rename from pkg/custom/command_error_test.go rename to internal/app/command_error_test.go index 53b5712e..bf4940e2 100644 --- a/pkg/custom/command_error_test.go +++ b/internal/app/command_error_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/command_test.go b/internal/app/command_test.go similarity index 99% rename from pkg/custom/command_test.go rename to internal/app/command_test.go index d89ad812..f52ce667 100644 --- a/pkg/custom/command_test.go +++ b/internal/app/command_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/deterministic_stream_cleanup_test.go b/internal/app/deterministic_stream_cleanup_test.go similarity index 99% rename from pkg/custom/deterministic_stream_cleanup_test.go rename to internal/app/deterministic_stream_cleanup_test.go index 8d774c91..5a382109 100644 --- a/pkg/custom/deterministic_stream_cleanup_test.go +++ b/internal/app/deterministic_stream_cleanup_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/download_file_windows_test.go b/internal/app/download_file_windows_test.go similarity index 99% rename from pkg/custom/download_file_windows_test.go rename to internal/app/download_file_windows_test.go index b8a5db9a..d4f5d0d6 100644 --- a/pkg/custom/download_file_windows_test.go +++ b/internal/app/download_file_windows_test.go @@ -1,6 +1,6 @@ //go:build windows -package custom +package app import ( "bytes" diff --git a/pkg/custom/download_filename_test.go b/internal/app/download_filename_test.go similarity index 99% rename from pkg/custom/download_filename_test.go rename to internal/app/download_filename_test.go index 3aa26284..af10dd82 100644 --- a/pkg/custom/download_filename_test.go +++ b/internal/app/download_filename_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "fmt" diff --git a/pkg/custom/download_terminal_test.go b/internal/app/download_terminal_test.go similarity index 99% rename from pkg/custom/download_terminal_test.go rename to internal/app/download_terminal_test.go index 35948bd0..57231a0d 100644 --- a/pkg/custom/download_terminal_test.go +++ b/internal/app/download_terminal_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/finite_json_list.go b/internal/app/finite_json_list.go similarity index 98% rename from pkg/custom/finite_json_list.go rename to internal/app/finite_json_list.go index 96e65d41..b0e1237f 100644 --- a/pkg/custom/finite_json_list.go +++ b/internal/app/finite_json_list.go @@ -1,4 +1,4 @@ -package custom +package app import ( "errors" diff --git a/pkg/custom/finite_json_list_test.go b/internal/app/finite_json_list_test.go similarity index 99% rename from pkg/custom/finite_json_list_test.go rename to internal/app/finite_json_list_test.go index 7a1c89fd..630d1b63 100644 --- a/pkg/custom/finite_json_list_test.go +++ b/internal/app/finite_json_list_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/flagoptions.go b/internal/app/flagoptions.go similarity index 99% rename from pkg/custom/flagoptions.go rename to internal/app/flagoptions.go index b6521b4f..6086ceb4 100644 --- a/pkg/custom/flagoptions.go +++ b/internal/app/flagoptions.go @@ -1,4 +1,4 @@ -package custom +package app import ( "io" diff --git a/internal/app/help_test.go b/internal/app/help_test.go index 9ac8f6d0..b28e0567 100644 --- a/internal/app/help_test.go +++ b/internal/app/help_test.go @@ -11,7 +11,6 @@ import ( imagecommands "github.com/openai/openai-cli/internal/commands/images" requestconfig "github.com/openai/openai-cli/internal/request" "github.com/openai/openai-cli/internal/requestflag" - "github.com/openai/openai-cli/pkg/custom" "github.com/urfave/cli/v3" ) @@ -157,9 +156,9 @@ func TestConfigureHelpPreservesImageGuidance(t *testing.T) { {Name: "unrelated", Description: "Keep this description", CustomHelpTemplate: "Keep this template"}, }} root := &cli.Command{Name: "openai", Commands: []*cli.Command{images}} - imagecommands.ConfigureSaving(root, custom.ImageSavingOptions()) - imagecommands.RegisterPreview(root, custom.ImagePreviewOptions()) - imagecommands.RegisterModels(root, custom.ImageModelOptions()) + imagecommands.ConfigureSaving(root, ImageSavingOptions()) + imagecommands.RegisterPreview(root, ImagePreviewOptions()) + imagecommands.RegisterModels(root, ImageModelOptions()) wantGuidance := map[string][]string{ "generate": {"interactive terminal", "picker reopens", "Ctrl+C exits", "~/Downloads/gpt-images/", "--format json", "without --name or --output-dir", "depend on the model"}, "edit": {"Replace photo.png", "Keeps your original", "~/Downloads/gpt-images/", "existing folder", "--format json", "depend on the model"}, diff --git a/pkg/custom/images_policy.go b/internal/app/images_policy.go similarity index 99% rename from pkg/custom/images_policy.go rename to internal/app/images_policy.go index 8b83dd24..70fedeb8 100644 --- a/pkg/custom/images_policy.go +++ b/internal/app/images_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/input_file_errors.go b/internal/app/input_file_errors.go similarity index 98% rename from pkg/custom/input_file_errors.go rename to internal/app/input_file_errors.go index 201aa77e..8bcc9a5c 100644 --- a/pkg/custom/input_file_errors.go +++ b/internal/app/input_file_errors.go @@ -1,4 +1,4 @@ -package custom +package app import ( "errors" diff --git a/pkg/custom/input_file_errors_test.go b/internal/app/input_file_errors_test.go similarity index 99% rename from pkg/custom/input_file_errors_test.go rename to internal/app/input_file_errors_test.go index 8d4ebda7..c1885455 100644 --- a/pkg/custom/input_file_errors_test.go +++ b/internal/app/input_file_errors_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/iteratorlimit_test.go b/internal/app/iteratorlimit_test.go similarity index 98% rename from pkg/custom/iteratorlimit_test.go rename to internal/app/iteratorlimit_test.go index 4a457c57..2a5519e6 100644 --- a/pkg/custom/iteratorlimit_test.go +++ b/internal/app/iteratorlimit_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/kitty_output_helper_test.go b/internal/app/kitty_output_helper_test.go similarity index 97% rename from pkg/custom/kitty_output_helper_test.go rename to internal/app/kitty_output_helper_test.go index d8dbe516..79ebbf9d 100644 --- a/pkg/custom/kitty_output_helper_test.go +++ b/internal/app/kitty_output_helper_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "os" diff --git a/pkg/custom/list_navigation.go b/internal/app/list_navigation.go similarity index 99% rename from pkg/custom/list_navigation.go rename to internal/app/list_navigation.go index f339ca4d..81dd1467 100644 --- a/pkg/custom/list_navigation.go +++ b/internal/app/list_navigation.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/list_navigation_test.go b/internal/app/list_navigation_test.go similarity index 99% rename from pkg/custom/list_navigation_test.go rename to internal/app/list_navigation_test.go index 08fffcab..bc855842 100644 --- a/pkg/custom/list_navigation_test.go +++ b/internal/app/list_navigation_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/list_table_test.go b/internal/app/list_table_test.go similarity index 99% rename from pkg/custom/list_table_test.go rename to internal/app/list_table_test.go index 9ab7390a..ab6c0b24 100644 --- a/pkg/custom/list_table_test.go +++ b/internal/app/list_table_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/local_errors.go b/internal/app/local_errors.go similarity index 99% rename from pkg/custom/local_errors.go rename to internal/app/local_errors.go index 42b087d0..f9709340 100644 --- a/pkg/custom/local_errors.go +++ b/internal/app/local_errors.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/local_errors_test.go b/internal/app/local_errors_test.go similarity index 99% rename from pkg/custom/local_errors_test.go rename to internal/app/local_errors_test.go index 25a7b768..15a1e210 100644 --- a/pkg/custom/local_errors_test.go +++ b/internal/app/local_errors_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/local_utilities.go b/internal/app/local_utilities.go similarity index 97% rename from pkg/custom/local_utilities.go rename to internal/app/local_utilities.go index 678530d1..b450dd98 100644 --- a/pkg/custom/local_utilities.go +++ b/internal/app/local_utilities.go @@ -1,4 +1,4 @@ -package custom +package app import ( localcommands "github.com/openai/openai-cli/internal/commands" diff --git a/pkg/custom/local_utilities_test.go b/internal/app/local_utilities_test.go similarity index 99% rename from pkg/custom/local_utilities_test.go rename to internal/app/local_utilities_test.go index a5091e93..7b60d15b 100644 --- a/pkg/custom/local_utilities_test.go +++ b/internal/app/local_utilities_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/local_utility_output_test.go b/internal/app/local_utility_output_test.go similarity index 99% rename from pkg/custom/local_utility_output_test.go rename to internal/app/local_utility_output_test.go index 3f816c0e..d7637f50 100644 --- a/pkg/custom/local_utility_output_test.go +++ b/internal/app/local_utility_output_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/local_utility_routing_test.go b/internal/app/local_utility_routing_test.go similarity index 99% rename from pkg/custom/local_utility_routing_test.go rename to internal/app/local_utility_routing_test.go index a6e80ae9..715e46f6 100644 --- a/pkg/custom/local_utility_routing_test.go +++ b/internal/app/local_utility_routing_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/local_utility_test.go b/internal/app/local_utility_test.go similarity index 99% rename from pkg/custom/local_utility_test.go rename to internal/app/local_utility_test.go index 2db5b1fa..f6192c7a 100644 --- a/pkg/custom/local_utility_test.go +++ b/internal/app/local_utility_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_filter_test.go b/internal/app/models_list_filter_test.go similarity index 99% rename from pkg/custom/models_list_filter_test.go rename to internal/app/models_list_filter_test.go index 95d3b09f..e05a9bae 100644 --- a/pkg/custom/models_list_filter_test.go +++ b/internal/app/models_list_filter_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_interrupt_test.go b/internal/app/models_list_interrupt_test.go similarity index 99% rename from pkg/custom/models_list_interrupt_test.go rename to internal/app/models_list_interrupt_test.go index 299256c8..ff9ae9e8 100644 --- a/pkg/custom/models_list_interrupt_test.go +++ b/internal/app/models_list_interrupt_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/models_list_loading_test.go b/internal/app/models_list_loading_test.go similarity index 99% rename from pkg/custom/models_list_loading_test.go rename to internal/app/models_list_loading_test.go index 8ed905a6..397d6ffe 100644 --- a/pkg/custom/models_list_loading_test.go +++ b/internal/app/models_list_loading_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_options_test.go b/internal/app/models_list_options_test.go similarity index 99% rename from pkg/custom/models_list_options_test.go rename to internal/app/models_list_options_test.go index 4d045f33..7cfcb2e3 100644 --- a/pkg/custom/models_list_options_test.go +++ b/internal/app/models_list_options_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_print_test.go b/internal/app/models_list_print_test.go similarity index 98% rename from pkg/custom/models_list_print_test.go rename to internal/app/models_list_print_test.go index 6327bf24..ae711f33 100644 --- a/pkg/custom/models_list_print_test.go +++ b/internal/app/models_list_print_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_table_test.go b/internal/app/models_list_table_test.go similarity index 98% rename from pkg/custom/models_list_table_test.go rename to internal/app/models_list_table_test.go index 3c36d638..8d6ca616 100644 --- a/pkg/custom/models_list_table_test.go +++ b/internal/app/models_list_table_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/models_list_viewer_test.go b/internal/app/models_list_viewer_test.go similarity index 99% rename from pkg/custom/models_list_viewer_test.go rename to internal/app/models_list_viewer_test.go index 5b951b71..044ed61a 100644 --- a/pkg/custom/models_list_viewer_test.go +++ b/internal/app/models_list_viewer_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/models_list_viewport_test.go b/internal/app/models_list_viewport_test.go similarity index 99% rename from pkg/custom/models_list_viewport_test.go rename to internal/app/models_list_viewport_test.go index b3a48889..e13c5ae4 100644 --- a/pkg/custom/models_list_viewport_test.go +++ b/internal/app/models_list_viewport_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "strings" diff --git a/pkg/custom/models_policy.go b/internal/app/models_policy.go similarity index 99% rename from pkg/custom/models_policy.go rename to internal/app/models_policy.go index 4d139522..b068f941 100644 --- a/pkg/custom/models_policy.go +++ b/internal/app/models_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/mtls_client_flag_test.go b/internal/app/mtls_client_flag_test.go similarity index 98% rename from pkg/custom/mtls_client_flag_test.go rename to internal/app/mtls_client_flag_test.go index 1ba860f8..8fb5dbae 100644 --- a/pkg/custom/mtls_client_flag_test.go +++ b/internal/app/mtls_client_flag_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "testing" diff --git a/pkg/custom/mtls_test.go b/internal/app/mtls_test.go similarity index 99% rename from pkg/custom/mtls_test.go rename to internal/app/mtls_test.go index 9b47bba6..4d673fcb 100644 --- a/pkg/custom/mtls_test.go +++ b/internal/app/mtls_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/multipart_input_test.go b/internal/app/multipart_input_test.go similarity index 99% rename from pkg/custom/multipart_input_test.go rename to internal/app/multipart_input_test.go index e465013b..f0626979 100644 --- a/pkg/custom/multipart_input_test.go +++ b/internal/app/multipart_input_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "io" diff --git a/pkg/custom/multipart_test.go b/internal/app/multipart_test.go similarity index 93% rename from pkg/custom/multipart_test.go rename to internal/app/multipart_test.go index 928bdbbb..53fe3ca3 100644 --- a/pkg/custom/multipart_test.go +++ b/internal/app/multipart_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "path/filepath" diff --git a/pkg/custom/nested_error_guidance_test.go b/internal/app/nested_error_guidance_test.go similarity index 99% rename from pkg/custom/nested_error_guidance_test.go rename to internal/app/nested_error_guidance_test.go index e048deac..ae7cc650 100644 --- a/pkg/custom/nested_error_guidance_test.go +++ b/internal/app/nested_error_guidance_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/nullablefile_test.go b/internal/app/nullablefile_test.go similarity index 99% rename from pkg/custom/nullablefile_test.go rename to internal/app/nullablefile_test.go index 5e44c882..967e6bee 100644 --- a/pkg/custom/nullablefile_test.go +++ b/internal/app/nullablefile_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/output_invocation_test.go b/internal/app/output_invocation_test.go similarity index 99% rename from pkg/custom/output_invocation_test.go rename to internal/app/output_invocation_test.go index 6ca83472..43802d40 100644 --- a/pkg/custom/output_invocation_test.go +++ b/internal/app/output_invocation_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/output_invocation_unix_test.go b/internal/app/output_invocation_unix_test.go similarity index 98% rename from pkg/custom/output_invocation_unix_test.go rename to internal/app/output_invocation_unix_test.go index 8a8e9e3e..86ea2a99 100644 --- a/pkg/custom/output_invocation_unix_test.go +++ b/internal/app/output_invocation_unix_test.go @@ -1,6 +1,6 @@ //go:build !windows -package custom +package app import ( "context" diff --git a/pkg/custom/output_policy.go b/internal/app/output_policy.go similarity index 99% rename from pkg/custom/output_policy.go rename to internal/app/output_policy.go index a4c5b4d5..a29657b4 100644 --- a/pkg/custom/output_policy.go +++ b/internal/app/output_policy.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/output_policy_test.go b/internal/app/output_policy_test.go similarity index 99% rename from pkg/custom/output_policy_test.go rename to internal/app/output_policy_test.go index 75c3a6da..bf46d36b 100644 --- a/pkg/custom/output_policy_test.go +++ b/internal/app/output_policy_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/output_transform.go b/internal/app/output_transform.go similarity index 98% rename from pkg/custom/output_transform.go rename to internal/app/output_transform.go index bae68edd..57a3d6d3 100644 --- a/pkg/custom/output_transform.go +++ b/internal/app/output_transform.go @@ -1,4 +1,4 @@ -package custom +package app import ( "strings" diff --git a/pkg/custom/output_transform_test.go b/internal/app/output_transform_test.go similarity index 99% rename from pkg/custom/output_transform_test.go rename to internal/app/output_transform_test.go index 93146a47..a2d48fcb 100644 --- a/pkg/custom/output_transform_test.go +++ b/internal/app/output_transform_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/parser_error_guidance_test.go b/internal/app/parser_error_guidance_test.go similarity index 99% rename from pkg/custom/parser_error_guidance_test.go rename to internal/app/parser_error_guidance_test.go index 66ae8d1d..b5dd6d08 100644 --- a/pkg/custom/parser_error_guidance_test.go +++ b/internal/app/parser_error_guidance_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "errors" diff --git a/pkg/custom/project_api_key_update.go b/internal/app/project_api_key_update.go similarity index 98% rename from pkg/custom/project_api_key_update.go rename to internal/app/project_api_key_update.go index 3a31ad81..72a4cdd8 100644 --- a/pkg/custom/project_api_key_update.go +++ b/internal/app/project_api_key_update.go @@ -1,4 +1,4 @@ -package custom +package app import ( localcommands "github.com/openai/openai-cli/internal/commands" diff --git a/pkg/custom/readable_errors.go b/internal/app/readable_errors.go similarity index 99% rename from pkg/custom/readable_errors.go rename to internal/app/readable_errors.go index b74fe94f..baa55e11 100644 --- a/pkg/custom/readable_errors.go +++ b/internal/app/readable_errors.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/readable_errors_test.go b/internal/app/readable_errors_test.go similarity index 99% rename from pkg/custom/readable_errors_test.go rename to internal/app/readable_errors_test.go index 62635daa..923e4f7b 100644 --- a/pkg/custom/readable_errors_test.go +++ b/internal/app/readable_errors_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/readable_output.go b/internal/app/readable_output.go similarity index 99% rename from pkg/custom/readable_output.go rename to internal/app/readable_output.go index ce9b39aa..92239b50 100644 --- a/pkg/custom/readable_output.go +++ b/internal/app/readable_output.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/readable_output_test.go b/internal/app/readable_output_test.go similarity index 99% rename from pkg/custom/readable_output_test.go rename to internal/app/readable_output_test.go index e995633a..eb5cc7da 100644 --- a/pkg/custom/readable_output_test.go +++ b/internal/app/readable_output_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/readable_request_id_test.go b/internal/app/readable_request_id_test.go similarity index 99% rename from pkg/custom/readable_request_id_test.go rename to internal/app/readable_request_id_test.go index 07e6464d..e690af48 100644 --- a/pkg/custom/readable_request_id_test.go +++ b/internal/app/readable_request_id_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/readable_speech_test.go b/internal/app/readable_speech_test.go similarity index 99% rename from pkg/custom/readable_speech_test.go rename to internal/app/readable_speech_test.go index 852782e6..b2d373c5 100644 --- a/pkg/custom/readable_speech_test.go +++ b/internal/app/readable_speech_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/report_save_receipt.go b/internal/app/report_save_receipt.go similarity index 99% rename from pkg/custom/report_save_receipt.go rename to internal/app/report_save_receipt.go index babb1e47..f871f8b6 100644 --- a/pkg/custom/report_save_receipt.go +++ b/internal/app/report_save_receipt.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/report_save_receipt_test.go b/internal/app/report_save_receipt_test.go similarity index 99% rename from pkg/custom/report_save_receipt_test.go rename to internal/app/report_save_receipt_test.go index 5ca34487..be70d2ac 100644 --- a/pkg/custom/report_save_receipt_test.go +++ b/internal/app/report_save_receipt_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/requestheaders.go b/internal/app/requestheaders.go similarity index 96% rename from pkg/custom/requestheaders.go rename to internal/app/requestheaders.go index 9a84d96d..40ad8e36 100644 --- a/pkg/custom/requestheaders.go +++ b/internal/app/requestheaders.go @@ -1,4 +1,4 @@ -package custom +package app import ( requestconfig "github.com/openai/openai-cli/internal/request" diff --git a/pkg/custom/resource_summaries_test.go b/internal/app/resource_summaries_test.go similarity index 99% rename from pkg/custom/resource_summaries_test.go rename to internal/app/resource_summaries_test.go index b066cc1e..2d5ffe93 100644 --- a/pkg/custom/resource_summaries_test.go +++ b/internal/app/resource_summaries_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/root_request_flags_test.go b/internal/app/root_request_flags_test.go similarity index 99% rename from pkg/custom/root_request_flags_test.go rename to internal/app/root_request_flags_test.go index 808d4917..6f33b5ee 100644 --- a/pkg/custom/root_request_flags_test.go +++ b/internal/app/root_request_flags_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/internal/app/run.go b/internal/app/run.go index 7223c71a..0bf53fae 100644 --- a/internal/app/run.go +++ b/internal/app/run.go @@ -8,17 +8,15 @@ import ( "net/url" "os" - "github.com/openai/openai-cli/generated/commands" batchcommands "github.com/openai/openai-cli/internal/commands/batches" requestconfig "github.com/openai/openai-cli/internal/request" "github.com/openai/openai-cli/internal/terminalimage" - "github.com/openai/openai-cli/pkg/custom" "github.com/urfave/cli/v3" ) // Run executes one CLI process invocation and returns its exit status. -// The generated command tree is process-scoped; Run is called once per process. -func Run(ctx context.Context, argv []string) int { +// The caller supplies the process-scoped command tree; Run is called once per process. +func Run(ctx context.Context, argv []string, app *cli.Command) int { if handled, err := terminalimage.RunKittyOutputHelper(argv); handled { if err != nil { fmt.Fprintln(os.Stderr, "Image output helper failed.") @@ -38,8 +36,7 @@ func Run(ctx context.Context, argv []string) int { } return 0 } - app := commands.Command - custom.ConfigureCommand(app) + ConfigureCommand(app) app.Flags = append(app.Flags, requestconfig.NewRequestHeaderFlag()) completing := len(argv) > 1 && argv[1] == "__complete" @@ -48,11 +45,11 @@ func Run(ctx context.Context, argv []string) int { } args, _, err := ConfigureHelp(app, argv) - custom.ConfigureCommandErrors(app) + ConfigureCommandErrors(app) if err == nil { SetupImagePickerShellOnFirstRun(ctx, argv, app) - err = custom.RunWithOutputPolicy(ctx, app, func(ctx context.Context) error { + err = RunWithOutputPolicy(ctx, app, func(ctx context.Context) error { return runWithRequestConfiguration(ctx, app, args, completing) }) } @@ -64,8 +61,8 @@ func Run(ctx context.Context, argv []string) int { if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() } - if showErr := custom.ShowCommandError(app, err, os.Stderr); showErr != nil { - _ = custom.ShowCommandErrorFallback(err, showErr, os.Stderr) + if showErr := ShowCommandError(app, err, os.Stderr); showErr != nil { + _ = ShowCommandErrorFallback(err, showErr, os.Stderr) } return exitCode } @@ -92,7 +89,7 @@ func runWithRequestConfiguration(ctx context.Context, app *cli.Command, args []s if completing || command.Args().First() == "@completion" || command.Args().First() == "help" { return ctx, nil } - localUtility := custom.IsLocalUtilityCommand(command) + localUtility := IsLocalUtilityCommand(command) if explicitURL := command.Root().String("base-url"); !localUtility && explicitURL != "" { // The SDK parses environment defaults before applying explicit options. if _, parseErr := url.Parse(baseURL); baseURLSet && parseErr != nil { diff --git a/pkg/custom/save_download_symlink_test.go b/internal/app/save_download_symlink_test.go similarity index 99% rename from pkg/custom/save_download_symlink_test.go rename to internal/app/save_download_symlink_test.go index a5f27bd7..43fc78fc 100644 --- a/pkg/custom/save_download_symlink_test.go +++ b/internal/app/save_download_symlink_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/save_download_symlink_unix_test.go b/internal/app/save_download_symlink_unix_test.go similarity index 99% rename from pkg/custom/save_download_symlink_unix_test.go rename to internal/app/save_download_symlink_unix_test.go index 36c62eca..f8ca3859 100644 --- a/pkg/custom/save_download_symlink_unix_test.go +++ b/internal/app/save_download_symlink_unix_test.go @@ -1,6 +1,6 @@ //go:build !windows -package custom +package app import ( "bytes" diff --git a/pkg/custom/save_download_test.go b/internal/app/save_download_test.go similarity index 99% rename from pkg/custom/save_download_test.go rename to internal/app/save_download_test.go index 77f9a59a..e793fbbf 100644 --- a/pkg/custom/save_download_test.go +++ b/internal/app/save_download_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/shell_suggestions.go b/internal/app/shell_suggestions.go similarity index 99% rename from pkg/custom/shell_suggestions.go rename to internal/app/shell_suggestions.go index 2dda794f..71ea9730 100644 --- a/pkg/custom/shell_suggestions.go +++ b/internal/app/shell_suggestions.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/shell_suggestions_test.go b/internal/app/shell_suggestions_test.go similarity index 99% rename from pkg/custom/shell_suggestions_test.go rename to internal/app/shell_suggestions_test.go index fadc2b05..4fdf549c 100644 --- a/pkg/custom/shell_suggestions_test.go +++ b/internal/app/shell_suggestions_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/speech_context_test.go b/internal/app/speech_context_test.go similarity index 98% rename from pkg/custom/speech_context_test.go rename to internal/app/speech_context_test.go index bb2a46cf..8c9dbc24 100644 --- a/pkg/custom/speech_context_test.go +++ b/internal/app/speech_context_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/stdin_errors_test.go b/internal/app/stdin_errors_test.go similarity index 99% rename from pkg/custom/stdin_errors_test.go rename to internal/app/stdin_errors_test.go index f290c0ee..d54112e0 100644 --- a/pkg/custom/stdin_errors_test.go +++ b/internal/app/stdin_errors_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/stdinsecurity_test.go b/internal/app/stdinsecurity_test.go similarity index 99% rename from pkg/custom/stdinsecurity_test.go rename to internal/app/stdinsecurity_test.go index adca8b36..4bfbe877 100644 --- a/pkg/custom/stdinsecurity_test.go +++ b/internal/app/stdinsecurity_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/streaming_text_test.go b/internal/app/streaming_text_test.go similarity index 99% rename from pkg/custom/streaming_text_test.go rename to internal/app/streaming_text_test.go index aaef3eb3..c7d8858f 100644 --- a/pkg/custom/streaming_text_test.go +++ b/internal/app/streaming_text_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "bytes" diff --git a/pkg/custom/tokenizer_socket_test.go b/internal/app/tokenizer_socket_test.go similarity index 99% rename from pkg/custom/tokenizer_socket_test.go rename to internal/app/tokenizer_socket_test.go index c98ccceb..09eea0cc 100644 --- a/pkg/custom/tokenizer_socket_test.go +++ b/internal/app/tokenizer_socket_test.go @@ -1,6 +1,6 @@ //go:build !windows -package custom +package app import ( "bytes" diff --git a/pkg/custom/unavailable_option.go b/internal/app/unavailable_option.go similarity index 98% rename from pkg/custom/unavailable_option.go rename to internal/app/unavailable_option.go index 7a460e71..3973ac4f 100644 --- a/pkg/custom/unavailable_option.go +++ b/internal/app/unavailable_option.go @@ -1,4 +1,4 @@ -package custom +package app import ( "slices" diff --git a/pkg/custom/unavailable_option_test.go b/internal/app/unavailable_option_test.go similarity index 99% rename from pkg/custom/unavailable_option_test.go rename to internal/app/unavailable_option_test.go index 4d59f29b..0aa9e6f1 100644 --- a/pkg/custom/unavailable_option_test.go +++ b/internal/app/unavailable_option_test.go @@ -1,4 +1,4 @@ -package custom +package app import ( "context" diff --git a/pkg/custom/version.go b/internal/app/version.go similarity index 78% rename from pkg/custom/version.go rename to internal/app/version.go index f1619dc3..5f13d20c 100644 --- a/pkg/custom/version.go +++ b/internal/app/version.go @@ -1,3 +1,3 @@ -package custom +package app const Version = "1.39.0" // x-release-please-version diff --git a/internal/commands/admin/admin_setup_cancellation_test.go b/internal/commands/admin/admin_setup_cancellation_test.go index c7d797da..455f575d 100644 --- a/internal/commands/admin/admin_setup_cancellation_test.go +++ b/internal/commands/admin/admin_setup_cancellation_test.go @@ -13,7 +13,7 @@ import ( "time" "github.com/openai/openai-cli/internal/commands/admin" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/option" "github.com/stretchr/testify/require" @@ -148,12 +148,12 @@ func assertAdminSetupCancellationPresentation(t *testing.T, failure error, messa Flags: []cli.Flag{&cli.StringFlag{Name: "format", Value: "auto"}, &cli.StringFlag{Name: "format-error", Value: "auto"}, &cli.StringFlag{Name: "transform-error"}, &cli.StringFlag{Name: "transform"}, &cli.BoolFlag{Name: "raw-output"}}, Action: func(context.Context, *cli.Command) error { return failure }, } - custom.ConfigureCommandErrors(root) + appconfig.ConfigureCommandErrors(root) var out bytes.Buffer // The normal command wrapper must retain the canonical cause and code. wrapped := root.Run(t.Context(), []string{"openai", "--format-error", format}) require.ErrorIs(t, wrapped, failure) - require.NoError(t, custom.ShowCommandError(root, wrapped, &out)) + require.NoError(t, appconfig.ShowCommandError(root, wrapped, &out)) for _, private := range []string{"synthetic-", "https://", "token=", "\x1b", "\a", "\u202e"} { require.NotContains(t, out.String(), private) } diff --git a/internal/commands/admin/admin_setup_interactive_test.go b/internal/commands/admin/admin_setup_interactive_test.go index f4cb4f4d..b0bd9c85 100644 --- a/internal/commands/admin/admin_setup_interactive_test.go +++ b/internal/commands/admin/admin_setup_interactive_test.go @@ -13,7 +13,7 @@ import ( "github.com/openai/openai-cli/internal/commands/admin" requestconfig "github.com/openai/openai-cli/internal/request" mtlstest "github.com/openai/openai-cli/internal/testutil/mtls" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -21,7 +21,7 @@ import ( func TestAdminSetupVerificationPreservesMTLS(t *testing.T) { const entered = "synthetic-entered-admin-key" root := adminCredentialsTestCommand(t, func(ctx context.Context, command *cli.Command) error { - options, _, err := admin.TestRequestOptions(command, custom.GetDefaultRequestOptions) + options, _, err := admin.TestRequestOptions(command, appconfig.GetDefaultRequestOptions) if err != nil { return err } @@ -51,7 +51,7 @@ func TestAdminSetupVerificationPreservesMTLS(t *testing.T) { t.Cleanup(server.Close) configureTestServerTrust(t, server) certFile, keyFile := mtlstest.WriteClientFiles(t, pki.ClientChainPEM, pki.ClientKeyPEM) - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) require.NoError(t, root.Run(t.Context(), []string{ root.Name, "--base-url", server.URL, "--admin-api-key", "synthetic-flag-key", "--mtls-client-cert-file", certFile, "--mtls-client-key-file", keyFile, @@ -85,7 +85,7 @@ func TestAdminSetupVerificationRejectsRedirectWithoutChangingClient(t *testing.T return nil }} root := adminCredentialsTestCommand(t, func(ctx context.Context, command *cli.Command) error { - options, _, err := admin.TestRequestOptions(command, custom.GetDefaultRequestOptions) + options, _, err := admin.TestRequestOptions(command, appconfig.GetDefaultRequestOptions) if err != nil { return err } diff --git a/internal/commands/admin/admin_setup_origin_test.go b/internal/commands/admin/admin_setup_origin_test.go index f373af2b..a11180ae 100644 --- a/internal/commands/admin/admin_setup_origin_test.go +++ b/internal/commands/admin/admin_setup_origin_test.go @@ -10,7 +10,7 @@ import ( "github.com/openai/openai-cli/internal/commands/admin" requestconfig "github.com/openai/openai-cli/internal/request" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -30,7 +30,7 @@ func TestAdminSetupOriginDisplayPreservesDestination(t *testing.T) { require.NoError(t, err) requests := 0 root := adminCredentialsTestCommand(t, func(ctx context.Context, command *cli.Command) error { - options, display, err := admin.TestRequestOptions(command, custom.GetDefaultRequestOptions) + options, display, err := admin.TestRequestOptions(command, appconfig.GetDefaultRequestOptions) if err != nil { return err } diff --git a/internal/commands/admin/assembly_test.go b/internal/commands/admin/assembly_test.go index 7912fc7d..c0cfca0d 100644 --- a/internal/commands/admin/assembly_test.go +++ b/internal/commands/admin/assembly_test.go @@ -11,7 +11,7 @@ import ( "github.com/openai/openai-cli/internal/apiquery" requestconfig "github.com/openai/openai-cli/internal/request" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -31,13 +31,13 @@ func adminCredentialsTestCommand(t *testing.T, action cli.ActionFunc) *cli.Comma &cli.StringFlag{Name: "base-url"}, &cli.StringFlag{Name: "format", Value: "auto"}, &cli.StringFlag{Name: "format-error", Value: "auto"}, - custom.NewRequestHeaderFlag(), + appconfig.NewRequestHeaderFlag(), }, Commands: []*cli.Command{{ Name: "admin:organization:projects", Category: "API RESOURCE", Commands: []*cli.Command{{Name: "list", Action: func(ctx context.Context, command *cli.Command) error { // Generated actions validate request input before checking credentials. - if _, err := custom.FlagOptions(command, apiquery.NestedQueryFormatBrackets, apiquery.ArrayQueryFormatRepeat, custom.EmptyBody, false); err != nil { + if _, err := appconfig.FlagOptions(command, apiquery.NestedQueryFormatBrackets, apiquery.ArrayQueryFormatRepeat, appconfig.EmptyBody, false); err != nil { return err } return action(ctx, command) diff --git a/internal/commands/audio/generation_assembly_test.go b/internal/commands/audio/generation_assembly_test.go index 6a9c0d04..73511734 100644 --- a/internal/commands/audio/generation_assembly_test.go +++ b/internal/commands/audio/generation_assembly_test.go @@ -5,13 +5,13 @@ import ( "github.com/openai/openai-cli/internal/apiquery" "github.com/openai/openai-cli/internal/commands/audio" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/openai/openai-go/v3/option" "github.com/urfave/cli/v3" ) func buildAudioRequestOptions(command *cli.Command) ([]option.RequestOption, error) { - return custom.FlagOptions(command, apiquery.NestedQueryFormatBrackets, apiquery.ArrayQueryFormatBrackets, custom.ApplicationJSON, true) + return appconfig.FlagOptions(command, apiquery.NestedQueryFormatBrackets, apiquery.ArrayQueryFormatBrackets, appconfig.ApplicationJSON, true) } func TestAudioGenerationPreservesLiteralEditorValuesAndRestoresFlags(t *testing.T) { diff --git a/internal/commands/audio/readable_audio_assembly_test.go b/internal/commands/audio/readable_audio_assembly_test.go index be4c2201..fe8fff6e 100644 --- a/internal/commands/audio/readable_audio_assembly_test.go +++ b/internal/commands/audio/readable_audio_assembly_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands/audio" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) @@ -21,12 +21,12 @@ func TestAudioTextPresentationFormatsAndCancellation(t *testing.T) { ctx, cancel := context.WithCancel(audio.TextContextForTest(t.Context(), "Spoken words")) defer cancel() var out strings.Builder - opts := custom.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.transcriptions > (method) create", OutputKind: custom.OutputResponse, Stdout: &out, Format: tc.format, ExplicitFormat: true} - require.NoError(t, custom.ShowJSON(gjson.Result{}, opts)) + opts := appconfig.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.transcriptions > (method) create", OutputKind: appconfig.OutputResponse, Stdout: &out, Format: tc.format, ExplicitFormat: true} + require.NoError(t, appconfig.ShowJSON(gjson.Result{}, opts)) require.Equal(t, tc.want, out.String()) out.Reset() cancel() - require.ErrorIs(t, custom.ShowJSON(gjson.Result{}, opts), context.Canceled) + require.ErrorIs(t, appconfig.ShowJSON(gjson.Result{}, opts), context.Canceled) require.Empty(t, out.String()) }) } @@ -34,7 +34,7 @@ func TestAudioTextPresentationFormatsAndCancellation(t *testing.T) { func TestAudioTextRawReportsShortWrite(t *testing.T) { ctx := audio.TextContextForTest(t.Context(), "Spoken words") - err := custom.ShowJSON(gjson.Result{}, custom.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.translations > (method) create", OutputKind: custom.OutputResponse, Stdout: shortDownloadWriter{}, Format: "raw", ExplicitFormat: true}) + err := appconfig.ShowJSON(gjson.Result{}, appconfig.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.translations > (method) create", OutputKind: appconfig.OutputResponse, Stdout: shortDownloadWriter{}, Format: "raw", ExplicitFormat: true}) require.ErrorIs(t, err, io.ErrShortWrite) } @@ -51,7 +51,7 @@ func TestAudioTextPreservesExtractionAndEmptyResults(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx := audio.TextContextForTest(t.Context(), tc.input) var out strings.Builder - err := custom.ShowJSON(gjson.Result{}, custom.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.transcriptions > (method) create", OutputKind: custom.OutputResponse, Stdout: &out, Format: tc.format, ExplicitFormat: true, RawOutput: tc.raw, Transform: tc.transform}) + err := appconfig.ShowJSON(gjson.Result{}, appconfig.ShowJSONOpts{Context: ctx, Operation: "(resource) audio.transcriptions > (method) create", OutputKind: appconfig.OutputResponse, Stdout: &out, Format: tc.format, ExplicitFormat: true, RawOutput: tc.raw, Transform: tc.transform}) require.NoError(t, err) require.Equal(t, tc.want, out.String()) }) diff --git a/internal/commands/images/image_models_integration_test.go b/internal/commands/images/image_models_integration_test.go index 8864bdd4..ca104fae 100644 --- a/internal/commands/images/image_models_integration_test.go +++ b/internal/commands/images/image_models_integration_test.go @@ -18,7 +18,7 @@ import ( "github.com/openai/openai-cli/internal/commands/images" "github.com/openai/openai-cli/internal/imagemodels" mtlstest "github.com/openai/openai-cli/internal/testutil/mtls" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -42,7 +42,7 @@ func TestImageModelsErrorClassification(t *testing.T) { for _, format := range []string{"text", "json"} { root := readableErrorTestCommand(t, "--format-error", format) var out strings.Builder - if err := custom.ShowCommandError(root, wrapped, &out); err != nil { + if err := appconfig.ShowCommandError(root, wrapped, &out); err != nil { t.Fatal(err) } message := strings.TrimSpace(out.String()) @@ -94,7 +94,7 @@ func TestImageModelsUsesConfiguredMTLSClient(t *testing.T) { Flags: []cli.Flag{&cli.StringFlag{Name: "base-url"}, &cli.StringFlag{Name: "format", Value: "json"}}, Commands: []*cli.Command{{Name: "images"}}, } - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) if err := root.Run(t.Context(), []string{"openai", "--base-url", server.URL + "/v1/", "--mtls-client-cert-file", certFile, "--mtls-client-key-file", keyFile, "images", "models"}); err != nil { t.Fatal(err) @@ -163,6 +163,6 @@ func imageModelsOutputCommand(out, stderr io.Writer, report images.TestImageMode return &cli.Command{Name: "openai", Writer: out, Flags: []cli.Flag{ &cli.StringFlag{Name: "format", Value: "auto"}, &cli.StringFlag{Name: "transform"}, &cli.BoolFlag{Name: "raw-output"}, }, Action: func(ctx context.Context, command *cli.Command) error { - return images.TestWriteImageModelsData(ctx, command, report, stderr, custom.ImageModelOptions()) + return images.TestWriteImageModelsData(ctx, command, report, stderr, appconfig.ImageModelOptions()) }} } diff --git a/internal/commands/images/image_picker_shell_setup_integration_test.go b/internal/commands/images/image_picker_shell_setup_integration_test.go index 77ab9758..71b85b16 100644 --- a/internal/commands/images/image_picker_shell_setup_integration_test.go +++ b/internal/commands/images/image_picker_shell_setup_integration_test.go @@ -15,7 +15,7 @@ import ( "github.com/openai/openai-cli/internal/autocomplete" localcommands "github.com/openai/openai-cli/internal/commands" "github.com/openai/openai-cli/internal/commands/images" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -47,7 +47,7 @@ func runPickerShellSetupWithDiagnostics(t *testing.T, ctx context.Context, args Commands: []*cli.Command{{Name: "@completion", Action: autocomplete.OutputCompletionScript}}, } images.ConfigureCompletion(root) - images.ConfigureShellSetup(root, custom.ImagePreviewOptions().Receipt) + images.ConfigureShellSetup(root, appconfig.ImagePreviewOptions().Receipt) err = root.Run(ctx, append([]string{"openai", "@completion"}, args...)) os.Stderr = previousStderr closeErr := diagnostics.Close() @@ -118,7 +118,7 @@ func TestImagePickerShellSetupRejectsPowerShellWithoutWriting(t *testing.T) { require.Empty(t, output) require.Empty(t, diagnostic) var presented bytes.Buffer - require.NoError(t, custom.ShowCommandError(readableErrorTestCommand(t), err, &presented)) + require.NoError(t, appconfig.ShowCommandError(readableErrorTestCommand(t), err, &presented)) require.Contains(t, presented.String(), "PowerShell uses normal Tab completion") entries, err := os.ReadDir(home) require.NoError(t, err) @@ -151,7 +151,7 @@ func TestImagePickerShellSetupValidationPreservesRecoveryAndExit(t *testing.T) { require.True(t, errors.Is(failure, local.Cause)) for _, format := range []string{"text", "json"} { var presented bytes.Buffer - require.NoError(t, custom.ShowCommandError(readableErrorTestCommand(t, "--format-error", format), failure, &presented)) + require.NoError(t, appconfig.ShowCommandError(readableErrorTestCommand(t, "--format-error", format), failure, &presented)) require.Contains(t, presented.String(), tc.message) require.NotContains(t, presented.String(), "synthetic-private") } diff --git a/internal/commands/images/image_preferences_integration_test.go b/internal/commands/images/image_preferences_integration_test.go index e2a46507..b44b02f7 100644 --- a/internal/commands/images/image_preferences_integration_test.go +++ b/internal/commands/images/image_preferences_integration_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands/images" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -30,7 +30,7 @@ func TestImageInlinePreferenceFailureKeepsPrivatePathsAndOriginal(t *testing.T) for _, mode := range []string{"on", "off"} { root := &cli.Command{Name: "openai", Writer: &bytes.Buffer{}, ErrWriter: &bytes.Buffer{}, Flags: []cli.Flag{&cli.StringFlag{Name: "format", Value: "auto"}}, - Commands: []*cli.Command{{Name: "images", Commands: []*cli.Command{{Name: "inline", Commands: images.TestImageInlinePreferenceCommands(custom.ImagePreviewOptions().Receipt)}}}}, + Commands: []*cli.Command{{Name: "images", Commands: []*cli.Command{{Name: "inline", Commands: images.TestImageInlinePreferenceCommands(appconfig.ImagePreviewOptions().Receipt)}}}}, } failure := root.Run(t.Context(), []string{"openai", "images", "inline", mode}) var cause *os.PathError @@ -38,7 +38,7 @@ func TestImageInlinePreferenceFailureKeepsPrivatePathsAndOriginal(t *testing.T) require.ErrorIs(t, failure, cause) for _, format := range []string{"text", "json"} { var diagnostic bytes.Buffer - require.NoError(t, custom.ShowCommandError(readableErrorTestCommand(t, "--format-error", format), failure, &diagnostic)) + require.NoError(t, appconfig.ShowCommandError(readableErrorTestCommand(t, "--format-error", format), failure, &diagnostic)) require.Contains(t, diagnostic.String(), "Existing settings were kept.") require.Contains(t, diagnostic.String(), "image-preferences.json") location := images.TestImageInlinePreferenceLocation(runtime.GOOS) @@ -99,7 +99,7 @@ func TestImageInlinePreferenceWarningTerminal(t *testing.T) { cancel() } return nil - }, custom.ImageSavingOptions()), + }, appconfig.ImageSavingOptions()), } images.TestRegisterImageSavingFlags(command) err = command.Run(ctx, []string{"generate", "--output-dir", t.TempDir()}) @@ -130,8 +130,8 @@ func TestImagePreviewCommandsRegisterOnceAndRejectFormats(t *testing.T) { for _, argv := range [][]string{{"images", "preview"}, {"images", "preview", "one", "two"}, {"--format", "json", "images", "preview", "one"}, {"--transform", "id", "images", "inline", "on"}, {"--raw-output", "images", "inline", "off"}, {"images", "inline", "on", "extra"}} { t.Run(strings.Join(argv, " "), func(t *testing.T) { root := &cli.Command{Name: "openai", Writer: &bytes.Buffer{}, ErrWriter: &bytes.Buffer{}, Flags: []cli.Flag{&cli.StringFlag{Name: "format", Value: "auto"}, &cli.StringFlag{Name: "transform"}, &cli.BoolFlag{Name: "raw-output"}}, Commands: []*cli.Command{{Name: "images"}}} - custom.ConfigureCommand(root) - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) + appconfig.ConfigureCommand(root) require.NotNil(t, root.Command("images").Command("preview")) require.NotNil(t, root.Command("images").Command("inline").Command("off")) require.Error(t, root.Run(t.Context(), append([]string{"openai"}, argv...))) @@ -154,13 +154,13 @@ func TestImagePreviewRegistrationPreservesInlineCommands(t *testing.T) { shared.Commands = append(shared.Commands, &cli.Command{Name: "existing"}) } if order == "saved first" { - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) addOtherCommands() } else { addOtherCommands() - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) } - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) require.Same(t, shared, images.Command("inline")) counts := map[string]int{} for _, command := range images.Commands { diff --git a/internal/commands/images/interactive_image_preview_integration_test.go b/internal/commands/images/interactive_image_preview_integration_test.go index 2c682f69..14448564 100644 --- a/internal/commands/images/interactive_image_preview_integration_test.go +++ b/internal/commands/images/interactive_image_preview_integration_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands/images" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -33,7 +33,7 @@ func TestInteractiveImageFailureKeepsExitCodeAndViewerDiagnostic(t *testing.T) { require.Equal(t, max(1, code), exit.ExitCode()) require.ErrorIs(t, err, failure) var output bytes.Buffer - require.NoError(t, custom.ShowCommandError(&cli.Command{}, err, &output)) + require.NoError(t, appconfig.ShowCommandError(&cli.Command{}, err, &output)) require.Contains(t, output.String(), "interactive preview") require.Contains(t, output.String(), "original file is unchanged") require.NotContains(t, output.String(), "API") diff --git a/internal/commands/images/options_integration_test.go b/internal/commands/images/options_integration_test.go index 3d1e1a1d..8fa2c346 100644 --- a/internal/commands/images/options_integration_test.go +++ b/internal/commands/images/options_integration_test.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -13,13 +13,13 @@ func imageErrorMessage(command *cli.Command, failure error) string { command = &cli.Command{} } var out strings.Builder - if err := custom.ShowCommandError(command, failure, &out); err != nil { + if err := appconfig.ShowCommandError(command, failure, &out); err != nil { panic(err) } return strings.TrimSpace(out.String()) } func imageWrapError(command *cli.Command, failure error) error { - custom.ConfigureCommandErrors(command) + appconfig.ConfigureCommandErrors(command) return command.OnUsageError(context.Background(), command, failure, false) } diff --git a/internal/commands/images/prepare_image_upload_integration_test.go b/internal/commands/images/prepare_image_upload_integration_test.go index 3183cac1..055d718a 100644 --- a/internal/commands/images/prepare_image_upload_integration_test.go +++ b/internal/commands/images/prepare_image_upload_integration_test.go @@ -12,7 +12,7 @@ import ( "github.com/openai/openai-cli/internal/commands/images" "github.com/openai/openai-cli/internal/requestflag" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -35,7 +35,7 @@ func TestImageUploadPreparationClosesWhenActionAborts(t *testing.T) { return ctx.Err() } return failure - }, custom.ImageSavingOptions()), + }, appconfig.ImageSavingOptions()), } images.TestRegisterImageSavingFlags(command) err := command.Run(ctx, []string{"edit", "--image", source, "--output-dir", t.TempDir()}) diff --git a/internal/commands/images/save_generated_images_cancellation_test.go b/internal/commands/images/save_generated_images_cancellation_test.go index 591867ba..54986417 100644 --- a/internal/commands/images/save_generated_images_cancellation_test.go +++ b/internal/commands/images/save_generated_images_cancellation_test.go @@ -17,7 +17,7 @@ import ( "github.com/goccy/go-yaml" "github.com/openai/openai-cli/internal/commands/images" clioutput "github.com/openai/openai-cli/internal/output" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" @@ -75,7 +75,7 @@ func TestSavedImageCancellationPreservesRecovery(t *testing.T) { }) plan := images.NewOutputPlanForTest(directory, "robot", "off") presentation := images.WithPresentationForTest(checking, plan, writer) - failure := custom.ShowJSON(response, custom.ShowJSONOpts{ + failure := appconfig.ShowJSON(response, appconfig.ShowJSONOpts{ Context: presentation, Operation: imageGenerationOperation, OutputKind: OutputResponse, Format: "auto", Stdout: writer, Stderr: io.Discard, }) @@ -118,7 +118,7 @@ func TestSavedImageCancellationPreservesRecovery(t *testing.T) { } require.Equal(t, wantStatus, status) var diagnostic bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, wrapped, &diagnostic)) + require.NoError(t, appconfig.ShowCommandError(root, wrapped, &diagnostic)) assertReadableErrorContainsNoPrivateDetails(t, diagnostic.String()) require.NotContains(t, diagnostic.String(), encoded) message := diagnostic.String() @@ -188,7 +188,7 @@ func TestSavedImageCancellationWithoutCompletedFiles(t *testing.T) { return receipt.Write(data) }) presentation := images.WithPresentationForTest(checking, plan, writer) - failure := custom.ShowJSON(response, custom.ShowJSONOpts{ + failure := appconfig.ShowJSON(response, appconfig.ShowJSONOpts{ Context: presentation, Operation: imageGenerationOperation, OutputKind: OutputResponse, Format: "auto", Stdout: writer, Stderr: io.Discard, }) @@ -223,7 +223,7 @@ func TestSavedImageCancellationWithoutCompletedFiles(t *testing.T) { } require.Equal(t, wantStatus, status) var diagnostic bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, wrapped, &diagnostic)) + require.NoError(t, appconfig.ShowCommandError(root, wrapped, &diagnostic)) assertReadableErrorContainsNoPrivateDetails(t, diagnostic.String()) require.NotContains(t, diagnostic.String(), directory) require.NotContains(t, diagnostic.String(), encoded) @@ -256,7 +256,7 @@ func TestSavedImageFailureWithoutCompletedFilesKeepsRecovery(t *testing.T) { plan := images.NewOutputPlanForTest(t.TempDir(), "robot", "off") var receipt bytes.Buffer presentation := images.WithPresentationForTest(t.Context(), plan, &receipt) - failure := custom.ShowJSON(response, custom.ShowJSONOpts{ + failure := appconfig.ShowJSON(response, appconfig.ShowJSONOpts{ Context: presentation, Operation: imageGenerationOperation, OutputKind: OutputResponse, Format: "auto", Stdout: &receipt, Stderr: io.Discard, }) @@ -269,7 +269,7 @@ func TestSavedImageFailureWithoutCompletedFilesKeepsRecovery(t *testing.T) { require.Empty(t, entries) root := readableErrorTestCommand(t, "--format-error", format) var diagnostic bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, imageWrapError(root, failure), &diagnostic)) + require.NoError(t, appconfig.ShowCommandError(root, imageWrapError(root, failure), &diagnostic)) assertReadableErrorContainsNoPrivateDetails(t, diagnostic.String()) require.NotContains(t, diagnostic.String(), plan.DirectoryForTest()) want := "The API responded, but no images could be saved. Check the image data and output folder before trying again." @@ -296,12 +296,12 @@ func TestImagePreferenceCancellationKeepsPathPrivate(t *testing.T) { for _, format := range []string{"text", "json"} { t.Run(format, func(t *testing.T) { root := readableErrorTestCommand(t, "--format-error", format) - failure := images.TestImageInlinePreferenceCommands(custom.ImagePreviewOptions().Receipt)[0].Action(ctx, root) + failure := images.TestImageInlinePreferenceCommands(appconfig.ImagePreviewOptions().Receipt)[0].Action(ctx, root) require.ErrorIs(t, failure, context.Canceled) require.Contains(t, failure.Error(), "Could not save the inline preference.") require.NotContains(t, failure.Error(), path) var output bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, imageWrapError(root, failure), &output)) + require.NoError(t, appconfig.ShowCommandError(root, imageWrapError(root, failure), &output)) assertReadableErrorContainsNoPrivateDetails(t, output.String()) require.NotContains(t, output.String(), path) if format == "json" { @@ -338,7 +338,7 @@ func TestSavedImageCancellationOutputModes(t *testing.T) { t.Run(test.name, func(t *testing.T) { root := readableErrorTestCommand(t, test.args...) var output bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, failure, &output)) + require.NoError(t, appconfig.ShowCommandError(root, failure, &output)) switch test.format { case "text": require.Equal(t, want+"\n", output.String()) @@ -356,7 +356,7 @@ func TestSavedImageCancellationOutputModes(t *testing.T) { require.Equal(t, map[string]any{"message": want}, document) } sinkErr := errors.New("synthetic diagnostic sink failure") - require.ErrorIs(t, custom.ShowCommandError(root, failure, errorSink{sinkErr}), sinkErr) + require.ErrorIs(t, appconfig.ShowCommandError(root, failure, errorSink{sinkErr}), sinkErr) require.ErrorIs(t, failure, context.Canceled) }) } @@ -399,7 +399,7 @@ func TestSavedImageCancellationAfterReceiptKeepsSavedResult(t *testing.T) { return n, err }) presentation := images.WithPresentationForTest(ctx, plan, writer) - failure := custom.ShowJSON(response, custom.ShowJSONOpts{ + failure := appconfig.ShowJSON(response, appconfig.ShowJSONOpts{ Context: presentation, Operation: imageGenerationOperation, OutputKind: OutputResponse, Format: "auto", Stdout: writer, Stderr: io.Discard, }) @@ -430,7 +430,7 @@ func TestSavedImageCancellationAfterReceiptKeepsSavedResult(t *testing.T) { } require.Equal(t, wantStatus, status) var diagnostic bytes.Buffer - require.NoError(t, custom.ShowCommandError(root, wrapped, &diagnostic)) + require.NoError(t, appconfig.ShowCommandError(root, wrapped, &diagnostic)) assertReadableErrorContainsNoPrivateDetails(t, diagnostic.String()) require.NotContains(t, diagnostic.String(), plan.DirectoryForTest()) require.NotContains(t, diagnostic.String(), encoded) diff --git a/internal/commands/images/stream_cleanup_test.go b/internal/commands/images/stream_cleanup_test.go index 75e63a01..4d1730fe 100644 --- a/internal/commands/images/stream_cleanup_test.go +++ b/internal/commands/images/stream_cleanup_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands/images" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/tidwall/gjson" ) @@ -17,7 +17,7 @@ func TestDeterministicStreamCleanupKeepsSavedImageOwner(t *testing.T) { stream := &deterministicCleanupIterator{events: []images.TestOutputJSON{{Result: gjson.Parse(event)}}} plan := images.NewOutputPlanForTest(t.TempDir(), "synthetic", "off") ctx := images.WithPresentationForTest(t.Context(), plan, io.Discard) - err := custom.ShowJSONIterator(stream, 0, custom.ShowJSONOpts{Context: ctx, Operation: imageGenerationOperation, OutputKind: OutputStreamEvent}) + err := appconfig.ShowJSONIterator(stream, 0, appconfig.ShowJSONOpts{Context: ctx, Operation: imageGenerationOperation, OutputKind: OutputStreamEvent}) failed := gjson.Get(event, "type").String() == "image_generation.failed" if (err != nil) != failed || stream.reads != 1 || stream.closes != 1 { t.Fatalf("saved-image owner changed: err=%v reads=%d closes=%d", err, stream.reads, stream.closes) diff --git a/internal/commands/manpages/command_test.go b/internal/commands/manpages/command_test.go index 7fa00307..60716fe2 100644 --- a/internal/commands/manpages/command_test.go +++ b/internal/commands/manpages/command_test.go @@ -14,7 +14,7 @@ import ( localcommands "github.com/openai/openai-cli/internal/commands" manpagecmd "github.com/openai/openai-cli/internal/commands/manpages" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -41,7 +41,7 @@ func manpageTestCommand(t *testing.T, dir string) (*cli.Command, *cli.Command) { Commands: []*cli.Command{group, hidden, manpages}, } manpagecmd.Configure(root, func(command *cli.Command, message string) error { - return custom.ReportSaveReceipt(command, os.Stderr, message, nil) + return appconfig.ReportSaveReceipt(command, os.Stderr, message, nil) }) return root, manpages } @@ -281,7 +281,7 @@ func TestWriteManpageFileErrorsAndCleanup(t *testing.T) { t.Fatalf("write/finalization failure lost recovery: %v", err) } var diagnostic bytes.Buffer - if showErr := custom.ShowCommandError(manpageJSONErrorCommand(t), err, &diagnostic); showErr != nil { + if showErr := appconfig.ShowCommandError(manpageJSONErrorCommand(t), err, &diagnostic); showErr != nil { t.Fatal(showErr) } if strings.Contains(diagnostic.String(), "synthetic") { @@ -311,7 +311,7 @@ func TestWriteManpageFileErrorsAndCleanup(t *testing.T) { func manpageErrorMessage(t *testing.T, root *cli.Command, failure error) string { t.Helper() var diagnostic bytes.Buffer - if err := custom.ShowCommandError(root, failure, &diagnostic); err != nil { + if err := appconfig.ShowCommandError(root, failure, &diagnostic); err != nil { t.Fatal(err) } return strings.TrimSpace(diagnostic.String()) diff --git a/internal/commands/subgroups_test.go b/internal/commands/subgroups_test.go index b77b96b7..63aa8208 100644 --- a/internal/commands/subgroups_test.go +++ b/internal/commands/subgroups_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -24,8 +24,8 @@ func TestCommandSubgroupsKeepActionsFlagsAndParents(t *testing.T) { root := &cli.Command{Name: "openai", Writer: io.Discard, ErrWriter: io.Discard, Flags: []cli.Flag{&cli.StringFlag{Name: "format"}, &cli.StringFlag{Name: "admin-api-key"}}, Commands: []*cli.Command{ {Name: "admin:organization:users:roles", Category: "API RESOURCE", Commands: []*cli.Command{{Name: "list"}}}, original, }} - custom.ConfigureCommand(root) - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) + appconfig.ConfigureCommand(root) users := root.Command("admin").Command("organization").Command("users") if !original.Hidden || users.Hidden || users.Command("roles") == nil || users.Command("list") == leaf { t.Fatal("missing subgroup or shared command node") diff --git a/internal/commands/tasks_test.go b/internal/commands/tasks_test.go index 9352f58a..3384b856 100644 --- a/internal/commands/tasks_test.go +++ b/internal/commands/tasks_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/openai/openai-cli/internal/commands" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/urfave/cli/v3" ) @@ -26,9 +26,9 @@ func TestTaskCommandsPreserveIdentityAndIndependentNodes(t *testing.T) { selected = commands.ResourceName(command) return nil } - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) count := len(root.Commands) - custom.ConfigureCommand(root) + appconfig.ConfigureCommand(root) if len(root.Commands) != count { t.Fatal("repeated configuration duplicated routes") } diff --git a/internal/output/transform/select_transformer.go b/internal/output/transform/select_transformer.go index c956e78d..8592d3b4 100644 --- a/internal/output/transform/select_transformer.go +++ b/internal/output/transform/select_transformer.go @@ -1,5 +1,5 @@ // Package transform contains CLI-owned response transformations. Presentation -// and output formats belong to pkg/custom and its internal rendering helpers. +// and output formats belong to internal/app and shared output helpers. package transform import ( diff --git a/pkg/custom/runtime.go b/pkg/custom/runtime.go new file mode 100644 index 00000000..b693bdfb --- /dev/null +++ b/pkg/custom/runtime.go @@ -0,0 +1,59 @@ +// Package custom temporarily adapts Castiron-generated callers to application +// policy. Remove this package when the authoritative producer updates its imports. +package custom + +import ( + "io" + "net/http" + + "github.com/openai/openai-cli/internal/apiquery" + "github.com/openai/openai-cli/internal/app" + "github.com/openai/openai-cli/internal/jsonview" + "github.com/openai/openai-go/v3/option" + "github.com/tidwall/gjson" + "github.com/urfave/cli/v3" +) + +type BodyContentType = app.BodyContentType +type ShowJSONOpts = app.ShowJSONOpts + +const ( + EmptyBody = app.EmptyBody + MultipartFormEncoded = app.MultipartFormEncoded + ApplicationJSON = app.ApplicationJSON + ApplicationOctetStream = app.ApplicationOctetStream + Version = app.Version + OutputResponse = app.OutputResponse + OutputPageItem = app.OutputPageItem + OutputStreamEvent = app.OutputStreamEvent +) + +var OutputFormats = app.OutputFormats + +func GetDefaultRequestOptions(cmd *cli.Command) []option.RequestOption { + return app.GetDefaultRequestOptions(cmd) +} + +func FlagOptions(cmd *cli.Command, nested apiquery.NestedQueryFormat, array apiquery.ArrayQueryFormat, body BodyContentType, ignoreStdin bool) ([]option.RequestOption, error) { + return app.FlagOptions(cmd, nested, array, body, ignoreStdin) +} + +func WriteBinaryResponse(response *http.Response, stdout io.Writer, outfile string) (string, error) { + return app.WriteBinaryResponse(response, stdout, outfile) +} + +func ValidateBaseURL(value, source string) error { + return app.ValidateBaseURL(value, source) +} + +func ShowJSON(res gjson.Result, opts ShowJSONOpts) error { + return app.ShowJSON(res, opts) +} + +func ShowJSONIterator[T any](iter jsonview.Iterator[T], limit int64, opts ShowJSONOpts) error { + return app.ShowJSONIterator(iter, limit, opts) +} + +func NewRequestHeaderFlag() cli.Flag { + return app.NewRequestHeaderFlag() +} diff --git a/release-please-config.json b/release-please-config.json index 8183022b..483b958a 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -59,7 +59,7 @@ ], "release-type": "simple", "extra-files": [ - "pkg/custom/version.go", + "internal/app/version.go", "README.md" ] } diff --git a/scripts/demos/models-list-viewer/record.sh b/scripts/demos/models-list-viewer/record.sh index f0615a1c..4bbb6921 100755 --- a/scripts/demos/models-list-viewer/record.sh +++ b/scripts/demos/models-list-viewer/record.sh @@ -49,10 +49,10 @@ SCENE } > "$demo_output/metadata.txt" shasum -a 256 "$demo_source/record.sh" "$demo_source/scene.py" "$demo_source/server.py" \ "$demo_root/scripts/demos/capture_and_render.sh" "$demo_root/scripts/image_picker_harness.py" \ - "$demo_root/internal/commands/models/models_list_viewer.go" "$demo_root/pkg/custom/list_navigation.go" \ + "$demo_root/internal/commands/models/models_list_viewer.go" "$demo_root/internal/app/list_navigation.go" \ "$demo_root/internal/commands/models/models_list_table.go" "$demo_root/internal/output/transform/project_models_list.go" \ "$demo_root/internal/commands/models/models_list_loading.go" \ - "$demo_root/pkg/custom/models_policy.go" "$demo_root/internal/output/listview/navigation.go" \ + "$demo_root/internal/app/models_policy.go" "$demo_root/internal/output/listview/navigation.go" \ > "$demo_output/source-sha256.txt" cp "$demo_runtime/scene.sh" "$demo_output/scene.sh" demo_window_size="${demo_width}x36" diff --git a/scripts/demos/record-image-save-cancellation.py b/scripts/demos/record-image-save-cancellation.py index fedb83d0..bd33c571 100644 --- a/scripts/demos/record-image-save-cancellation.py +++ b/scripts/demos/record-image-save-cancellation.py @@ -22,7 +22,7 @@ TEST = "TestSavedImageCancellationPreservesRecovery" -PACKAGE = "github.com/openai/openai-cli/pkg/custom" +PACKAGE = "github.com/openai/openai-cli/internal/commands/images" PATTERN = re.compile( r'interrupted=false status_contract=(\d+) kept_bytes=(\d+) ' r'canceled_second_bytes=(\d+) receipt_bytes=(\d+) diagnostic=("(?:[^"\\]|\\.)*")\n$') diff --git a/tests/cli/audiovoice_auth_contract_test.go b/tests/cli/audiovoice_auth_contract_test.go index 4bd1c774..d2142ea1 100644 --- a/tests/cli/audiovoice_auth_contract_test.go +++ b/tests/cli/audiovoice_auth_contract_test.go @@ -8,7 +8,7 @@ import ( "github.com/openai/openai-cli/internal/mocktest" "github.com/openai/openai-cli/internal/requestflag" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" "github.com/stretchr/testify/require" "github.com/urfave/cli/v3" ) @@ -52,7 +52,7 @@ func TestAudioVoicesCreateCredentialSelection(t *testing.T) { // Match the real root's flag type and env sources: these affect precedence. &requestflag.Flag[string]{Name: "api-key", Sources: cli.EnvVars("OPENAI_API_KEY")}, &requestflag.Flag[string]{Name: "admin-api-key", Sources: cli.EnvVars("OPENAI_ADMIN_KEY")}, - custom.NewRequestHeaderFlag(), + appconfig.NewRequestHeaderFlag(), &cli.StringFlag{Name: "format", Value: "json"}, &cli.StringFlag{Name: "transform"}, &cli.BoolFlag{Name: "raw-output"}, diff --git a/tests/cli/mocktest_main_test.go b/tests/cli/mocktest_main_test.go index be0d92ed..454d731e 100644 --- a/tests/cli/mocktest_main_test.go +++ b/tests/cli/mocktest_main_test.go @@ -6,10 +6,10 @@ import ( "github.com/openai/openai-cli/generated/commands" "github.com/openai/openai-cli/internal/mocktest" - "github.com/openai/openai-cli/pkg/custom" + appconfig "github.com/openai/openai-cli/internal/app" ) func TestMain(m *testing.M) { - custom.ConfigureCommand(commands.Command) + appconfig.ConfigureCommand(commands.Command) os.Exit(mocktest.RunTests(m)) }