@vocoder/cli 0.10.0 → 0.11.0

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.
@@ -43568,6 +43568,687 @@ var require_brace_expansion = __commonJS({
43568
43568
  }
43569
43569
  });
43570
43570
 
43571
+ // src/utils/api.ts
43572
+ function isLimitErrorResponse(value) {
43573
+ if (!value || typeof value !== "object") {
43574
+ return false;
43575
+ }
43576
+ const candidate = value;
43577
+ return typeof candidate.errorCode === "string" && typeof candidate.limitType === "string" && typeof candidate.planId === "string" && typeof candidate.current === "number" && typeof candidate.required === "number" && typeof candidate.upgradeUrl === "string" && typeof candidate.message === "string";
43578
+ }
43579
+ function isSyncPolicyErrorResponse(value) {
43580
+ if (!value || typeof value !== "object") {
43581
+ return false;
43582
+ }
43583
+ const candidate = value;
43584
+ return (candidate.errorCode === "BRANCH_NOT_ALLOWED" || candidate.errorCode === "PROJECT_REPOSITORY_MISMATCH") && typeof candidate.message === "string";
43585
+ }
43586
+ function extractErrorMessage(payload, fallback) {
43587
+ if (!payload || typeof payload !== "object") {
43588
+ return fallback;
43589
+ }
43590
+ const candidate = payload;
43591
+ if (typeof candidate.message === "string") {
43592
+ return candidate.message;
43593
+ }
43594
+ if (typeof candidate.error === "string") {
43595
+ return candidate.error;
43596
+ }
43597
+ return fallback;
43598
+ }
43599
+ function parsePayload(raw) {
43600
+ if (raw.length === 0) {
43601
+ return null;
43602
+ }
43603
+ const trimmed = raw.trimStart();
43604
+ if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) {
43605
+ return {
43606
+ message: "Unexpected response from server (received HTML). Check your network connection or try again."
43607
+ };
43608
+ }
43609
+ try {
43610
+ return JSON.parse(raw);
43611
+ } catch {
43612
+ return { message: raw };
43613
+ }
43614
+ }
43615
+ async function readPayload(response) {
43616
+ if (typeof response.text === "function") {
43617
+ const raw = await response.text();
43618
+ return parsePayload(raw);
43619
+ }
43620
+ if (typeof response.json === "function") {
43621
+ return response.json();
43622
+ }
43623
+ return null;
43624
+ }
43625
+ var VocoderAPIError = class extends Error {
43626
+ constructor(params) {
43627
+ super(params.message);
43628
+ this.name = "VocoderAPIError";
43629
+ this.status = params.status;
43630
+ this.payload = params.payload;
43631
+ this.limitError = params.limitError ?? null;
43632
+ this.syncPolicyError = params.syncPolicyError ?? null;
43633
+ }
43634
+ };
43635
+ var VocoderAPI = class {
43636
+ constructor(config) {
43637
+ this.apiUrl = config.apiUrl;
43638
+ this.apiKey = config.apiKey;
43639
+ }
43640
+ async request(path2, init = {}, errorPrefix) {
43641
+ const response = await fetch(`${this.apiUrl}${path2}`, {
43642
+ ...init,
43643
+ headers: {
43644
+ Authorization: `Bearer ${this.apiKey}`,
43645
+ ...init.headers ?? {}
43646
+ }
43647
+ });
43648
+ const payload = await readPayload(response);
43649
+ if (!response.ok) {
43650
+ const limitError = isLimitErrorResponse(payload) ? payload : null;
43651
+ const syncPolicyError = isSyncPolicyErrorResponse(payload) ? payload : null;
43652
+ const baseMessage = extractErrorMessage(
43653
+ payload,
43654
+ `Request failed with status ${response.status}`
43655
+ );
43656
+ throw new VocoderAPIError({
43657
+ message: errorPrefix ? `${errorPrefix}: ${baseMessage}` : baseMessage,
43658
+ status: response.status,
43659
+ payload,
43660
+ limitError,
43661
+ syncPolicyError
43662
+ });
43663
+ }
43664
+ return payload;
43665
+ }
43666
+ /**
43667
+ * Fetch project configuration from API
43668
+ * Project is determined from the API key
43669
+ */
43670
+ async getProjectConfig() {
43671
+ const data = await this.request("/api/cli/config", {}, "Failed to fetch project config");
43672
+ return {
43673
+ projectName: data.projectName,
43674
+ organizationName: data.organizationName,
43675
+ shortCode: data.shortCode,
43676
+ sourceLocale: data.sourceLocale,
43677
+ targetLocales: data.targetLocales,
43678
+ targetBranches: data.targetBranches ?? ["main"],
43679
+ primaryBranch: data.primaryBranch,
43680
+ syncPolicy: {
43681
+ blockingBranches: data.syncPolicy?.blockingBranches ?? [
43682
+ "main",
43683
+ "master"
43684
+ ],
43685
+ blockingMode: data.syncPolicy?.blockingMode ?? "required",
43686
+ nonBlockingMode: data.syncPolicy?.nonBlockingMode ?? "best-effort",
43687
+ defaultMaxWaitMs: data.syncPolicy?.defaultMaxWaitMs ?? 6e4
43688
+ }
43689
+ };
43690
+ }
43691
+ /**
43692
+ * Submit strings for translation
43693
+ * Project is determined from the API key
43694
+ */
43695
+ stableTextKey(text) {
43696
+ let hash = 2166136261;
43697
+ for (let i = 0; i < text.length; i++) {
43698
+ hash ^= text.charCodeAt(i);
43699
+ hash = Math.imul(hash, 16777619);
43700
+ }
43701
+ return `SK_TEXT_${(hash >>> 0).toString(16).toUpperCase().padStart(8, "0")}`;
43702
+ }
43703
+ normalizeStringEntries(entries) {
43704
+ if (entries.length === 0) {
43705
+ return [];
43706
+ }
43707
+ const first = entries[0];
43708
+ if (typeof first === "string") {
43709
+ return entries.map((text) => ({
43710
+ key: this.stableTextKey(text),
43711
+ text
43712
+ }));
43713
+ }
43714
+ return entries.map((entry, index) => ({
43715
+ key: entry.key || this.stableTextKey(`${entry.text}:${index}`),
43716
+ text: entry.text,
43717
+ ...entry.context ? { context: entry.context } : {},
43718
+ ...entry.formality ? { formality: entry.formality } : {},
43719
+ ...entry.uiRole ? { uiRole: entry.uiRole } : {}
43720
+ }));
43721
+ }
43722
+ async submitTranslation(branch, entries, targetLocales, options, repoIdentity) {
43723
+ const stringEntries = this.normalizeStringEntries(entries);
43724
+ const strings = stringEntries.map((entry) => entry.text);
43725
+ const crypto = await import("crypto");
43726
+ const sortedStrings = [...strings].sort();
43727
+ const stringsHash = crypto.createHash("sha256").update(JSON.stringify(sortedStrings)).digest("hex");
43728
+ return this.request(
43729
+ "/api/cli/sync",
43730
+ {
43731
+ method: "POST",
43732
+ headers: {
43733
+ "Content-Type": "application/json"
43734
+ },
43735
+ body: JSON.stringify({
43736
+ branch,
43737
+ stringEntries,
43738
+ targetLocales,
43739
+ ...options?.force ? {} : { stringsHash },
43740
+ ...options?.requestedMode ? { requestedMode: options.requestedMode } : {},
43741
+ ...typeof options?.requestedMaxWaitMs === "number" ? { requestedMaxWaitMs: options.requestedMaxWaitMs } : {},
43742
+ ...options?.clientRunId ? { clientRunId: options.clientRunId } : {},
43743
+ ...repoIdentity?.repoCanonical ? { repoCanonical: repoIdentity.repoCanonical } : {},
43744
+ ...repoIdentity?.repoAppDir !== void 0 ? { repoAppDir: repoIdentity.repoAppDir } : {},
43745
+ ...repoIdentity?.commitSha ? { commitSha: repoIdentity.commitSha } : {},
43746
+ ...options?.appIndustry ? { appIndustry: options.appIndustry } : {}
43747
+ })
43748
+ },
43749
+ "Translation submission failed"
43750
+ );
43751
+ }
43752
+ /**
43753
+ * Check translation status
43754
+ */
43755
+ async getTranslationStatus(batchId) {
43756
+ return this.request(
43757
+ `/api/cli/sync/status/${batchId}`,
43758
+ {},
43759
+ "Failed to check translation status"
43760
+ );
43761
+ }
43762
+ async getTranslationSnapshot(params) {
43763
+ const search = new URLSearchParams();
43764
+ search.set("branch", params.branch);
43765
+ for (const locale of params.targetLocales) {
43766
+ search.append("targetLocale", locale);
43767
+ }
43768
+ return this.request(
43769
+ `/api/cli/sync/snapshot?${search.toString()}`,
43770
+ {},
43771
+ "Failed to fetch translation snapshot"
43772
+ );
43773
+ }
43774
+ /**
43775
+ * Wait for translation to complete with polling
43776
+ */
43777
+ async waitForCompletion(batchId, timeout = 6e4, onProgress) {
43778
+ const startTime = Date.now();
43779
+ const pollInterval = 1e3;
43780
+ while (Date.now() - startTime < timeout) {
43781
+ const status = await this.getTranslationStatus(batchId);
43782
+ if (onProgress) {
43783
+ onProgress(status.progress);
43784
+ }
43785
+ if (status.status === "COMPLETED") {
43786
+ if (!status.translations) {
43787
+ throw new Error("Translation completed but no translations returned");
43788
+ }
43789
+ return {
43790
+ translations: status.translations,
43791
+ localeMetadata: status.localeMetadata
43792
+ };
43793
+ }
43794
+ if (status.status === "FAILED") {
43795
+ throw new Error(
43796
+ `Translation failed: ${status.errorMessage || "Unknown error"}`
43797
+ );
43798
+ }
43799
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
43800
+ }
43801
+ throw new Error(`Translation timeout after ${timeout}ms`);
43802
+ }
43803
+ async startInitSession(input) {
43804
+ const response = await fetch(`${this.apiUrl}/api/cli/init/start`, {
43805
+ method: "POST",
43806
+ headers: {
43807
+ "Content-Type": "application/json"
43808
+ },
43809
+ body: JSON.stringify(input)
43810
+ });
43811
+ const payload = await readPayload(response);
43812
+ if (!response.ok) {
43813
+ throw new VocoderAPIError({
43814
+ message: extractErrorMessage(
43815
+ payload,
43816
+ `Failed to start init session (${response.status})`
43817
+ ),
43818
+ status: response.status,
43819
+ payload
43820
+ });
43821
+ }
43822
+ return payload;
43823
+ }
43824
+ async getInitSessionStatus(params) {
43825
+ const response = await fetch(
43826
+ `${this.apiUrl}/api/cli/init/status/${params.sessionId}`,
43827
+ {
43828
+ headers: {
43829
+ Authorization: `Bearer ${params.pollToken}`
43830
+ }
43831
+ }
43832
+ );
43833
+ const payload = await readPayload(response);
43834
+ if (!response.ok) {
43835
+ throw new VocoderAPIError({
43836
+ message: extractErrorMessage(
43837
+ payload,
43838
+ `Failed to get init status (${response.status})`
43839
+ ),
43840
+ status: response.status,
43841
+ payload
43842
+ });
43843
+ }
43844
+ return payload;
43845
+ }
43846
+ // ── CLI Auth endpoints (no project API key needed) ──────────────────────────
43847
+ /**
43848
+ * Start a CLI auth session. Returns `{ sessionId, verificationUrl, expiresAt }`.
43849
+ * `sessionId` is the raw poll token — keep it secret, used for polling.
43850
+ */
43851
+ async startCliAuthSession(callbackPort, repoCanonical) {
43852
+ const response = await fetch(`${this.apiUrl}/api/cli/auth/start`, {
43853
+ method: "POST",
43854
+ headers: { "Content-Type": "application/json" },
43855
+ body: JSON.stringify({
43856
+ ...callbackPort != null ? { callbackPort } : {},
43857
+ ...repoCanonical ? { repoCanonical } : {}
43858
+ })
43859
+ });
43860
+ const payload = await readPayload(response);
43861
+ if (!response.ok) {
43862
+ throw new VocoderAPIError({
43863
+ message: extractErrorMessage(
43864
+ payload,
43865
+ `Failed to start auth session (${response.status})`
43866
+ ),
43867
+ status: response.status,
43868
+ payload
43869
+ });
43870
+ }
43871
+ return payload;
43872
+ }
43873
+ /**
43874
+ * Poll for CLI auth session completion.
43875
+ * Returns `{ token }` on success, throws on failure/expiry.
43876
+ * The server returns HTTP 202 while still pending.
43877
+ */
43878
+ async pollCliAuthSession(pollToken) {
43879
+ const response = await fetch(
43880
+ `${this.apiUrl}/api/cli/auth/session?session=${encodeURIComponent(pollToken)}`
43881
+ );
43882
+ const payload = await readPayload(response);
43883
+ if (response.status === 202) {
43884
+ return { status: "pending" };
43885
+ }
43886
+ if (response.status === 410) {
43887
+ return {
43888
+ status: "failed",
43889
+ reason: extractErrorMessage(payload, "Auth session expired or failed")
43890
+ };
43891
+ }
43892
+ if (!response.ok) {
43893
+ return {
43894
+ status: "failed",
43895
+ reason: extractErrorMessage(
43896
+ payload,
43897
+ `Auth session error (${response.status})`
43898
+ )
43899
+ };
43900
+ }
43901
+ const result = payload;
43902
+ if (!result.token) {
43903
+ return { status: "failed", reason: "No token in response" };
43904
+ }
43905
+ return {
43906
+ status: "complete",
43907
+ token: result.token,
43908
+ ...result.organizationId ? { organizationId: result.organizationId } : {}
43909
+ };
43910
+ }
43911
+ /**
43912
+ * Validate a CLI user token and return the authenticated user's info.
43913
+ * Used by the CLI to verify stored credentials on startup.
43914
+ */
43915
+ async getCliUserInfo(userToken) {
43916
+ const response = await fetch(`${this.apiUrl}/api/cli/auth/me`, {
43917
+ headers: { Authorization: `Bearer ${userToken}` }
43918
+ });
43919
+ const payload = await readPayload(response);
43920
+ if (!response.ok) {
43921
+ throw new VocoderAPIError({
43922
+ message: extractErrorMessage(
43923
+ payload,
43924
+ `Token validation failed (${response.status})`
43925
+ ),
43926
+ status: response.status,
43927
+ payload
43928
+ });
43929
+ }
43930
+ return payload;
43931
+ }
43932
+ /**
43933
+ * Revoke the given CLI user token server-side.
43934
+ */
43935
+ async revokeCliToken(userToken) {
43936
+ const response = await fetch(`${this.apiUrl}/api/cli/auth/token`, {
43937
+ method: "DELETE",
43938
+ headers: { Authorization: `Bearer ${userToken}` }
43939
+ });
43940
+ if (!response.ok) {
43941
+ const payload = await readPayload(response);
43942
+ throw new VocoderAPIError({
43943
+ message: extractErrorMessage(
43944
+ payload,
43945
+ `Token revocation failed (${response.status})`
43946
+ ),
43947
+ status: response.status,
43948
+ payload
43949
+ });
43950
+ }
43951
+ }
43952
+ // ── Workspaces ────────────────────────────────────────────────────────────────
43953
+ async listWorkspaces(userToken, params) {
43954
+ const url = new URL(`${this.apiUrl}/api/cli/workspaces`);
43955
+ if (params?.repo) url.searchParams.set("repo", params.repo);
43956
+ const response = await fetch(url.toString(), {
43957
+ headers: { Authorization: `Bearer ${userToken}` }
43958
+ });
43959
+ const payload = await readPayload(response);
43960
+ if (!response.ok) {
43961
+ throw new VocoderAPIError({
43962
+ message: extractErrorMessage(
43963
+ payload,
43964
+ `Failed to list workspaces (${response.status})`
43965
+ ),
43966
+ status: response.status,
43967
+ payload
43968
+ });
43969
+ }
43970
+ return payload;
43971
+ }
43972
+ async listProjects(userToken, organizationId) {
43973
+ const url = new URL(`${this.apiUrl}/api/cli/projects`);
43974
+ url.searchParams.set("organizationId", organizationId);
43975
+ const response = await fetch(url.toString(), {
43976
+ headers: { Authorization: `Bearer ${userToken}` }
43977
+ });
43978
+ const payload = await readPayload(response);
43979
+ if (!response.ok) {
43980
+ throw new VocoderAPIError({
43981
+ message: extractErrorMessage(
43982
+ payload,
43983
+ `Failed to list projects (${response.status})`
43984
+ ),
43985
+ status: response.status,
43986
+ payload
43987
+ });
43988
+ }
43989
+ const result = payload;
43990
+ return result.projects;
43991
+ }
43992
+ async regenerateProjectApiKey(userToken, projectId) {
43993
+ const response = await fetch(
43994
+ `${this.apiUrl}/api/cli/project/regenerate-key`,
43995
+ {
43996
+ method: "POST",
43997
+ headers: {
43998
+ "Content-Type": "application/json",
43999
+ Authorization: `Bearer ${userToken}`
44000
+ },
44001
+ body: JSON.stringify({ projectId })
44002
+ }
44003
+ );
44004
+ const payload = await readPayload(response);
44005
+ if (!response.ok) {
44006
+ throw new VocoderAPIError({
44007
+ message: extractErrorMessage(
44008
+ payload,
44009
+ `Failed to regenerate API key (${response.status})`
44010
+ ),
44011
+ status: response.status,
44012
+ payload
44013
+ });
44014
+ }
44015
+ return payload;
44016
+ }
44017
+ // ── CLI GitHub endpoints ──────────────────────────────────────────────────────
44018
+ async startCliGitHubInstall(userToken, params) {
44019
+ const response = await fetch(
44020
+ `${this.apiUrl}/api/cli/github/install/start`,
44021
+ {
44022
+ method: "POST",
44023
+ headers: {
44024
+ Authorization: `Bearer ${userToken}`,
44025
+ "Content-Type": "application/json"
44026
+ },
44027
+ body: JSON.stringify(params)
44028
+ }
44029
+ );
44030
+ const payload = await readPayload(response);
44031
+ if (!response.ok) {
44032
+ throw new VocoderAPIError({
44033
+ message: extractErrorMessage(
44034
+ payload,
44035
+ `Failed to start GitHub install (${response.status})`
44036
+ ),
44037
+ status: response.status,
44038
+ payload
44039
+ });
44040
+ }
44041
+ return payload;
44042
+ }
44043
+ /**
44044
+ * Start the "link existing installation" discovery flow.
44045
+ * Unlike startCliGitHubOAuth, this requires no bearer token — the Vocoder
44046
+ * account is created from the OAuth code in the callback.
44047
+ */
44048
+ async startCliGitHubLinkSession(sessionId, callbackPort) {
44049
+ const response = await fetch(
44050
+ `${this.apiUrl}/api/cli/github/oauth/link-start`,
44051
+ {
44052
+ method: "POST",
44053
+ headers: { "Content-Type": "application/json" },
44054
+ body: JSON.stringify({
44055
+ sessionId,
44056
+ ...callbackPort != null ? { callbackPort } : {}
44057
+ })
44058
+ }
44059
+ );
44060
+ const payload = await readPayload(response);
44061
+ if (!response.ok) {
44062
+ throw new VocoderAPIError({
44063
+ message: extractErrorMessage(
44064
+ payload,
44065
+ `Failed to start GitHub link session (${response.status})`
44066
+ ),
44067
+ status: response.status,
44068
+ payload
44069
+ });
44070
+ }
44071
+ return payload;
44072
+ }
44073
+ async startCliGitHubOAuth(userToken, params) {
44074
+ const response = await fetch(`${this.apiUrl}/api/cli/github/oauth/start`, {
44075
+ method: "POST",
44076
+ headers: {
44077
+ Authorization: `Bearer ${userToken}`,
44078
+ "Content-Type": "application/json"
44079
+ },
44080
+ body: JSON.stringify(params)
44081
+ });
44082
+ const payload = await readPayload(response);
44083
+ if (!response.ok) {
44084
+ throw new VocoderAPIError({
44085
+ message: extractErrorMessage(
44086
+ payload,
44087
+ `Failed to start GitHub OAuth (${response.status})`
44088
+ ),
44089
+ status: response.status,
44090
+ payload
44091
+ });
44092
+ }
44093
+ return payload;
44094
+ }
44095
+ async getCliGitHubDiscovery(userToken) {
44096
+ const response = await fetch(`${this.apiUrl}/api/cli/github/discovery`, {
44097
+ headers: { Authorization: `Bearer ${userToken}` }
44098
+ });
44099
+ const payload = await readPayload(response);
44100
+ if (!response.ok) {
44101
+ throw new VocoderAPIError({
44102
+ message: extractErrorMessage(
44103
+ payload,
44104
+ `Failed to fetch GitHub discovery (${response.status})`
44105
+ ),
44106
+ status: response.status,
44107
+ payload
44108
+ });
44109
+ }
44110
+ return payload;
44111
+ }
44112
+ async claimCliGitHubInstallation(userToken, params) {
44113
+ const response = await fetch(`${this.apiUrl}/api/cli/github/claim`, {
44114
+ method: "POST",
44115
+ headers: {
44116
+ Authorization: `Bearer ${userToken}`,
44117
+ "Content-Type": "application/json"
44118
+ },
44119
+ body: JSON.stringify(params)
44120
+ });
44121
+ const payload = await readPayload(response);
44122
+ if (!response.ok) {
44123
+ throw new VocoderAPIError({
44124
+ message: extractErrorMessage(
44125
+ payload,
44126
+ `Failed to claim GitHub installation (${response.status})`
44127
+ ),
44128
+ status: response.status,
44129
+ payload
44130
+ });
44131
+ }
44132
+ return payload;
44133
+ }
44134
+ // ── Locales ───────────────────────────────────────────────────────────────────
44135
+ async listLocales(userToken) {
44136
+ const response = await fetch(`${this.apiUrl}/api/cli/locales`, {
44137
+ headers: { Authorization: `Bearer ${userToken}` }
44138
+ });
44139
+ const payload = await readPayload(response);
44140
+ if (!response.ok) {
44141
+ throw new VocoderAPIError({
44142
+ message: extractErrorMessage(
44143
+ payload,
44144
+ `Failed to list locales (${response.status})`
44145
+ ),
44146
+ status: response.status,
44147
+ payload
44148
+ });
44149
+ }
44150
+ const result = payload;
44151
+ return result;
44152
+ }
44153
+ async listCompatibleLocales(userToken, sourceLocale) {
44154
+ const url = `${this.apiUrl}/api/cli/locales/compatible?source=${encodeURIComponent(sourceLocale)}`;
44155
+ const response = await fetch(url, {
44156
+ headers: { Authorization: `Bearer ${userToken}` }
44157
+ });
44158
+ const payload = await readPayload(response);
44159
+ if (!response.ok) {
44160
+ throw new VocoderAPIError({
44161
+ message: extractErrorMessage(
44162
+ payload,
44163
+ `Failed to list compatible locales (${response.status})`
44164
+ ),
44165
+ status: response.status,
44166
+ payload
44167
+ });
44168
+ }
44169
+ const result = payload;
44170
+ return result.locales;
44171
+ }
44172
+ // ── Project creation ──────────────────────────────────────────────────────────
44173
+ async createProject(userToken, params) {
44174
+ const response = await fetch(`${this.apiUrl}/api/cli/projects`, {
44175
+ method: "POST",
44176
+ headers: {
44177
+ "Content-Type": "application/json",
44178
+ Authorization: `Bearer ${userToken}`
44179
+ },
44180
+ body: JSON.stringify(params)
44181
+ });
44182
+ const payload = await readPayload(response);
44183
+ if (!response.ok) {
44184
+ throw new VocoderAPIError({
44185
+ message: extractErrorMessage(
44186
+ payload,
44187
+ `Failed to create project (${response.status})`
44188
+ ),
44189
+ status: response.status,
44190
+ payload
44191
+ });
44192
+ }
44193
+ return payload;
44194
+ }
44195
+ // ── Project lookup ────────────────────────────────────────────────────────────
44196
+ /**
44197
+ * Look up all project apps for a given repo. Returns info about exact matches,
44198
+ * existing apps in other scopes, and whether a whole-repo app exists.
44199
+ * No auth required.
44200
+ */
44201
+ async lookupProjectByRepo(params) {
44202
+ try {
44203
+ const response = await fetch(`${this.apiUrl}/api/cli/init/lookup`, {
44204
+ method: "POST",
44205
+ headers: { "Content-Type": "application/json" },
44206
+ body: JSON.stringify({
44207
+ repo: params.repoCanonical,
44208
+ appDir: params.appDir
44209
+ })
44210
+ });
44211
+ if (!response.ok) {
44212
+ return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
44213
+ }
44214
+ const data = await response.json();
44215
+ return {
44216
+ exactMatch: data.exactMatch ?? null,
44217
+ existingApps: data.existingApps ?? [],
44218
+ hasWholeRepoApp: data.hasWholeRepoApp ?? false
44219
+ };
44220
+ } catch {
44221
+ return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
44222
+ }
44223
+ }
44224
+ /**
44225
+ * Add a new ProjectApp to an existing project (monorepo: new app directory).
44226
+ * Does not check plan limits — no new project is created.
44227
+ */
44228
+ async createProjectApp(userToken, params) {
44229
+ const response = await fetch(`${this.apiUrl}/api/cli/project/apps`, {
44230
+ method: "POST",
44231
+ headers: {
44232
+ "Content-Type": "application/json",
44233
+ Authorization: `Bearer ${userToken}`
44234
+ },
44235
+ body: JSON.stringify(params)
44236
+ });
44237
+ const payload = await readPayload(response);
44238
+ if (!response.ok) {
44239
+ throw new VocoderAPIError({
44240
+ message: extractErrorMessage(
44241
+ payload,
44242
+ `Failed to create project app (${response.status})`
44243
+ ),
44244
+ status: response.status,
44245
+ payload
44246
+ });
44247
+ }
44248
+ return payload;
44249
+ }
44250
+ };
44251
+
43571
44252
  // src/utils/detect-local.ts
