@gpc-cli/core 0.9.52 → 0.9.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1323,15 +1323,43 @@ declare function renderJson(g: GeneratedChangelog): string;
1323
1323
 
1324
1324
  declare function renderPrompt(g: GeneratedChangelog): string;
1325
1325
 
1326
+ type Provider = "anthropic" | "openai" | "google";
1327
+ type TranslationPath = "gateway" | "direct";
1328
+ type ErrorReason = "rate_limited" | "auth" | "safety_blocked" | "timeout" | "network" | "no_source" | "unknown";
1329
+ interface TranslationResult {
1330
+ text: string;
1331
+ tokensIn: number;
1332
+ tokensOut: number;
1333
+ }
1334
+ type Translator = (locale: string, sourceText: string) => Promise<TranslationResult>;
1335
+ interface TranslatorConfig {
1336
+ path: TranslationPath;
1337
+ provider: Provider;
1338
+ model: string;
1339
+ runId: string;
1340
+ }
1341
+ interface ResolveAiConfigOptions {
1342
+ provider?: string;
1343
+ model?: string;
1344
+ env?: NodeJS.ProcessEnv;
1345
+ }
1346
+ declare const PROVIDER_WHITELIST: readonly Provider[];
1347
+ declare const DEFAULT_MODELS: Record<Provider, string>;
1348
+ declare function resolveAiConfig(opts?: ResolveAiConfigOptions): TranslatorConfig;
1349
+ declare function classifyError(err: unknown): ErrorReason;
1350
+ declare function createTranslator(config: TranslatorConfig): Promise<Translator>;
1351
+ declare function fetchAggregateCost(runId: string): Promise<number | undefined>;
1352
+ declare function formatPathLabel(config: TranslatorConfig): string;
1353
+
1326
1354
  declare const PLAY_STORE_LIMIT = 500;
1327
- declare const PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai once v0.9.63 ships, or paste the prompt emitted by --format prompt]";
1355
+ declare const PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai, or paste the prompt emitted by --format prompt]";
1328
1356
  type PlayStoreFormat = "md" | "json" | "prompt";
1329
1357
  interface LocaleEntry {
1330
1358
  language: string;
1331
1359
  text: string;
1332
1360
  chars: number;
1333
1361
  limit: number;
1334
- status: "ok" | "over" | "placeholder" | "empty";
1362
+ status: "ok" | "over" | "placeholder" | "empty" | "failed";
1335
1363
  }
