@gpc-cli/core 0.9.51 → 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,9 +1323,91 @@ 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
+
1354
+ declare const PLAY_STORE_LIMIT = 500;
1355
+ declare const PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai, or paste the prompt emitted by --format prompt]";
1356
+ type PlayStoreFormat = "md" | "json" | "prompt";
1357
+ interface LocaleEntry {
1358
+ language: string;
1359
+ text: string;
1360
+ chars: number;
1361
+ limit: number;
1362
+ status: "ok" | "over" | "placeholder" | "empty" | "failed";
1363
+ }
1364
+ interface LocaleBundle {
1365
+ from: string;
1366
+ to: string;
1367
+ limit: number;
1368
+ sourceLanguage: string;
1369
+ locales: LocaleEntry[];
1370
+ overflows: string[];
1371
+ }
1372
+ interface PlayStoreRenderOptions {
1373
+ locales: string[];
1374
+ format: PlayStoreFormat;
1375
+ sourceLanguage?: string;
1376
+ }
1377
+ declare function buildLocaleBundle(g: GeneratedChangelog, opts: PlayStoreRenderOptions): LocaleBundle;
1378
+ declare function renderPlayStoreMd(bundle: LocaleBundle): string;
1379
+ declare function renderPlayStoreJson(bundle: LocaleBundle): string;
1380
+ declare function renderPlayStorePrompt(bundle: LocaleBundle, g: GeneratedChangelog): string;
1381
+ declare function renderPlayStore(g: GeneratedChangelog, opts: PlayStoreRenderOptions): {
1382
+ output: string;
1383
+ bundle: LocaleBundle;
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>;
1401
+
1326
1402
  type Renderer = (g: GeneratedChangelog) => string;
1327
1403
  declare const RENDERERS: Record<OutputMode, Renderer>;
1328
1404
 
1405
+ interface ResolveLocalesOptions {
1406
+ client?: PlayApiClient;
1407
+ packageName?: string;
1408
+ }
1409
+ declare function resolveLocales(input: string, options?: ResolveLocalesOptions): Promise<string[]>;
1410
+
1329
1411
  interface RtdnStatus {
1330
1412
  topicName: string | null;
1331
1413
  enabled: boolean;
@@ -1371,4 +1453,4 @@ declare function decodeNotification(base64Payload: string): DecodedNotification;
1371
1453
  */
1372
1454
  declare function formatNotification(notification: DecodedNotification): Record<string, unknown>;
1373
1455
 
1374
- 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 MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, type ParsedCommit, type ParsedManifest, type ParsedMonth, 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 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, 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, renderPrompt, replyToReview, 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,6 +7881,416 @@ 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
+
8054
+ // src/commands/changelog-renderers/play-store.ts
8055
+ var PLAY_STORE_LIMIT = 500;
8056
+ var PLACEHOLDER_TEXT = "[needs translation \u2014 pass --ai, or paste the prompt emitted by --format prompt]";
8057
+ function safeLine2(s) {
8058
+ return s.replace(/[\r\n]+/g, " ").trim();
8059
+ }
8060
+ function countChars(s) {
8061
+ return [...s].length;
8062
+ }
8063
+ function truncateToLimit(text, limit) {
8064
+ if (countChars(text) <= limit) return text;
8065
+ const chars = [...text];
8066
+ return chars.slice(0, limit - 1).join("") + "\u2026";
8067
+ }
8068
+ function renderEnglishBullets(g) {
8069
+ const lines = [];
8070
+ for (const type of SECTION_ORDER) {
8071
+ const commits = g.grouped[type] ?? [];
8072
+ for (const commit of commits) {
8073
+ lines.push(`- ${type}: ${safeLine2(commit.subject)}`);
8074
+ }
8075
+ }
8076
+ return lines.join("\n");
8077
+ }
8078
+ function buildLocaleBundle(g, opts) {
8079
+ const sourceLanguage = opts.sourceLanguage ?? "en-US";
8080
+ const sourceText = renderEnglishBullets(g);
8081
+ const isEmpty = sourceText.length === 0;
8082
+ const overflows = [];
8083
+ const entries = opts.locales.map((language) => {
8084
+ if (language === sourceLanguage) {
8085
+ if (isEmpty) {
8086
+ const emptyText = "No notable changes.";
8087
+ return {
8088
+ language,
8089
+ text: emptyText,
8090
+ chars: countChars(emptyText),
8091
+ limit: PLAY_STORE_LIMIT,
8092
+ status: "empty"
8093
+ };
8094
+ }
8095
+ const chars = countChars(sourceText);
8096
+ const status = chars > PLAY_STORE_LIMIT ? "over" : "ok";
8097
+ if (status === "over") overflows.push(language);
8098
+ const text = status === "over" ? truncateToLimit(sourceText, PLAY_STORE_LIMIT) : sourceText;
8099
+ return {
8100
+ language,
8101
+ text,
8102
+ chars,
8103
+ limit: PLAY_STORE_LIMIT,
8104
+ status
8105
+ };
8106
+ }
8107
+ return {
8108
+ language,
8109
+ text: PLACEHOLDER_TEXT,
8110
+ chars: countChars(PLACEHOLDER_TEXT),
8111
+ limit: PLAY_STORE_LIMIT,
8112
+ status: "placeholder"
8113
+ };
8114
+ });
8115
+ return {
8116
+ from: g.from,
8117
+ to: g.to,
8118
+ limit: PLAY_STORE_LIMIT,
8119
+ sourceLanguage,
8120
+ locales: entries,
8121
+ overflows
8122
+ };
8123
+ }
8124
+ function renderPlayStoreMd(bundle) {
8125
+ const lines = [];
8126
+ lines.push(`# Play Store release notes (${bundle.from} \u2192 ${bundle.to})`);
8127
+ lines.push("");
8128
+ for (const entry of bundle.locales) {
8129
+ let heading;
8130
+ if (entry.status === "placeholder") {
8131
+ heading = `## ${entry.language} (needs translation)`;
8132
+ } else if (entry.status === "empty") {
8133
+ heading = `## ${entry.language} (empty)`;
8134
+ } else {
8135
+ const suffix = entry.status === "over" ? ` \u26A0 truncated` : "";
8136
+ heading = `## ${entry.language} (${entry.chars}/${entry.limit})${suffix}`;
8137
+ }
8138
+ lines.push(heading);
8139
+ lines.push(entry.text);
8140
+ lines.push("");
8141
+ }
8142
+ lines.push("## Summary");
8143
+ for (const entry of bundle.locales) {
8144
+ if (entry.status === "placeholder") {
8145
+ lines.push(`- ${entry.language}: placeholder`);
8146
+ } else if (entry.status === "empty") {
8147
+ lines.push(`- ${entry.language}: empty`);
8148
+ } else {
8149
+ const mark = entry.status === "over" ? "\u2717 over limit" : "\u2713";
8150
+ lines.push(`- ${entry.language}: ${entry.chars}/${entry.limit} ${mark}`);
8151
+ }
8152
+ }
8153
+ return lines.join("\n");
8154
+ }
8155
+ function renderPlayStoreJson(bundle) {
8156
+ return JSON.stringify(bundle, null, 2);
8157
+ }
8158
+ function renderPlayStorePrompt(bundle, g) {
8159
+ const source = bundle.locales.find((e) => e.language === bundle.sourceLanguage);
8160
+ const targets = bundle.locales.filter((e) => e.language !== bundle.sourceLanguage);
8161
+ const lines = [];
8162
+ lines.push(`You are translating Play Store "What's new" release notes from ${bundle.sourceLanguage}.`);
8163
+ lines.push("");
8164
+ lines.push("TARGETS:");
8165
+ for (const t of targets) lines.push(` - ${t.language}`);
8166
+ if (targets.length === 0) lines.push(" (none \u2014 source-only bundle)");
8167
+ lines.push("");
8168
+ lines.push("CONSTRAINTS:");
8169
+ lines.push(`- Each translation MUST be \u2264 ${bundle.limit} Unicode code points`);
8170
+ lines.push('- Preserve the bullet format (one item per line, starts with "- ")');
8171
+ lines.push("- Keep a user-facing tone (no internal jargon)");
8172
+ lines.push('- Do not translate technical names (package names, CLI flags, "GPC")');
8173
+ lines.push("- Drop the conventional-commit prefix (feat:/fix:) if it feels unnatural in the target language");
8174
+ lines.push("");
8175
+ lines.push(`SOURCE (${bundle.sourceLanguage}, ${source?.chars ?? 0}/${bundle.limit} chars):`);
8176
+ lines.push("```");
8177
+ lines.push(source?.text ?? "(empty)");
8178
+ lines.push("```");
8179
+ lines.push("");
8180
+ if (g.headlineCandidates.length > 0) {
8181
+ lines.push("CONTEXT (clusters, for theme awareness):");
8182
+ for (const c of g.headlineCandidates) {
8183
+ lines.push(` - ${c.label} (${c.commits.length} commits, primary ${c.primaryType})`);
8184
+ }
8185
+ lines.push("");
8186
+ }
8187
+ lines.push("OUTPUT FORMAT (one heading + body per target language):");
8188
+ lines.push("```markdown");
8189
+ for (const t of targets) {
8190
+ lines.push(`## ${t.language}`);
8191
+ lines.push("<translation>");
8192
+ lines.push("");
8193
+ }
8194
+ if (targets.length === 0) {
8195
+ lines.push("(no target locales \u2014 nothing to translate)");
8196
+ }
8197
+ lines.push("```");
8198
+ return lines.join("\n");
8199
+ }
8200
+ function renderPlayStore(g, opts) {
8201
+ const bundle = buildLocaleBundle(g, opts);
8202
+ switch (opts.format) {
8203
+ case "md":
8204
+ return { output: renderPlayStoreMd(bundle), bundle };
8205
+ case "json":
8206
+ return { output: renderPlayStoreJson(bundle), bundle };
8207
+ case "prompt":
8208
+ return { output: renderPlayStorePrompt(bundle, g), bundle };
8209
+ }
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
+ }
8293
+
7884
8294
  // src/commands/changelog-renderers/index.ts
7885
8295
  var RENDERERS = {
7886
8296
  md: renderMarkdown,
@@ -7888,6 +8298,68 @@ var RENDERERS = {
7888
8298
  prompt: renderPrompt
7889
8299
  };
7890
8300
 
8301
+ // src/commands/changelog-locales.ts
8302
+ async function resolveLocales(input, options = {}) {
8303
+ const trimmed = input.trim();
8304
+ if (!trimmed) {
8305
+ throw new GpcError(
8306
+ "--locales is empty",
8307
+ "CHANGELOG_LOCALES_REQUIRED",
8308
+ 2,
8309
+ "Pass a comma-separated list (e.g., --locales en-US,fr-FR) or --locales auto."
8310
+ );
8311
+ }
8312
+ if (trimmed === "auto") {
8313
+ const { client, packageName } = options;
8314
+ if (!client || !packageName) {
8315
+ throw new GpcError(
8316
+ "--locales auto requires an authenticated API client and --app",
8317
+ "CHANGELOG_LOCALES_AUTO_NO_APP",
8318
+ 2,
8319
+ "Pass --app <package> or set config.app, and ensure credentials are configured."
8320
+ );
8321
+ }
8322
+ const edit = await client.edits.insert(packageName);
8323
+ try {
8324
+ const listings = await client.listings.list(packageName, edit.id);
8325
+ await client.edits.delete(packageName, edit.id);
8326
+ const langs = listings.map((l) => l.language);
8327
+ if (langs.length === 0) {
8328
+ throw new GpcError(
8329
+ `No Play Store listings found for ${packageName}`,
8330
+ "CHANGELOG_LOCALES_EMPTY",
8331
+ 1,
8332
+ "Create at least one listing in Play Console, or pass --locales explicitly."
8333
+ );
8334
+ }
8335
+ return langs;
8336
+ } catch (error) {
8337
+ await client.edits.delete(packageName, edit.id).catch(() => {
8338
+ });
8339
+ throw error;
8340
+ }
8341
+ }
8342
+ const locales = trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8343
+ if (locales.length === 0) {
8344
+ throw new GpcError(
8345
+ "--locales parsed to an empty list",
8346
+ "CHANGELOG_LOCALES_REQUIRED",
8347
+ 2,
8348
+ "Pass a comma-separated list (e.g., --locales en-US,fr-FR) or --locales auto."
8349
+ );
8350
+ }
8351
+ const invalid = locales.filter((l) => !isValidBcp47(l));
8352
+ if (invalid.length > 0) {
8353
+ throw new GpcError(
8354
+ `Invalid locale(s): ${invalid.join(", ")}`,
8355
+ "CHANGELOG_LOCALES_INVALID",
8356
+ 2,
8357
+ "Use BCP 47 codes recognized by Google Play (e.g., en-US, fr-FR, de-DE)."
8358
+ );
8359
+ }
8360
+ return locales;
8361
+ }
8362
+
7891
8363
  // src/commands/rtdn.ts
7892
8364
  var SUBSCRIPTION_NOTIFICATION_TYPES = {
7893
8365
  1: "SUBSCRIPTION_RECOVERED",
@@ -7984,11 +8456,15 @@ export {
7984
8456
  ApiError,
7985
8457
  ConfigError,
7986
8458
  DEFAULT_LIMITS,
8459
+ DEFAULT_MODELS,
7987
8460
  DEFAULT_PREFLIGHT_CONFIG,
7988
8461
  GOOGLE_PLAY_LANGUAGES,
7989
8462
  GpcError,
7990
8463
  NetworkError,
7991
8464
  PERMISSION_PROPAGATION_WARNING,
8465
+ PLACEHOLDER_TEXT,
8466
+ PLAY_STORE_LIMIT,
8467
+ PROVIDER_WHITELIST,
7992
8468
  PluginManager,
7993
8469
  RENDERERS,
7994
8470
  SECTION_ORDER,
@@ -8007,11 +8483,13 @@ export {
8007
8483
  analyzeReviews2 as analyzeReviews,
8008
8484
  batchGetOrders,
8009
8485
  batchSyncInAppProducts,
8486
+ buildLocaleBundle,
8010
8487
  cancelRecoveryAction,
8011
8488
  cancelSubscriptionPurchase,
8012
8489
  cancelSubscriptionV2,
8013
8490
  checkBundleSize,
8014
8491
  checkThreshold,
8492
+ classifyError,
8015
8493
  clearAuditLog,
8016
8494
  compareBundles,
8017
8495
  compareVersionVitals,
@@ -8032,6 +8510,7 @@ export {
8032
8510
  createSpinner,
8033
8511
  createSubscription,
8034
8512
  createTrack,
8513
+ createTranslator,
8035
8514
  deactivateBasePlan,
8036
8515
  deactivateOffer,
8037
8516
  decodeNotification,
@@ -8061,6 +8540,7 @@ export {
8061
8540
  downloadReport,
8062
8541
  exportImages,
8063
8542
  exportReviews,
8543
+ fetchAggregateCost,
8064
8544
  fetchChangelog,
8065
8545
  fetchReleaseNotes,
8066
8546
  formatChangelogEntry,
@@ -8069,6 +8549,7 @@ export {
8069
8549
  formatJunit,
8070
8550
  formatNotification,
8071
8551
  formatOutput,
8552
+ formatPathLabel,
8072
8553
  formatSlackPayload,
8073
8554
  formatStatusDiff,
8074
8555
  formatStatusSummary,
@@ -8170,8 +8651,14 @@ export {
8170
8651
  removeUser,
8171
8652
  renderJson,
8172
8653
  renderMarkdown,
8654
+ renderPlayStore,
8655
+ renderPlayStoreJson,
8656
+ renderPlayStoreMd,
8657
+ renderPlayStorePrompt,
8173
8658
  renderPrompt,
8174
8659
  replyToReview,
8660
+ resolveAiConfig,
8661
+ resolveLocales,
8175
8662
  revokeSubscriptionPurchase,
8176
8663
  runPreflight,
8177
8664
  runWatchLoop,
@@ -8189,6 +8676,7 @@ export {
8189
8676
  syncInAppProducts,
8190
8677
  topFiles,
8191
8678
  trackBreachState,
8679
+ translateBundle,
8192
8680
  updateAppDetails,
8193
8681
  updateDataSafety,
8194
8682
  updateGrant,