@keystrokehq/cli 0.1.139 → 0.1.140

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.
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import { A as isExpectedCliError, C as getPlatformUrl, D as resolvePlatformUrlForWebUrl, O as parseCredentialStorage, S as getEffectiveApiTarget, T as DEFAULT_PLATFORM_URL, _ as resolveCliRoot, a as formatReleaseAgeBlockMessage, b as getConfigDir, c as detectManagerFromLockfile, d as installDependencies$1, f as installPlaygroundDependencies, g as readCliVersion, h as isNewerVersion, i as fetchLatestCliRelease, k as ExpectedCliError, l as buildPlaygroundWorkspace, m as resolvePackageManager, n as computeReleaseAgeRetryAfter, o as isReleaseAgeBlock, p as resolveGithubPackagesToken, r as readPnpmMinimumReleaseAgeMinutes, s as detectCliInstall, t as runPackageManagerUpdate, u as detectPackageManager, v as createCliConfig, w as getWebUrl, x as getCredentialStorage, y as getCliConfigDir } from "./run-package-manager-update-DS5Xr8oA.mjs";
2
3
  import { $ as CredentialInstanceListResponseSchema, $n as UpdateOrganizationMemberRequestSchema, $t as OrganizationSidebarBrandingSchema, A as ConnectAuthorizeUrlResponseSchema, An as SkillSummaryListResponseSchema, Ar as WorkspaceTriggerRunListResponseSchema, At as ListAgentMemoryFilesResponseSchema, B as CreateCustomAppRequestSchema, Bn as SubmitTeamRequestRequestSchema, Br as parseAppSlug, Bt as ListOrganizationMembersPageResponseSchema, C as ChannelAccountListResponseSchema, Cn as PublishedFormListResponseSchema, Cr as WorkflowRunResponseSchema, Ct as HistoryRunListQuerySchema, D as CompleteProjectArtifactResponseSchema, Dr as WorkspaceTriggerFileSchema, Dt as InviteProjectMembersRequestSchema, E as ChannelDirectoryListResponseSchema, En as QueuedRunResponseSchema, Er as WorkspaceTriggerDetailSchema, Et as InviteOrganizationMembersResponseSchema, F as CreateBillingPortalRequestSchema, Fn as StartMcpOAuthConnectionResultSchema, Fr as detectProjectPackageManagerFromSnapshot, Ft as ListCredentialsPageQuerySchema, G as CreateProjectRequestSchema, Gn as TriggerListResponseSchema, Gr as resolveDocsMcpUrl, Gt as ListProjectMetricsResponseSchema, H as CreateOrganizationRequestSchema, Hn as TriggerDetailResponseSchema, Ht as ListProjectDeploymentsResponseSchema, I as CreateCredentialInstanceBodySchema, In as StartOAuthConnectionInputSchema, Ir as isAcceptableInstallExit, It as ListCredentialsPageResponseSchema, J as CredentialAssignmentListQuerySchema, Jn as UpdateAutoTopupRequestSchema, Jt as ManagedServiceCredentialSchema, K as CreateProjectResponseSchema, Kn as TriggerRunDetailResponseSchema, Kr as resolvePublicPlatformOrigin, Kt as ListProjectsResponseSchema, L as CreateCredentialsRequestSchema, Ln as StartOAuthConnectionResultSchema, Lr as listenPortFromPublicUrl, Lt as ListManagedServiceCredentialsResponseSchema, M as ConnectProvidersResponseSchema, Mn as StartKeystrokeConnectionInputSchema, Mr as buildConnectDeeplink, Mt as ListApiKeysResponseSchema, N as CreateApiKeyRequestSchema, Nn as StartKeystrokeConnectionResultSchema, Nt as ListAppsResponseSchema, O as ConfirmCheckoutRequestSchema, On as RecentResourceListResponseSchema, Or as WorkspaceTriggerListResponseSchema, Ot as InviteProjectMembersResponseSchema, P as CreateApiKeyResponseSchema, Pn as StartMcpOAuthConnectionInputSchema, Pr as deriveCustomAppDisplay, Pt as ListChannelPlatformsResponseSchema, Q as CredentialConsumerListResponseSchema, Qn as UpdateManagedServiceCredentialRequestSchema, Qt as OrganizationSidebarBrandingPatchSchema, R as CreateCredentialsResponseSchema, Rn as SubmitAgentFeedbackRequestSchema, Rt as ListOrganizationInvitationsResponseSchema, S as CatalogAppsPageResponseSchema, Sr as WorkflowRunListResponseSchema, St as HistoryRunDetailResponseSchema, T as ChannelConnectionSchema, Tn as QueuedAgentPromptResponseSchema, Tr as WorkflowSummaryListResponseSchema, Tt as InviteOrganizationMembersRequestSchema, U as CreateOrganizationResponseSchema, Un as TriggerInvokeInputsSchema, Ut as ListProjectFilesResponseSchema, V as CreateCustomAppResponseSchema, Vn as TemplateFilesResponseSchema, Vr as parseErrorResponse, Vt as ListOrganizationsResponseSchema, W as CreateProjectArtifactResponseSchema, Wn as TriggerInvokeResponseSchema, Wr as resolveConnectAppSlug, Wt as ListProjectMembersResponseSchema, X as CredentialAssignmentRecordSchema, Xn as UpdateCredentialInstanceBodySchema, Xt as McpDiscoverResponseSchema, Y as CredentialAssignmentListResponseSchema, Yn as UpdateChannelBindingBodySchema, Yt as ManagedServiceKindSchema, Z as CredentialConsumerListQuerySchema, Zn as UpdateCredentialRequestSchema, Zt as OpenApiDiscoverResponseSchema, _ as BindChannelBodySchema, _n as PromptInputSchema, _r as WorkflowFormUpsertBodySchema, _t as GetCustomAppResponseSchema, a as AgentSessionListResponseSchema, an as PresignCustomAppLogoResponseSchema, ar as UpdateProjectSettingsRequestSchema, at as DownloadActiveProjectArtifactResponseSchema, b as CatalogActionsPageResponseSchema, bn as PublicFormSubmitBodySchema, br as WorkflowRunInputsPutBodySchema, bt as HealthResponseSchema, c as AgentTriggerSummaryListResponseSchema, cn as PresignProjectSourceRequestSchema, cr as UpsertGatewayAttachmentBodySchema, ct as DuplicateCredentialsResponseSchema, d as AutoTopupSummarySchema, dn as PresignUserAvatarResponseSchema, dr as UserPreferencesPatchSchema, dt as FinalizeCustomAppLogoRequestSchema, en as PROJECT_PULL_STATE_RELATIVE_PATH, er as UpdateOrganizationMemberResponseSchema, et as CredentialInstanceRecordSchema, f as BillingActivityResponseSchema, fn as ProjectPullStateSchema, fr as UserPreferencesSchema, ft as FinalizeCustomAppLogoResponseSchema, g as BillingUsageResponseSchema, gn as ProjectSlugAvailabilityResponseSchema, gr as WorkflowFormResponseSchema, gt as GetCredentialResponseSchema, h as BillingSummaryResponseSchema, hn as ProjectSettingsResponseSchema, hr as WorkflowCanvasSchema, ht as GetAppCatalogEntryResponseSchema, i as AgentSessionDetailResponseSchema, in as PresignCustomAppLogoRequestSchema, ir as UpdateProjectRequestSchema, it as DeclineOrganizationInvitationResponseSchema, j as ConnectManagedServiceCredentialRequestSchema, jn as SlugAvailabilityResponseSchema, jr as WorkspaceWorkflowOverviewSchema, jt as ListAgentWorkspaceFilesResponseSchema, k as ConfirmCheckoutResponseSchema, kn as SkillSummaryDetailResponseSchema, kr as WorkspaceTriggerOverviewSchema, l as AppSlugAvailabilityResponseSchema, ln as PresignProjectSourceResponseSchema, lr as UserAvatarPatchSchema, lt as ExecuteKeystrokeToolRequestSchema, m as BillingRedirectResponseSchema, mn as ProjectResponseSchema, mr as WorkflowCanvasRunSchema, mt as GatewayAttachmentRecordSchema, n as AcceptOrganizationInvitationResponseSchema, nn as PresignChatAttachmentRequestSchema, nr as UpdateProjectMemberRequestSchema, nt as DOCS_QUERY_TOOL, o as AgentSummaryDetailResponseSchema, on as PresignOrgLogoRequestSchema, or as UploadProjectSourceManifestRequestSchema, ot as DownloadActiveProjectSourceResponseSchema, p as BillingInvoiceUrlResponseSchema, pn as ProjectReachabilityResponseSchema, pr as WorkflowCanvasCredentialBindingsSchema, pt as FormFieldConfigSchema, q as CreateSubscriptionCheckoutRequestSchema, qn as TriggerRunListResponseSchema, qr as slugifyAppName, qt as ListTemplatesResponseSchema, r as ActiveOrganizationResponseSchema, rn as PresignChatAttachmentResponseSchema, rr as UpdateProjectMemberResponseSchema, rt as DOCS_SEARCH_TOOL, s as AgentSummaryListResponseSchema, sn as PresignOrgLogoResponseSchema, sr as UploadProjectSourceResponseSchema, st as DuplicateCredentialRequestSchema, t as ACTIVE_ORG_HEADER, tn as PROJECT_REACHABILITY_REQUEST_TIMEOUT_MS, tr as UpdateOrganizationRequestSchema, u as AssignCredentialBodySchema, un as PresignUserAvatarRequestSchema, ur as UserAvatarSchema, v as CLIENT_CHANNEL_HEADER, vn as PromptResponseSchema, vr as WorkflowRunDetailResponseSchema, vt as GetTemplateResponseSchema, w as ChannelConnectionListResponseSchema, wn as PublishedFormSchema, wr as WorkflowSummaryDetailResponseSchema, wt as HistoryRunListResponseSchema, x as CatalogAppDetailResponseSchema, xn as PublicFormSubmitResponseSchema, xr as WorkflowRunInputsSchema, xt as HistoryRunCancelResponseSchema, y as CatalogActionDetailResponseSchema, yn as PublicFormMetadataSchema, yr as WorkflowRunHooksResponseSchema, yt as GraphqlDiscoverResponseSchema, z as CreateCreditsCheckoutRequestSchema, zn as SubmitMarketingContactRequestSchema, zt as ListOrganizationMembersPageQuerySchema } from "./dist-BcY5XTlp.mjs";