1336
1364
  interface LocaleBundle {
1337
1365
  from: string;
@@ -1354,6 +1382,22 @@ declare function renderPlayStore(g: GeneratedChangelog, opts: PlayStoreRenderOpt
1354
1382
  output: string;
1355
1383
  bundle: LocaleBundle;
1356
1384
  };
1385
+ interface TranslationFailure {
1386
+ language: string;
1387
+ reason: ErrorReason;
1388
+ }
1389
+ interface TranslateBundleOptions {
1390
+ translator: Translator;
1391
+ strict?: boolean;
1392
+ onError?: (failure: TranslationFailure, err: unknown) => void;
1393
+ onTranslated?: (entry: LocaleEntry) => void;
1394
+ }
1395
+ interface TranslatedBundle extends LocaleBundle {
1396
+ tokensIn: number;
1397
+ tokensOut: number;
1398
+ failures: TranslationFailure[];
1399
+ }
1400
+ declare function translateBundle(bundle: LocaleBundle, options: TranslateBundleOptions): Promise<TranslatedBundle>;
1357
1401
 
1358
1402
  type Renderer = (g: GeneratedChangelog) => string;
1359
1403
  declare const RENDERERS: Record<OutputMode, Renderer>;
@@ -1409,4 +1453,4 @@ declare function decodeNotification(base64Payload: string): DecodedNotification;
1409
1453
  */
1410
1454
  declare function formatNotification(notification: DecodedNotification): Record<string, unknown>;
1411
1455
 
1412
- export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, buildLocaleBundle, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveLocales, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
1456
+ export { ApiError, type AppInfo, type AppStatus, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type ChangelogEntry, type CommandContext, type CommitCluster, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type ListIapOptions, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, type RawCommit, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewsFilterOptions, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type Spinner, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, type ValidateCheck, type ValidateOptions, type ValidateResult, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchOptions, type WatchVitalsOptions, type WebhookPayload, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, batchGetOrders, batchSyncInAppProducts, buildLocaleBundle, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkThreshold, classifyError, clearAuditLog, compareBundles, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeNotification, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPlugins, downloadGeneratedApk, downloadReport, exportImages, exportReviews, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAllScannerNames, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, lintListing, lintListings, lintLocalListings, listAchievements, listAuditEvents, listDeviceTiers, listEvents, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullListings, pushListings, readListingsFromDir, readReleaseNotesFromDir, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, resolveAiConfig, resolveLocales, revokeSubscriptionPurchase, runPreflight, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, sortResults, startTrain, statusHasBreach, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateImage, validateLanguageCode, validatePackageName, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
package/dist/index.js CHANGED
@@ -7881,9 +7881,179 @@ function renderPrompt(g) {
7881
7881
  return lines.join("\n");
7882
7882
  }
7883
7883
 
7884
+ // src/commands/changelog-ai.ts
7885
+ import { randomUUID } from "crypto";
7886
+ var PROVIDER_WHITELIST = ["anthropic", "openai", "google"];
7887
+ var DEFAULT_MODELS = {
7888
+ anthropic: "claude-sonnet-4-6",
7889
+ openai: "gpt-4o-mini",
7890
+ google: "gemini-2.5-flash"
7891
+ };
7892
+ function resolveAiConfig(opts = {}) {
7893
+ const env = opts.env ?? process.env;
7894
+ const hasGateway = typeof env["AI_GATEWAY_API_KEY"] === "string" && env["AI_GATEWAY_API_KEY"].length > 0;
7895
+ let provider;
7896
+ if (opts.provider) {
7897
+ const normalized = opts.provider.toLowerCase();
7898
+ if (!PROVIDER_WHITELIST.includes(normalized)) {
7899
+ throw new GpcError(
7900
+ `Unknown --provider "${opts.provider}"`,
7901
+ "CHANGELOG_AI_UNKNOWN_PROVIDER",
7902
+ 2,
7903
+ `Valid providers: ${PROVIDER_WHITELIST.join(", ")}`
7904
+ );
7905
+ }
7906
+ provider = normalized;
7907
+ } else if (hasGateway || env["ANTHROPIC_API_KEY"]) {
7908
+ provider = "anthropic";
7909
+ } else if (env["OPENAI_API_KEY"]) {
7910
+ provider = "openai";
7911
+ } else if (env["GOOGLE_GENERATIVE_AI_API_KEY"]) {
7912
+ provider = "google";
7913
+ } else {
7914
+ throw new GpcError(
7915
+ "No AI provider credentials found in env",
7916
+ "CHANGELOG_AI_NO_CREDENTIALS",
7917
+ 3,
7918
+ "Set one of: AI_GATEWAY_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY."
7919
+ );
7920
+ }
7921
+ const model = opts.model ?? DEFAULT_MODELS[provider];
7922
+ const path = hasGateway ? "gateway" : "direct";
7923
+ const runId = randomUUID();
7924
+ return { path, provider, model, runId };
7925
+ }
7926
+ function classifyError(err) {
7927
+ if (!err || typeof err !== "object") return "unknown";
7928
+ const e = err;
7929
+ const name = typeof e["name"] === "string" ? e["name"] : "";
7930
+ const statusCode = typeof e["statusCode"] === "number" ? e["statusCode"] : typeof e["status"] === "number" ? e["status"] : 0;
7931
+ const message = typeof e["message"] === "string" ? e["message"].toLowerCase() : "";
7932
+ const finishReason = typeof e["finishReason"] === "string" ? e["finishReason"] : "";
7933
+ if (name === "RateLimitError" || statusCode === 429 || statusCode === 529) return "rate_limited";
7934
+ if (statusCode === 401 || statusCode === 403) return "auth";
7935
+ if (message.includes("api key invalid") || message.includes("invalid api key")) return "auth";
7936
+ if (finishReason === "SAFETY") return "safety_blocked";
7937
+ if (statusCode === 400 && (message.includes("content_policy") || message.includes("content policy") || message.includes("safety"))) {
7938
+ return "safety_blocked";
7939
+ }
7940
+ if (name === "AbortError" || name === "TimeoutError") return "timeout";
7941
+ if (message.includes("timeout") || message.includes("timed out")) return "timeout";
7942
+ if (message.includes("econnrefused") || message.includes("enotfound") || message.includes("etimedout") || message.includes("network") || name === "TypeError" && message.includes("fetch")) {
7943
+ return "network";
7944
+ }
7945
+ return "unknown";
7946
+ }
7947
+ function buildSystemPrompt() {
7948
+ return [
7949
+ `You translate Play Store "What's new" release notes for Android apps.`,
7950
+ "Rules:",
7951
+ "- Translation MUST be at most 500 Unicode code points.",
7952
+ '- Preserve bullet format (one item per line, starts with "- ").',
7953
+ "- User-facing tone. Avoid internal jargon.",
7954
+ '- Do not translate technical names (package names, CLI flags, "GPC").',
7955
+ "- Drop the conventional-commit prefix (feat:/fix:/docs:) if it feels unnatural in the target language.",
7956
+ "Respond with the translated text only. No explanations, no markdown headings."
7957
+ ].join("\n");
7958
+ }
7959
+ function buildUserPrompt(locale, sourceText) {
7960
+ return `Translate the following release notes into ${locale}:
7961
+
7962
+ ${sourceText}`;
7963
+ }
7964
+ function providerSpecificOptions(provider) {
7965
+ if (provider === "google") {
7966
+ return { google: { thinkingConfig: { thinkingBudget: 0 } } };
7967
+ }
7968
+ return {};
7969
+ }
7970
+ function readUsage(usage) {
7971
+ if (!usage || typeof usage !== "object") return { tokensIn: 0, tokensOut: 0 };
7972
+ const u = usage;
7973
+ const tokensIn = typeof u["inputTokens"] === "number" ? u["inputTokens"] : typeof u["promptTokens"] === "number" ? u["promptTokens"] : 0;
7974
+ const tokensOut = typeof u["outputTokens"] === "number" ? u["outputTokens"] : typeof u["completionTokens"] === "number" ? u["completionTokens"] : 0;
7975
+ return { tokensIn, tokensOut };
7976
+ }
7977
+ async function createTranslator(config) {
7978
+ const ai = await import("ai");
7979
+ const generateText = ai.generateText;
7980
+ if (config.path === "gateway") {
7981
+ const modelString = `${config.provider}/${config.model}`;
7982
+ return async (locale, sourceText) => {
7983
+ const result = await generateText({
7984
+ model: ai.gateway(modelString),
7985
+ system: buildSystemPrompt(),
7986
+ prompt: buildUserPrompt(locale, sourceText),
7987
+ temperature: 0.2,
7988
+ providerOptions: {
7989
+ gateway: { tags: [`gpc-changelog-${config.runId}`] },
7990
+ ...providerSpecificOptions(config.provider)
7991
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7992
+ }
7993
+ });
7994
+ const { tokensIn, tokensOut } = readUsage(result.usage);
7995
+ return { text: result.text, tokensIn, tokensOut };
7996
+ };
7997
+ }
7998
+ let modelFactory;
7999
+ if (config.provider === "anthropic") {
8000
+ const mod = await import("@ai-sdk/anthropic");
8001
+ modelFactory = (id) => mod.anthropic(id);
8002
+ } else if (config.provider === "openai") {
8003
+ const mod = await import("@ai-sdk/openai");
8004
+ modelFactory = (id) => mod.openai(id);
8005
+ } else {
8006
+ const mod = await import("@ai-sdk/google");
8007
+ modelFactory = (id) => mod.google(id);
8008
+ }
8009
+ return async (locale, sourceText) => {
8010
+ const result = await generateText({
8011
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8012
+ model: modelFactory(config.model),
8013
+ system: buildSystemPrompt(),
8014
+ prompt: buildUserPrompt(locale, sourceText),
8015
+ temperature: 0.2,
8016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8017
+ providerOptions: providerSpecificOptions(config.provider)
8018
+ });
8019
+ const { tokensIn, tokensOut } = readUsage(result.usage);
8020
+ return { text: result.text, tokensIn, tokensOut };
8021
+ };
8022
+ }
8023
+ async function fetchAggregateCost(runId) {
8024
+ try {
8025
+ const { gateway } = await import("ai");
8026
+ const now = /* @__PURE__ */ new Date();
8027
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
8028
+ const today = now.toISOString().slice(0, 10);
8029
+ const startDate = yesterday.toISOString().slice(0, 10);
8030
+ const report = await gateway.getSpendReport({
8031
+ startDate,
8032
+ endDate: today,
8033
+ groupBy: "tag",
8034
+ tags: [`gpc-changelog-${runId}`]
8035
+ });
8036
+ const results = report.results ?? [];
8037
+ return results.reduce((sum, row) => sum + (row.totalCost ?? 0), 0);
8038
+ } catch {
8039
+ return void 0;
8040
+ }
8041
+ }
8042
+ var PROVIDER_BRAND = {
8043
+ anthropic: "Anthropic",
8044
+ openai: "OpenAI",
8045
+ google: "Google"
8046
+ };
8047
+ function formatPathLabel(config) {
8048
+ if (config.path === "gateway") {
8049
+ return `routing via AI Gateway (${config.provider}/${config.model})`;
8050
+ }
8051
+ return `direct ${PROVIDER_BRAND[config.provider]} SDK (${config.model})`;
8052
+ }
8053
+
7884
8054
  // src/commands/changelog-renderers/play-store.ts
7885
8055
  var PLAY_STORE_LIMIT = 500;
7886
- var PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai once v0.9.63 ships, or paste the prompt emitted by --format prompt]";
8056
+ var PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai, or paste the prompt emitted by --format prompt]";
7887
8057
  function safeLine2(s) {
7888
8058
  return s.replace(/[\r\n]+/g, " ").trim();
7889
8059
  }