43572
44253
  import { existsSync, readFileSync } from "fs";
43573
44254
  import { join } from "path";
@@ -43679,6 +44360,48 @@ function getPackagesToInstall(detection) {
43679
44360
  return { devPackages, runtimePackages };
43680
44361
  }
43681
44362
 
44363
+ // src/utils/auth-store.ts
44364
+ import { mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
44365
+ import { homedir } from "os";
44366
+ import { dirname, join as join2 } from "path";
44367
+ function getAuthFilePath() {
44368
+ return join2(homedir(), ".config", "vocoder", "auth.json");
44369
+ }
44370
+ function readAuthData() {
44371
+ const filePath = getAuthFilePath();
44372
+ try {
44373
+ const raw = readFileSync2(filePath, "utf8");
44374
+ const parsed = JSON.parse(raw);
44375
+ if (!parsed || typeof parsed !== "object") return null;
44376
+ const data = parsed;
44377
+ if (typeof data.token !== "string" || typeof data.userId !== "string" || typeof data.email !== "string" || typeof data.createdAt !== "string") {
44378
+ return null;
44379
+ }
44380
+ return {
44381
+ token: data.token,
44382
+ userId: data.userId,
44383
+ email: data.email,
44384
+ name: typeof data.name === "string" ? data.name : null,
44385
+ createdAt: data.createdAt
44386
+ };
44387
+ } catch {
44388
+ return null;
44389
+ }
44390
+ }
44391
+ function writeAuthData(data) {
44392
+ const filePath = getAuthFilePath();
44393
+ const dir = dirname(filePath);
44394
+ mkdirSync(dir, { recursive: true, mode: 448 });
44395
+ writeFileSync(filePath, JSON.stringify(data, null, 2), { mode: 384 });
44396
+ }
44397
+ function clearAuthData() {
44398
+ const filePath = getAuthFilePath();
44399
+ try {
44400
+ unlinkSync(filePath);
44401
+ } catch {
44402
+ }
44403
+ }
44404
+
43682
44405
  // src/utils/setup-snippets.ts
43683
44406
  function getSetupSnippets(params) {
43684
44407
  const { framework, ecosystem, sourceLocale } = params;
@@ -43840,28 +44563,28 @@ import { T } from '@vocoder/vue';
43840
44563
  // ../extractor/src/config.ts
43841
44564
  var import_parser = __toESM(require_lib());
43842
44565
  var import_traverse = __toESM(require_lib8());
43843
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
43844
- import { join as join2 } from "path";
44566
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
44567
+ import { join as join3 } from "path";
43845
44568
  var traverse = import_traverse.default.default || import_traverse.default;
43846
44569
  function loadVocoderConfig(cwd) {
43847
44570
  const candidates = [
43848
- join2(cwd, "vocoder.config.ts"),
43849
- join2(cwd, "vocoder.config.js"),
43850
- join2(cwd, "vocoder.config.mjs"),
43851
- join2(cwd, "vocoder.config.cjs")
44571
+ join3(cwd, "vocoder.config.ts"),
44572
+ join3(cwd, "vocoder.config.js"),
44573
+ join3(cwd, "vocoder.config.mjs"),
44574
+ join3(cwd, "vocoder.config.cjs")
43852
44575
  ];
43853
44576
  for (const configPath of candidates) {
43854
44577
  if (!existsSync2(configPath)) continue;
43855
44578
  try {
43856
- const code = readFileSync2(configPath, "utf-8");
44579
+ const code = readFileSync3(configPath, "utf-8");
43857
44580
  return parseConfigFromSource(code);
43858
44581
  } catch {
43859
44582
  }
43860
44583
  }
43861
- const jsonPath = join2(cwd, "vocoder.config.json");
44584
+ const jsonPath = join3(cwd, "vocoder.config.json");
43862
44585
  if (existsSync2(jsonPath)) {
43863
44586
  try {
43864
- return JSON.parse(readFileSync2(jsonPath, "utf-8"));
44587
+ return JSON.parse(readFileSync3(jsonPath, "utf-8"));
43865
44588
  } catch {
43866
44589
  return null;
43867
44590
  }
@@ -43922,7 +44645,7 @@ function extractFromObject(obj) {
43922
44645
  // ../extractor/src/index.ts
43923
44646
  var import_parser2 = __toESM(require_lib());
43924
44647
  var import_traverse2 = __toESM(require_lib8());
43925
- import { readFileSync as readFileSync3 } from "fs";
44648
+ import { readFileSync as readFileSync4 } from "fs";
43926
44649
  import { relative as pathRelative } from "path";
43927
44650
 
43928
44651
  // ../../node_modules/.pnpm/minimatch@9.0.5/node_modules/minimatch/dist/esm/index.js
@@ -50399,7 +51122,7 @@ var StringExtractor = class {
50399
51122
  const sortedFiles = Array.from(allFiles).sort();
50400
51123
  for (const file of sortedFiles) {
50401
51124
  try {
50402
- const code = readFileSync3(file, "utf-8");
51125
+ const code = readFileSync4(file, "utf-8");
50403
51126
  const relPath = pathRelative(projectRoot, file).split("\\").join("/");
50404
51127
  const strings = _extractFromContent(relPath, code);
50405
51128
  allStrings.push(...strings);
@@ -50481,25 +51204,6 @@ function detectUiRole(path2) {
50481
51204
  }
50482
51205
  return "unknown";
50483
51206
  }
50484
- var FEATURE_PATTERNS = [
50485
- [/\/(checkout|cart|basket)/, "checkout"],
50486
- [/\/(auth|login|signup|sign-up|register|forgot|reset-password|verify)/, "auth"],
50487
- [/\/(onboarding|welcome|setup)/, "onboarding"],
50488
- [/\/(dashboard|overview|home)/, "dashboard"],
50489
- [/\/(settings|preferences|profile|account)/, "settings"],
50490
- [/\/(search|results|explore)/, "search"],
50491
- [/\/(product|item|detail|pdp)/, "product"],
50492
- [/\/(pricing|plans|billing|upgrade)/, "pricing"],
50493
- [/\/(admin|manage)/, "admin"],
50494
- [/\/(help|support|faq|docs)/, "support"]
50495
- ];
50496
- function inferFeatureArea(filePath) {
50497
- const normalized = filePath.replace(/\\/g, "/").toLowerCase();
50498
- for (const [pattern, area] of FEATURE_PATTERNS) {
50499
- if (pattern.test(normalized)) return area;
50500
- }
50501
- return void 0;
50502
- }
50503
51207
  function _extractFromContent(filePath, content) {
50504
51208
  const strings = [];
50505
51209
  try {
@@ -50573,7 +51277,6 @@ function _extractFromContent(filePath, content) {
50573
51277
  const line = path2.node.loc?.start.line || 0;
50574
51278
  const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
50575
51279
  const uiRole = detectUiRole(path2);
50576
- const featureArea = inferFeatureArea(filePath);
50577
51280
  strings.push({
50578
51281
  key,
50579
51282
  text: text.trim(),
@@ -50581,8 +51284,7 @@ function _extractFromContent(filePath, content) {
50581
51284
  line,
50582
51285
  context,
50583
51286
  formality,
50584
- uiRole: uiRole !== "unknown" ? uiRole : void 0,
50585
- featureArea
51287
+ uiRole: uiRole !== "unknown" ? uiRole : void 0
50586
51288
  });
50587
51289
  },
50588
51290
  JSXElement: (path2) => {
@@ -50613,7 +51315,6 @@ function _extractFromContent(filePath, content) {
50613
51315
  const line = path2.node.loc?.start.line || 0;
50614
51316
  const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
50615
51317
  const uiRole = detectUiRole(path2);
50616
- const featureArea = inferFeatureArea(filePath);
50617
51318
  strings.push({
50618
51319
  key,
50619
51320
  text: text.trim(),
@@ -50621,8 +51322,7 @@ function _extractFromContent(filePath, content) {
50621
51322
  line,
50622
51323
  context,
50623
51324
  formality,
50624
- uiRole: uiRole !== "unknown" ? uiRole : void 0,
50625
- featureArea
51325
+ uiRole: uiRole !== "unknown" ? uiRole : void 0
50626
51326
  });
50627
51327
  }
50628
51328
  });
@@ -50730,11 +51430,16 @@ function deduplicateStrings(strings) {
50730
51430
  }
50731
51431
 
50732
51432
  export {
51433
+ VocoderAPIError,
51434
+ VocoderAPI,
50733
51435
  detectLocalEcosystem,
50734
51436
  buildInstallCommand,
50735
51437
  getPackagesToInstall,
51438
+ readAuthData,
51439
+ writeAuthData,
51440
+ clearAuthData,
50736
51441
  getSetupSnippets,
50737
51442
  loadVocoderConfig,
50738
51443
  StringExtractor
50739
51444
  };
50740
- //# sourceMappingURL=chunk-73U4VZYP.mjs.map
51445
+ //# sourceMappingURL=chunk-XF3KGGYQ.mjs.map