3
- import { C as getWebUrl, E as resolvePlatformUrlForWebUrl, S as getPlatformUrl, _ as resolveCliRoot, a as formatReleaseAgeBlockMessage, b as getConfigDir, c as detectManagerFromLockfile, d as installDependencies$1, f as installPlaygroundDependencies, g as readCliVersion, h as isNewerVersion, i as fetchLatestCliRelease, l as buildPlaygroundWorkspace, m as resolvePackageManager, n as computeReleaseAgeRetryAfter, o as isReleaseAgeBlock, p as resolveGithubPackagesToken, r as readPnpmMinimumReleaseAgeMinutes, s as detectCliInstall, t as runPackageManagerUpdate, u as detectPackageManager, v as createCliConfig, w as DEFAULT_PLATFORM_URL, x as getEffectiveApiTarget, y as getCliConfigDir } from "./run-package-manager-update-CQT2HD0m.mjs";
4
- import { a as alias, c as event, i as packProjectArtifact, l as flushTelemetry, n as withMcpReadClient, o as captureException, r as mergeFilteredArtifact, s as configureTelemetry, t as mapInParallelBatches, u as shutdownTelemetry } from "./dist-BkXtCIR2.mjs";
4
+ import { a as alias, c as event, i as packProjectArtifact, l as flushTelemetry, n as withMcpReadClient, o as captureException, r as mergeFilteredArtifact, s as configureTelemetry, t as mapInParallelBatches, u as shutdownTelemetry } from "./dist-DaDxDpeX.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
7
7
  import { platform, release } from "node:os";