@@ -8038,6 +8208,88 @@ function renderPlayStore(g, opts) {
8038
8208
  return { output: renderPlayStorePrompt(bundle, g), bundle };
8039
8209
  }
8040
8210
  }
8211
+ async function translateBundle(bundle, options) {
8212
+ const source = bundle.locales.find((e) => e.language === bundle.sourceLanguage);
8213
+ const sourceText = source?.text ?? "";
8214
+ const hasSource = source !== void 0 && sourceText.trim().length > 0;
8215
+ let tokensIn = 0;
8216
+ let tokensOut = 0;
8217
+ const failures = [];
8218
+ const newLocales = [];
8219
+ for (const entry of bundle.locales) {
8220
+ if (entry.status !== "placeholder") {
8221
+ newLocales.push(entry);
8222
+ continue;
8223
+ }
8224
+ if (!hasSource) {
8225
+ const failure = { language: entry.language, reason: "no_source" };
8226
+ failures.push(failure);
8227
+ options.onError?.(failure, new Error("source locale missing or empty"));
8228
+ const failedText = `[translation failed: no_source]`;
8229
+ newLocales.push({
8230
+ language: entry.language,
8231
+ text: failedText,
8232
+ chars: countChars(failedText),
8233
+ limit: PLAY_STORE_LIMIT,
8234
+ status: "failed"
8235
+ });
8236
+ continue;
8237
+ }
8238
+ try {
8239
+ const result = await options.translator(entry.language, sourceText);
8240
+ tokensIn += result.tokensIn;
8241
+ tokensOut += result.tokensOut;
8242
+ let text = result.text.trim();
8243
+ let chars = countChars(text);
8244
+ let status = "ok";
8245
+ if (chars > PLAY_STORE_LIMIT) {
8246
+ text = truncateToLimit(text, PLAY_STORE_LIMIT);
8247
+ chars = countChars(text);
8248
+ status = "over";
8249
+ }
8250
+ const translated2 = {
8251
+ language: entry.language,
8252
+ text,
8253
+ chars,
8254
+ limit: PLAY_STORE_LIMIT,
8255
+ status
8256
+ };
8257
+ newLocales.push(translated2);
8258
+ options.onTranslated?.(translated2);
8259
+ } catch (err) {
8260
+ const reason = classifyError(err);
8261
+ const failure = { language: entry.language, reason };
8262
+ failures.push(failure);
8263
+ options.onError?.(failure, err);
8264
+ const failedText = `[translation failed: ${reason}]`;
8265
+ newLocales.push({
8266
+ language: entry.language,
8267
+ text: failedText,
8268
+ chars: countChars(failedText),
8269
+ limit: PLAY_STORE_LIMIT,
8270
+ status: "failed"
8271
+ });
8272
+ }
8273
+ }
8274
+ const overflows = newLocales.filter((e) => e.status === "over").map((e) => e.language);
8275
+ const translated = {
8276
+ ...bundle,
8277
+ locales: newLocales,
8278
+ overflows,
8279
+ tokensIn,
8280
+ tokensOut,
8281
+ failures
8282
+ };
8283
+ if (options.strict && failures.length > 0) {
8284
+ throw new GpcError(
8285
+ `${failures.length} locale${failures.length === 1 ? "" : "s"} failed to translate: ${failures.map((f) => `${f.language}=${f.reason}`).join(", ")}`,
8286
+ "CHANGELOG_AI_TRANSLATION_FAILED",
8287
+ 1,
8288
+ "Remove --strict to continue on errors, or check credentials and retry."
8289
+ );
8290
+ }
8291
+ return translated;
8292
+ }
8041
8293
 