8
8
  import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
9
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import { spawn, spawnSync } from "node:child_process";
9
11
  import { Entry } from "@napi-rs/keyring";
10
- import { existsSync, lstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
11
12
  import { PostHog } from "posthog-node";
12
13
  import { pathToFileURL } from "node:url";
13
14
  import { access, lstat, mkdir, readFile, readdir, rm, stat, symlink, writeFile } from "node:fs/promises";
14
- import { spawn, spawnSync } from "node:child_process";
15
15
  import { confirm, input } from "@inquirer/prompts";
16
16
  //#region ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/KyError.js
17
17
  /**
@@ -1640,32 +1640,107 @@ function createKeystrokeClient(options) {
1640
1640
  };
1641
1641
  }
1642
1642
  //#endregion
1643
- //#region src/errors/expected-cli-error.ts
1644
- /**
1645
- * User/project failures that are expected CLI exits (lint, typecheck, validation, etc.).
1646
- * These should not become Error Tracking issues.
1647
- */
1648
- var ExpectedCliError = class extends Error {
1649
- constructor(message, options) {
1650
- super(message, options);
1651
- this.name = "ExpectedCliError";
1643
+ //#region src/auth/credentials-file.ts
1644
+ const CREDENTIALS_FILE_NAME = "credentials.json";
1645
+ function accountForUrl$2(url) {
1646
+ return new URL(url).hostname;
1647
+ }
1648
+ function credentialsFilePath(configDir = getCliConfigDir()) {
1649
+ return join(configDir, CREDENTIALS_FILE_NAME);
1650
+ }
1651
+ function assertSafeCredentialsPath(path) {
1652
+ if (!existsSync(path)) return;
1653
+ const stats = lstatSync(path);
1654
+ if (stats.isSymbolicLink() || !stats.isFile()) throw new ExpectedCliError(`Refusing to use credentials path that is not a regular file: ${path}`);
1655
+ }
1656
+ function ensureCredentialsDir(path) {
1657
+ const dir = dirname(path);
1658
+ mkdirSync(dir, {
1659
+ recursive: true,
1660
+ mode: 448
1661
+ });
1662
+ try {
1663
+ chmodSync(dir, 448);
1664
+ } catch {}
1665
+ }
1666
+ function emptyCredentials() {
1667
+ return { tokens: {} };
1668
+ }
1669
+ function readCredentialsFile(path) {
1670
+ assertSafeCredentialsPath(path);
1671
+ if (!existsSync(path)) return emptyCredentials();
1672
+ let parsed;
1673
+ try {
1674
+ parsed = JSON.parse(readFileSync(path, "utf8"));
1675
+ } catch {
1676
+ throw new ExpectedCliError(`Credentials file is not valid JSON: ${path}`);
1677
+ }
1678
+ if (typeof parsed !== "object" || parsed === null || !("tokens" in parsed) || typeof parsed.tokens !== "object" || parsed.tokens === null || Array.isArray(parsed.tokens)) throw new ExpectedCliError(`Credentials file has an invalid shape: ${path}`);
1679
+ const tokens = {};
1680
+ for (const [key, value] of Object.entries(parsed.tokens)) if (typeof value === "string" && value.length > 0) tokens[key] = value;
1681
+ return { tokens };
1682
+ }
1683
+ function writeCredentialsFile(path, data) {
1684
+ ensureCredentialsDir(path);
1685
+ assertSafeCredentialsPath(path);
1686
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
1687
+ const payload = `${JSON.stringify(data, null, 2)}\n`;
1688
+ try {
1689
+ writeFileSync(tempPath, payload, {
1690
+ encoding: "utf8",
1691
+ mode: 384,
1692
+ flag: "wx"
1693
+ });
1694
+ try {
1695
+ chmodSync(tempPath, 384);
1696
+ } catch {}
1697
+ renameSync(tempPath, path);
1698
+ try {
1699
+ chmodSync(path, 384);
1700
+ } catch {}
1701
+ } catch (error) {
1702
+ try {
1703
+ unlinkSync(tempPath);
1704
+ } catch {}
1705
+ throw error;
1652
1706
  }
1653
- };
1654
- function isExpectedCliError(error) {
1655
- return error instanceof ExpectedCliError;
1656
1707
  }
1657
- //#endregion
1658
- //#region src/auth/credentials.ts
1659
- const SERVICE = "keystroke";
1660
- const KEYCHAIN_UNAVAILABLE_MESSAGE = "OS keychain is unavailable or locked. For headless environments, set KEYSTROKE_API_KEY instead of using `keystroke auth login`.";
1661
- function accountForWebUrl(webUrl) {
1662
- return new URL(webUrl).hostname;
1708
+ function getFileToken(url, configDir = getCliConfigDir()) {
1709
+ return readCredentialsFile(credentialsFilePath(configDir)).tokens[accountForUrl$2(url)];
1710
+ }
1711
+ function setFileToken(url, token, configDir = getCliConfigDir()) {
1712
+ const path = credentialsFilePath(configDir);
1713
+ const data = readCredentialsFile(path);
1714
+ data.tokens[accountForUrl$2(url)] = token;
1715
+ writeCredentialsFile(path, data);
1716
+ }
1717
+ function clearFileToken(url, configDir = getCliConfigDir()) {
1718
+ const path = credentialsFilePath(configDir);
1719
+ if (!existsSync(path)) return;
1720
+ const account = accountForUrl$2(url);
1721
+ const data = readCredentialsFile(path);
1722
+ if (!(account in data.tokens)) return;
1723
+ delete data.tokens[account];
1724
+ if (Object.keys(data.tokens).length === 0) {
1725
+ assertSafeCredentialsPath(path);
1726
+ unlinkSync(path);
1727
+ return;
1728
+ }
1729
+ writeCredentialsFile(path, data);
1663
1730
  }
1731
+ //#endregion
1732
+ //#region src/auth/credentials-keychain.ts
1733
+ /** Stable service for the Apple `/usr/bin/security` backend (not Node-bound). */
1734
+ const KEYCHAIN_SERVICE = "keystroke-cli";
1735
+ /** Legacy `@napi-rs/keyring` service — migrated once into KEYCHAIN_SERVICE on macOS. */
1736
+ const LEGACY_KEYCHAIN_SERVICE = "keystroke";
1737
+ const SECURITY_BIN = "/usr/bin/security";
1738
+ const KEYCHAIN_UNAVAILABLE_MESSAGE = "OS keychain is unavailable or locked. For headless environments, set KEYSTROKE_API_KEY instead of using `keystroke auth login`, or run `keystroke auth login --credential-storage file`.";
1664
1739
  /** Match storage-specific keychain/keyring failures only — avoid generic OS permission phrases. */
1665
1740
  function isKeychainFailure(error) {
1666
1741
  if (!(error instanceof Error)) return false;
1667
1742
  const message = error.message.toLowerCase();
1668
- return message.includes("keychain") || message.includes("keyring") || message.includes("secret service") || message.includes("libsecret") || message.includes("password store") || message.includes("no password store") || message.includes("dbus") || message.includes("no such interface") || message.includes("platform secure storage");
1743
+ return message.includes("keychain") || message.includes("keyring") || message.includes("secret service") || message.includes("libsecret") || message.includes("password store") || message.includes("no password store") || message.includes("dbus") || message.includes("no such interface") || message.includes("platform secure storage") || message.includes("user interaction is not allowed") || message.includes("errsec") || message.includes("keychain access failed via security") || /security.*(denied|locked|unable|failed)/i.test(message);
1669
1744
  }
1670
1745
  function withKeychain(fn) {
1671
1746
  try {
@@ -1675,23 +1750,224 @@ function withKeychain(fn) {
1675
1750
  throw error;
1676
1751
  }
1677
1752
  }
1678
- function getAccessToken(webUrl) {
1753
+ function accountForUrl$1(url) {
1754
+ return new URL(url).hostname;
1755
+ }
1756
+ function runSecurity(args, input) {
1757
+ const result = spawnSync(SECURITY_BIN, args, {
1758
+ encoding: "utf8",
1759
+ input,
1760
+ stdio: [
1761
+ "pipe",
1762
+ "pipe",
1763
+ "pipe"
1764
+ ]
1765
+ });
1766
+ if (result.error) throw result.error;
1767
+ return {
1768
+ status: result.status,
1769
+ stdout: result.stdout ?? "",
1770
+ stderr: result.stderr ?? ""
1771
+ };
1772
+ }
1773
+ function securityErrorMessage(result) {
1774
+ const detail = (result.stderr || result.stdout).trim();
1775
+ return detail.length > 0 ? `Keychain access failed via security: ${detail}` : `Keychain access failed via security (exit ${result.status ?? "unknown"})`;
1776
+ }
1777
+ /** errSecItemNotFound — item does not exist. */
1778
+ function isSecurityNotFound(result) {
1779
+ if (result.status === 44) return true;
1780
+ const text = `${result.stderr}\n${result.stdout}`.toLowerCase();
1781
+ return text.includes("could not be found") || text.includes("item not found");
1782
+ }
1783
+ function getViaSecurity(account, service) {
1784
+ const result = runSecurity([
1785
+ "find-generic-password",
1786
+ "-s",
1787
+ service,
1788
+ "-a",
1789
+ account,
1790
+ "-w"
1791
+ ]);
1792
+ if (isSecurityNotFound(result)) return;
1793
+ if (result.status !== 0) throw new Error(securityErrorMessage(result));
1794
+ const password = result.stdout.replace(/\n$/, "");
1795
+ return password.length > 0 ? password : void 0;
1796
+ }
1797
+ function setViaSecurity(account, service, token) {
1798
+ if (token.length === 0) throw new Error("Refusing to store an empty keychain token");
1799
+ const result = runSecurity([
1800
+ "add-generic-password",
1801
+ "-U",
1802
+ "-s",
1803
+ service,
1804
+ "-a",
1805
+ account,
1806
+ "-w",
1807
+ token
1808
+ ]);
1809
+ if (result.status !== 0) throw new Error(securityErrorMessage(result));
1810
+ }
1811
+ function deleteViaSecurity(account, service) {
1812
+ const result = runSecurity([
1813
+ "delete-generic-password",
1814
+ "-s",
1815
+ service,
1816
+ "-a",
1817
+ account
1818
+ ]);
1819
+ if (isSecurityNotFound(result)) return;
1820
+ if (result.status !== 0) throw new Error(securityErrorMessage(result));
1821
+ }
1822
+ function getLegacyNapi(account) {
1823
+ const password = new Entry(LEGACY_KEYCHAIN_SERVICE, account).getPassword();
1824
+ return password === null ? void 0 : password;
1825
+ }
1826
+ function clearLegacyNapi(account) {
1827
+ const entry = new Entry(LEGACY_KEYCHAIN_SERVICE, account);
1828
+ try {
1829
+ entry.deletePassword();
1830
+ } catch {}
1831
+ }
1832
+ function getNapi(account, service = KEYCHAIN_SERVICE) {
1833
+ const password = new Entry(service, account).getPassword();
1834
+ return password === null ? void 0 : password;
1835
+ }
1836
+ function setNapi(account, token, service = KEYCHAIN_SERVICE) {
1837
+ new Entry(service, account).setPassword(token);
1838
+ }
1839
+ function deleteNapi(account, service = KEYCHAIN_SERVICE) {
1840
+ new Entry(service, account).deletePassword();
1841
+ }
1842
+ function migrateLegacyMacToken(account) {
1843
+ let legacy;
1844
+ try {
1845
+ legacy = getLegacyNapi(account);
1846
+ } catch (error) {
1847
+ if (isKeychainFailure(error)) throw error;
1848
+ return;
1849
+ }
1850
+ if (!legacy) return;
1851
+ setViaSecurity(account, KEYCHAIN_SERVICE, legacy);
1852
+ if (getViaSecurity(account, "keystroke-cli") !== legacy) throw new Error("Keychain migration failed: wrote legacy token via security but could not read it back");
1853
+ clearLegacyNapi(account);
1854
+ return legacy;
1855
+ }
1856
+ function getKeychainToken(url) {
1679
1857
  return withKeychain(() => {
1680
- const password = new Entry(SERVICE, accountForWebUrl(webUrl)).getPassword();
1681
- return password === null ? void 0 : password;
1858
+ const account = accountForUrl$1(url);
1859
+ if (process.platform === "darwin") {
1860
+ const current = getViaSecurity(account, KEYCHAIN_SERVICE);
1861
+ if (current !== void 0) return current;
1862
+ return migrateLegacyMacToken(account);
1863
+ }
1864
+ return getNapi(account);
1682
1865
  });
1683
1866
  }
1684
- function setAccessToken(webUrl, token) {
1867
+ function setKeychainToken(url, token) {
1685
1868
  withKeychain(() => {
1686
- new Entry(SERVICE, accountForWebUrl(webUrl)).setPassword(token);
1869
+ const account = accountForUrl$1(url);
1870
+ if (process.platform === "darwin") {
1871
+ setViaSecurity(account, KEYCHAIN_SERVICE, token);
1872
+ clearLegacyNapi(account);
1873
+ return;
1874
+ }
1875
+ setNapi(account, token);
1687
1876
  });
1688
1877
  }
1689
- function clearAccessToken(webUrl) {
1878
+ function clearKeychainToken(url) {
1690
1879
  withKeychain(() => {
1691
- new Entry(SERVICE, accountForWebUrl(webUrl)).deletePassword();
1880
+ const account = accountForUrl$1(url);
1881
+ if (process.platform === "darwin") {
1882
+ deleteViaSecurity(account, KEYCHAIN_SERVICE);
1883
+ clearLegacyNapi(account);
1884
+ return;
1885
+ }
1886
+ try {
1887
+ deleteNapi(account);
1888
+ } catch (error) {
1889
+ if (error instanceof Error && /no.?entry|not found|password not found/i.test(error.message)) return;
1890
+ throw error;
1891
+ }
1692
1892
  });
1693
1893
  }
1694
1894
  //#endregion
1895
+ //#region src/auth/credentials.ts
1896
+ const MISSING = Symbol("missing");
1897
+ const tokenCache = /* @__PURE__ */ new Map();
1898
+ function accountForUrl(url) {
1899
+ return new URL(url).hostname;
1900
+ }
1901
+ function resolveStorage(storage) {
1902
+ return storage ?? "keychain";
1903
+ }
1904
+ function cacheKey$1(url, storage) {
1905
+ return `${storage}:${accountForUrl(url)}`;
1906
+ }
1907
+ function readFromBackend(url, storage, configDir) {
1908
+ if (storage === "file") return getFileToken(url, configDir);
1909
+ return getKeychainToken(url);
1910
+ }
1911
+ function writeToBackend(url, token, storage, configDir) {
1912
+ if (storage === "file") {
1913
+ setFileToken(url, token, configDir);
1914
+ return;
1915
+ }
1916
+ setKeychainToken(url, token);
1917
+ }
1918
+ function deleteFromBackend(url, storage, configDir) {
1919
+ if (storage === "file") {
1920
+ clearFileToken(url, configDir);
1921
+ return;
1922
+ }
1923
+ clearKeychainToken(url);
1924
+ }
1925
+ /** Clear the in-process credential cache (tests / logout). */
1926
+ function clearAccessTokenCache() {
1927
+ tokenCache.clear();
1928
+ }
1929
+ function getAccessToken(url, options = {}) {
1930
+ const storage = resolveStorage(options.storage);
1931
+ const key = cacheKey$1(url, storage);
1932
+ const cached = tokenCache.get(key);
1933
+ if (cached !== void 0) return cached === MISSING ? void 0 : cached;
1934
+ const token = readFromBackend(url, storage, options.configDir);
1935
+ tokenCache.set(key, token === void 0 ? MISSING : token);
1936
+ return token;
1937
+ }
1938
+ function setAccessToken(url, token, options = {}) {
1939
+ const storage = resolveStorage(options.storage);
1940
+ writeToBackend(url, token, storage, options.configDir);
1941
+ tokenCache.set(cacheKey$1(url, storage), token);
1942
+ }
1943
+ function clearAccessToken(url, options = {}) {
1944
+ const storage = resolveStorage(options.storage);
1945
+ deleteFromBackend(url, storage, options.configDir);
1946
+ tokenCache.set(cacheKey$1(url, storage), MISSING);
1947
+ }
1948
+ /**
1949
+ * Persist a token to `storage`, then best-effort clear the other backend when switching.
1950
+ * Does not discard the newly stored credential if cleanup fails.
1951
+ */
1952
+ function setAccessTokenSwitchingStorage(url, token, options) {
1953
+ setAccessToken(url, token, {
1954
+ storage: options.storage,
1955
+ configDir: options.configDir
1956
+ });
1957
+ const previous = options.previousStorage;
1958
+ if (!previous || previous === options.storage) return {};
1959
+ try {
1960
+ clearAccessToken(url, {
1961
+ storage: previous,
1962
+ configDir: options.configDir
1963
+ });
1964
+ return {};
1965
+ } catch (error) {
1966
+ const detail = error instanceof Error ? error.message : String(error);
1967
+ return { cleanupWarning: `Stored credentials in ${options.storage}, but failed to clear ${previous} storage: ${detail}` };
1968
+ }
1969
+ }
1970
+ //#endregion
1695
1971
  //#region ../../packages/auth/dist/jwt-BVJ7dVm9.mjs
1696
1972
  /** OAuth device-flow client id for `keystroke auth login`. */
1697
1973
  const CLI_CLIENT_ID = "keystroke-cli";
@@ -3526,18 +3802,25 @@ async function getJwtWithBearer(baseURL, sessionToken) {
3526
3802
  //#endregion
3527
3803
  //#region src/auth/jwt-cache.ts
3528
3804
  const caches = /* @__PURE__ */ new Map();
3529
- async function getCliJwt(platformUrl, force = false) {
3530
- if (!getAccessToken(platformUrl)) {
3531
- caches.delete(platformUrl);
3805
+ function cacheKey(platformUrl, storage) {
3806
+ return `${storage}:${platformUrl}`;
3807
+ }
3808
+ function clearCliJwtCache() {
3809
+ caches.clear();
3810
+ }
3811
+ async function getCliJwt(platformUrl, force = false, storage = "keychain") {
3812
+ if (!getAccessToken(platformUrl, { storage })) {
3813
+ caches.delete(cacheKey(platformUrl, storage));
3532
3814
  return;
3533
3815
  }
3534
- let cache = caches.get(platformUrl);
3816
+ const key = cacheKey(platformUrl, storage);
3817
+ let cache = caches.get(key);
3535
3818
  if (!cache) {
3536
3819
  cache = createJwtTokenCache(async () => {
3537
- const sessionToken = getAccessToken(platformUrl);
3820
+ const sessionToken = getAccessToken(platformUrl, { storage });
3538
3821
  return sessionToken ? getJwtWithBearer(platformUrl, sessionToken) : null;
3539
3822
  });
3540
- caches.set(platformUrl, cache);
3823
+ caches.set(key, cache);
3541
3824
  }
3542
3825
  return cache.get(force);
3543
3826
  }
@@ -3551,10 +3834,11 @@ function resolveCliAuth(config) {
3551
3834
  getKey: () => apiKey
3552
3835
  };
3553
3836
  const platformUrl = getPlatformUrl(config);
3554
- if (!getAccessToken(platformUrl)) return { type: "none" };
3837
+ const storage = getCredentialStorage(config);
3838
+ if (!getAccessToken(platformUrl, { storage })) return { type: "none" };
3555
3839
  return {
3556
3840
  type: "bearer",
3557
- getToken: () => getCliJwt(platformUrl)
3841
+ getToken: () => getCliJwt(platformUrl, false, storage)
3558
3842
  };
3559
3843
  }
3560
3844
  //#endregion
@@ -5297,10 +5581,11 @@ function toPlatformAuth(auth) {
5297
5581
  }
5298
5582
  function createCliPlatformClient(config, options = {}) {
5299
5583
  const platformUrl = getPlatformUrl(config);
5584
+ const storage = getCredentialStorage(config);
5300
5585
  return createPlatformClient({
5301
5586
  baseUrl: platformUrl,
5302
5587
  auth: toPlatformAuth(resolveCliAuth(config)),
5303
- refreshBearerToken: () => getCliJwt(platformUrl, true),
5588
+ refreshBearerToken: () => getCliJwt(platformUrl, true, storage),
5304
5589
  getActiveOrganizationId: () => options.organizationId === void 0 ? getResolvedOrganizationId() ?? null : options.organizationId,
5305
5590
  getExtraHeaders: () => ({ [CLIENT_CHANNEL_HEADER]: "cli" })
5306
5591
  });
@@ -5714,14 +5999,9 @@ function unreachableTarget(context) {
5714
5999
  function unreachableServerMessage(targetUrl) {
5715
6000
  return `Could not reach ${targetUrl ?? "the keystroke server"}.`;
5716
6001
  }
5717
- function authApiOrigin(context) {
5718
- if (context?.platformUrl) return context.platformUrl;
5719
- if (context?.webUrl) return resolvePlatformUrlForWebUrl(context.webUrl, {});
5720
- }
5721
6002
  function formatHttpClientError(error, context) {
5722
6003
  if (error.status === 401) {
5723
- const origin = authApiOrigin(context);
5724
- if (origin && !getAccessToken(origin)) return "Not logged in. Run `keystroke auth login` first.";
6004
+ if (context?.hasCredentials === false) return "Not logged in. Run `keystroke auth login` first.";
5725
6005
  return "Authentication failed. Run `keystroke auth login` again.";
5726
6006
  }
5727
6007
  if (error.status === 403) {
@@ -7597,9 +7877,11 @@ function resolveAuthLoginTargets(options, config) {
7597
7877
  //#endregion
7598
7878
  //#region src/commands/auth/login.ts
7599
7879
  function registerAuthLoginCommand(auth) {
7600
- auth.command("login").description("Authenticate via browser device flow").option("--web-url <url>", "Web dashboard origin (omit to use production defaults; local dev: http://localhost:3000)").option("--platform-url <url>", "Platform API origin (derived from web URL when omitted)").action(async (options) => {
7880
+ auth.command("login").description("Authenticate via browser device flow").option("--web-url <url>", "Web dashboard origin (omit to use production defaults; local dev: http://localhost:3000)").option("--platform-url <url>", "Platform API origin (derived from web URL when omitted)").option("--credential-storage <keychain|file>", "Where to store the session token (default: keychain; file is owner-only under ~/.keystroke)").action(async (options) => {
7601
7881
  const config = createCliConfig();
7602
7882
  const { webUrl, platformUrl } = resolveAuthLoginTargets(options, config);
7883
+ const previousStorage = getCredentialStorage(config);
7884
+ const storage = options.credentialStorage ? parseCredentialStorage(options.credentialStorage) : previousStorage;
7603
7885
  config.set("webUrl", webUrl);
7604
7886
  config.set("platformUrl", platformUrl);
7605
7887
  try {
@@ -7608,7 +7890,13 @@ function registerAuthLoginCommand(auth) {
7608
7890
  clientId: CLI_CLIENT_ID,
7609
7891
  openUrl
7610
7892
  });
7611
- setAccessToken(platformUrl, result.accessToken);
7893
+ const { cleanupWarning } = setAccessTokenSwitchingStorage(platformUrl, result.accessToken, {
7894
+ storage,
7895
+ previousStorage
7896
+ });
7897
+ config.set("credentialStorage", storage);
7898
+ clearCliJwtCache();
7899
+ if (cleanupWarning) process.stderr.write(`${cleanupWarning}\n`);
7612
7900
  const user = await getSessionWithBearer(platformUrl, result.accessToken);
7613
7901
  if (user) {
7614
7902
  await aliasCliTelemetryUser(user.id);
@@ -7638,12 +7926,16 @@ function registerAuthLogoutCommand(auth) {
7638
7926
  const config = createCliConfig();
7639
7927
  const webUrl = options.webUrl ?? getWebUrl(config);
7640
7928
  const platformUrl = getPlatformUrl(config);
7641
- clearAccessToken(platformUrl);
7929
+ const storage = getCredentialStorage(config);
7930
+ clearAccessToken(platformUrl, { storage });
7931
+ clearAccessTokenCache();
7932
+ clearCliJwtCache();
7642
7933
  clearCliTelemetryUserId();
7643
7934
  process.stdout.write(`${JSON.stringify({
7644
7935
  authenticated: false,
7645
7936
  webUrl,
7646
- platformUrl
7937
+ platformUrl,
7938
+ credentialStorage: storage
7647
7939
  })}\n`);
7648
7940
  });
7649
7941
  }
@@ -7654,12 +7946,14 @@ function registerAuthStatusCommand(auth) {
7654
7946
  const config = createCliConfig();
7655
7947
  const webUrl = options.webUrl ?? getWebUrl(config);
7656
7948
  const platformUrl = getPlatformUrl(config);
7657
- const token = getAccessToken(platformUrl);
7949
+ const credentialStorage = getCredentialStorage(config);
7950
+ const token = getAccessToken(platformUrl, { storage: credentialStorage });
7658
7951
  if (!token) {
7659
7952
  process.stdout.write(`${JSON.stringify({
7660
7953
  authenticated: false,
7661
7954
  webUrl,
7662
- platformUrl
7955
+ platformUrl,
7956
+ credentialStorage
7663
7957
  })}\n`);
7664
7958
  return;
7665
7959
  }
@@ -7669,6 +7963,8 @@ function registerAuthStatusCommand(auth) {
7669
7963
  process.stdout.write(`${JSON.stringify({
7670
7964
  authenticated: user !== null,
7671
7965
  webUrl,
7966
+ platformUrl,
7967
+ credentialStorage,
7672
7968
  user,
7673
7969
  organizations: organizations.map((entry) => ({
7674
7970
  slug: entry.organization.slug,
@@ -7791,7 +8087,7 @@ function registerBuildCommand(program) {
7791
8087
  try {
7792
8088
  const root = resolveProjectRoot(options.dir);
7793
8089
  await ensureSdkCurrent(root);
7794
- const { buildApp } = await import("./dist-Bo7OJs1C.mjs");
8090
+ const { buildApp } = await import("./dist-Cp7XrP0x.mjs");
7795
8091
  await buildApp({ root });
7796
8092
  process.stdout.write(`Built ${root}\n`);
7797
8093
  } catch (error) {
@@ -7926,7 +8222,7 @@ async function sleep(ms) {
7926
8222
  }
7927
8223
  async function buildDeployArchive(client, root, projectId, filter) {
7928
8224
  if (filter?.length) {
7929
- const { buildFilteredApp } = await import("./dist-Bo7OJs1C.mjs");
8225
+ const { buildFilteredApp } = await import("./dist-Cp7XrP0x.mjs");
7930
8226
  const filtered = await buildFilteredApp({
7931
8227
  root,
7932
8228
  filter,
@@ -7948,7 +8244,7 @@ async function buildDeployArchive(client, root, projectId, filter) {
7948
8244
  sourceFiles: filtered.sourceFiles
7949
8245
  };
7950
8246
  }
7951
- const { buildApp } = await import("./dist-Bo7OJs1C.mjs");
8247
+ const { buildApp } = await import("./dist-Cp7XrP0x.mjs");
7952
8248
  const { sourceFiles } = await buildApp({
7953
8249
  root,
7954
8250
  collectSources: true,
@@ -8079,7 +8375,7 @@ function runtimeChildEnv(parentEnv, overrides) {
8079
8375
  //#region src/project/bootstrap-run.ts
8080
8376
  /** Node args + env for `@keystrokehq/build` bootstrap (shared by start + dev). */
8081
8377
  async function resolveBootstrapRun(options) {
8082
- const { resolveRuntimeBuildArtifact } = await import("./dist-Bo7OJs1C.mjs");
8378
+ const { resolveRuntimeBuildArtifact } = await import("./dist-Cp7XrP0x.mjs");
8083
8379
  const loader = pathToFileURL(resolveRuntimeBuildArtifact(options.runtimeNodeModules, "dist/runtime-loader.mjs")).href;
8084
8380
  const bootstrap = resolveRuntimeBuildArtifact(options.runtimeNodeModules, "dist/standalone-bootstrap.mjs");
8085
8381
  const args = [`--import=${loader}`];
@@ -8229,7 +8525,7 @@ async function runDev(options) {
8229
8525
  process.on("SIGINT", shutdown);
8230
8526
  process.on("SIGTERM", shutdown);
8231
8527
  try {
8232
- const { watchApp } = await import("./dist-Bo7OJs1C.mjs");
8528
+ const { watchApp } = await import("./dist-Cp7XrP0x.mjs");
8233
8529
  await watchApp({
8234
8530
  root,
8235
8531
  clean: false,
@@ -8880,7 +9176,7 @@ async function runOrganizationMembersRemove(config, userId) {
8880
9176
  }
8881
9177
  async function runOrganizationLeave(config) {
8882
9178
  const platformUrl = getPlatformUrl(config);
8883
- const token = getAccessToken(platformUrl);
9179
+ const token = getAccessToken(platformUrl, { storage: getCredentialStorage(config) });
8884
9180
  if (!token) throw new Error("Not authenticated. Run `keystroke auth login` first.");
8885
9181
  const user = await getSessionWithBearer(platformUrl, token);
8886
9182
  if (!user) throw new Error("Session expired. Run `keystroke auth login` again.");
@@ -9339,7 +9635,7 @@ async function runStart(options) {
9339
9635
  const apiPort = Number(new URL(serverUrl).port || 80);
9340
9636
  const runtimeNodeModules = resolveCliRuntimeNodeModules(resolveCliRoot(import.meta.url));
9341
9637
  ensureNativeDeps(runtimeNodeModules);
9342
- const { buildApp } = await import("./dist-Bo7OJs1C.mjs");
9638
+ const { buildApp } = await import("./dist-Cp7XrP0x.mjs");
9343
9639
  await buildApp({
9344
9640
  root,
9345
9641
  clean: false
@@ -10231,7 +10527,7 @@ function createProgram() {
10231
10527
  async function runCli(argv) {
10232
10528
  initCliTelemetry();
10233
10529
  try {
10234
- const { maybeAutoUpdate } = await import("./maybe-auto-update-C99Obm7k.mjs");
10530
+ const { maybeAutoUpdate } = await import("./maybe-auto-update-D8VbU88T.mjs");
10235
10531
  await maybeAutoUpdate(argv);
10236
10532
  await createProgram().parseAsync(argv);
10237
10533
  } finally {
@@ -10241,4 +10537,4 @@ async function runCli(argv) {
10241
10537
  //#endregion
10242
10538
  export { runCli };
10243
10539
 
10244
- //# sourceMappingURL=program-DMhbEvwF.mjs.map
10540
+ //# sourceMappingURL=program-CYtD52e1.mjs.map