8042
8294
  // src/commands/changelog-renderers/index.ts
8043
8295
  var RENDERERS = {
@@ -8204,6 +8456,7 @@ export {
8204
8456
  ApiError,
8205
8457
  ConfigError,
8206
8458
  DEFAULT_LIMITS,
8459
+ DEFAULT_MODELS,
8207
8460
  DEFAULT_PREFLIGHT_CONFIG,
8208
8461
  GOOGLE_PLAY_LANGUAGES,
8209
8462
  GpcError,
@@ -8211,6 +8464,7 @@ export {
8211
8464
  PERMISSION_PROPAGATION_WARNING,
8212
8465
  PLACEHOLDER_TEXT,
8213
8466
  PLAY_STORE_LIMIT,
8467
+ PROVIDER_WHITELIST,
8214
8468
  PluginManager,
8215
8469
  RENDERERS,
8216
8470
  SECTION_ORDER,
@@ -8235,6 +8489,7 @@ export {
8235
8489
  cancelSubscriptionV2,
8236
8490
  checkBundleSize,
8237
8491
  checkThreshold,
8492
+ classifyError,
8238
8493
  clearAuditLog,
8239
8494
  compareBundles,
8240
8495
  compareVersionVitals,
@@ -8255,6 +8510,7 @@ export {
8255
8510
  createSpinner,
8256
8511
  createSubscription,
8257
8512
  createTrack,
8513
+ createTranslator,
8258
8514
  deactivateBasePlan,
8259
8515
  deactivateOffer,
8260
8516
  decodeNotification,
@@ -8284,6 +8540,7 @@ export {
8284
8540
  downloadReport,
8285
8541
  exportImages,
8286
8542
  exportReviews,
8543
+ fetchAggregateCost,
8287
8544
  fetchChangelog,
8288
8545
  fetchReleaseNotes,
8289
8546
  formatChangelogEntry,
@@ -8292,6 +8549,7 @@ export {
8292
8549
  formatJunit,
8293
8550
  formatNotification,
8294
8551
  formatOutput,
8552
+ formatPathLabel,
8295
8553
  formatSlackPayload,
8296
8554
  formatStatusDiff,
8297
8555
  formatStatusSummary,
@@ -8399,6 +8657,7 @@ export {
8399
8657
  renderPlayStorePrompt,
8400
8658
  renderPrompt,
8401
8659
  replyToReview,
8660
+ resolveAiConfig,
8402
8661
  resolveLocales,
8403
8662
  revokeSubscriptionPurchase,
8404
8663
  runPreflight,
@@ -8417,6 +8676,7 @@ export {
8417
8676
  syncInAppProducts,
8418
8677
  topFiles,
8419
8678
  trackBreachState,
8679
+ translateBundle,
8420
8680
  updateAppDetails,
8421
8681
  updateDataSafety,
8422
8682
  updateGrant,