convex 1.48.0 → 1.49.0-alpha.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.
Files changed (248) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/browser.bundle.js +1 -1
  3. package/dist/browser.bundle.js.map +1 -1
  4. package/dist/cjs/bundler/context.js +7 -5
  5. package/dist/cjs/bundler/context.js.map +2 -2
  6. package/dist/cjs/bundler/fs.js +47 -6
  7. package/dist/cjs/bundler/fs.js.map +2 -2
  8. package/dist/cjs/cli/account.js +237 -0
  9. package/dist/cjs/cli/account.js.map +7 -0
  10. package/dist/cjs/cli/configure.js +15 -2
  11. package/dist/cjs/cli/configure.js.map +2 -2
  12. package/dist/cjs/cli/lib/accountManagement.js +196 -0
  13. package/dist/cjs/cli/lib/accountManagement.js.map +7 -0
  14. package/dist/cjs/cli/lib/accounts.js +334 -0
  15. package/dist/cjs/cli/lib/accounts.js.map +7 -0
  16. package/dist/cjs/cli/lib/deployKeyWarning.js +43 -0
  17. package/dist/cjs/cli/lib/deployKeyWarning.js.map +7 -0
  18. package/dist/cjs/cli/lib/deployment.js +2 -0
  19. package/dist/cjs/cli/lib/deployment.js.map +2 -2
  20. package/dist/cjs/cli/lib/deploymentSelection.js +27 -18
  21. package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
  22. package/dist/cjs/cli/lib/envvars.js +32 -0
  23. package/dist/cjs/cli/lib/envvars.js.map +2 -2
  24. package/dist/cjs/cli/lib/fsUtils.js +1 -1
  25. package/dist/cjs/cli/lib/fsUtils.js.map +2 -2
  26. package/dist/cjs/cli/lib/generateDocs.js +1 -1
  27. package/dist/cjs/cli/lib/generateDocs.js.map +1 -1
  28. package/dist/cjs/cli/lib/login.js +42 -4
  29. package/dist/cjs/cli/lib/login.js.map +3 -3
  30. package/dist/cjs/cli/lib/mcp/requestContext.js +5 -1
  31. package/dist/cjs/cli/lib/mcp/requestContext.js.map +2 -2
  32. package/dist/cjs/cli/lib/plugins/claudeCode.js +188 -0
  33. package/dist/cjs/cli/lib/plugins/claudeCode.js.map +7 -0
  34. package/dist/cjs/cli/lib/plugins/codex.js +167 -0
  35. package/dist/cjs/cli/lib/plugins/codex.js.map +7 -0
  36. package/dist/cjs/cli/lib/plugins/harness.js +50 -0
  37. package/dist/cjs/cli/lib/plugins/harness.js.map +7 -0
  38. package/dist/cjs/cli/lib/plugins/marketplaces.js +76 -0
  39. package/dist/cjs/cli/lib/plugins/marketplaces.js.map +7 -0
  40. package/dist/cjs/cli/lib/plugins/skills.js +98 -0
  41. package/dist/cjs/cli/lib/plugins/skills.js.map +7 -0
  42. package/dist/cjs/cli/lib/plugins/utils.js +125 -0
  43. package/dist/cjs/cli/lib/plugins/utils.js.map +7 -0
  44. package/dist/cjs/cli/lib/utils/utils.js +18 -9
  45. package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
  46. package/dist/cjs/cli/login.js +213 -114
  47. package/dist/cjs/cli/login.js.map +3 -3
  48. package/dist/cjs/cli/logout.js +113 -12
  49. package/dist/cjs/cli/logout.js.map +3 -3
  50. package/dist/cjs/cli/mcp.js +43 -10
  51. package/dist/cjs/cli/mcp.js.map +3 -3
  52. package/dist/cjs/cli/plugins.js +114 -0
  53. package/dist/cjs/cli/plugins.js.map +7 -0
  54. package/dist/cjs/cli/program.js +3 -1
  55. package/dist/cjs/cli/program.js.map +2 -2
  56. package/dist/cjs/index.js +1 -1
  57. package/dist/cjs/index.js.map +1 -1
  58. package/dist/cjs/server/impl/actions_impl.js.map +2 -2
  59. package/dist/cjs-types/bundler/context.d.ts +5 -0
  60. package/dist/cjs-types/bundler/context.d.ts.map +1 -1
  61. package/dist/cjs-types/bundler/fs.d.ts +7 -2
  62. package/dist/cjs-types/bundler/fs.d.ts.map +1 -1
  63. package/dist/cjs-types/cli/account.d.ts +6 -0
  64. package/dist/cjs-types/cli/account.d.ts.map +1 -0
  65. package/dist/cjs-types/cli/configure.d.ts.map +1 -1
  66. package/dist/cjs-types/cli/lib/accountManagement.d.ts +10 -0
  67. package/dist/cjs-types/cli/lib/accountManagement.d.ts.map +1 -0
  68. package/dist/cjs-types/cli/lib/accounts.d.ts +59 -0
  69. package/dist/cjs-types/cli/lib/accounts.d.ts.map +1 -0
  70. package/dist/cjs-types/cli/lib/deployKeyWarning.d.ts +3 -0
  71. package/dist/cjs-types/cli/lib/deployKeyWarning.d.ts.map +1 -0
  72. package/dist/cjs-types/cli/lib/deployment.d.ts +2 -0
  73. package/dist/cjs-types/cli/lib/deployment.d.ts.map +1 -1
  74. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts +2 -1
  75. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  76. package/dist/cjs-types/cli/lib/envvars.d.ts +6 -0
  77. package/dist/cjs-types/cli/lib/envvars.d.ts.map +1 -1
  78. package/dist/cjs-types/cli/lib/fsUtils.d.ts +1 -0
  79. package/dist/cjs-types/cli/lib/fsUtils.d.ts.map +1 -1
  80. package/dist/cjs-types/cli/lib/login.d.ts +3 -1
  81. package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
  82. package/dist/cjs-types/cli/lib/mcp/requestContext.d.ts.map +1 -1
  83. package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +2 -2
  84. package/dist/cjs-types/cli/lib/plugins/claudeCode.d.ts +25 -0
  85. package/dist/cjs-types/cli/lib/plugins/claudeCode.d.ts.map +1 -0
  86. package/dist/cjs-types/cli/lib/plugins/codex.d.ts +25 -0
  87. package/dist/cjs-types/cli/lib/plugins/codex.d.ts.map +1 -0
  88. package/dist/cjs-types/cli/lib/plugins/harness.d.ts +33 -0
  89. package/dist/cjs-types/cli/lib/plugins/harness.d.ts.map +1 -0
  90. package/dist/cjs-types/cli/lib/plugins/marketplaces.d.ts +40 -0
  91. package/dist/cjs-types/cli/lib/plugins/marketplaces.d.ts.map +1 -0
  92. package/dist/cjs-types/cli/lib/plugins/skills.d.ts +18 -0
  93. package/dist/cjs-types/cli/lib/plugins/skills.d.ts.map +1 -0
  94. package/dist/cjs-types/cli/lib/plugins/utils.d.ts +21 -0
  95. package/dist/cjs-types/cli/lib/plugins/utils.d.ts.map +1 -0
  96. package/dist/cjs-types/cli/lib/utils/utils.d.ts +5 -0
  97. package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
  98. package/dist/cjs-types/cli/login.d.ts +21 -0
  99. package/dist/cjs-types/cli/login.d.ts.map +1 -1
  100. package/dist/cjs-types/cli/logout.d.ts +8 -1
  101. package/dist/cjs-types/cli/logout.d.ts.map +1 -1
  102. package/dist/cjs-types/cli/mcp.d.ts.map +1 -1
  103. package/dist/cjs-types/cli/plugins.d.ts +3 -0
  104. package/dist/cjs-types/cli/plugins.d.ts.map +1 -0
  105. package/dist/cjs-types/cli/program.d.ts.map +1 -1
  106. package/dist/cjs-types/index.d.ts +1 -1
  107. package/dist/cjs-types/index.d.ts.map +1 -1
  108. package/dist/cjs-types/server/impl/actions_impl.d.ts +3 -2
  109. package/dist/cjs-types/server/impl/actions_impl.d.ts.map +1 -1
  110. package/dist/cli.bundle.cjs +10059 -7094
  111. package/dist/cli.bundle.cjs.map +4 -4
  112. package/dist/esm/bundler/context.js +7 -5
  113. package/dist/esm/bundler/context.js.map +2 -2
  114. package/dist/esm/bundler/fs.js +47 -6
  115. package/dist/esm/bundler/fs.js.map +2 -2
  116. package/dist/esm/cli/account.js +221 -0
  117. package/dist/esm/cli/account.js.map +7 -0
  118. package/dist/esm/cli/configure.js +15 -2
  119. package/dist/esm/cli/configure.js.map +2 -2
  120. package/dist/esm/cli/lib/accountManagement.js +179 -0
  121. package/dist/esm/cli/lib/accountManagement.js.map +7 -0
  122. package/dist/esm/cli/lib/accounts.js +289 -0
  123. package/dist/esm/cli/lib/accounts.js.map +7 -0
  124. package/dist/esm/cli/lib/deployKeyWarning.js +27 -0
  125. package/dist/esm/cli/lib/deployKeyWarning.js.map +7 -0
  126. package/dist/esm/cli/lib/deployment.js +2 -2
  127. package/dist/esm/cli/lib/deployment.js.map +2 -2
  128. package/dist/esm/cli/lib/deploymentSelection.js +35 -20
  129. package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
  130. package/dist/esm/cli/lib/envvars.js +35 -1
  131. package/dist/esm/cli/lib/envvars.js.map +2 -2
  132. package/dist/esm/cli/lib/fsUtils.js +1 -1
  133. package/dist/esm/cli/lib/fsUtils.js.map +2 -2
  134. package/dist/esm/cli/lib/generateDocs.js +1 -1
  135. package/dist/esm/cli/lib/generateDocs.js.map +1 -1
  136. package/dist/esm/cli/lib/login.js +52 -4
  137. package/dist/esm/cli/lib/login.js.map +3 -3
  138. package/dist/esm/cli/lib/mcp/requestContext.js +5 -1
  139. package/dist/esm/cli/lib/mcp/requestContext.js.map +2 -2
  140. package/dist/esm/cli/lib/plugins/claudeCode.js +160 -0
  141. package/dist/esm/cli/lib/plugins/claudeCode.js.map +7 -0
  142. package/dist/esm/cli/lib/plugins/codex.js +140 -0
  143. package/dist/esm/cli/lib/plugins/codex.js.map +7 -0
  144. package/dist/esm/cli/lib/plugins/harness.js +17 -0
  145. package/dist/esm/cli/lib/plugins/harness.js.map +7 -0
  146. package/dist/esm/cli/lib/plugins/marketplaces.js +53 -0
  147. package/dist/esm/cli/lib/plugins/marketplaces.js.map +7 -0
  148. package/dist/esm/cli/lib/plugins/skills.js +65 -0
  149. package/dist/esm/cli/lib/plugins/skills.js.map +7 -0
  150. package/dist/esm/cli/lib/plugins/utils.js +86 -0
  151. package/dist/esm/cli/lib/plugins/utils.js.map +7 -0
  152. package/dist/esm/cli/lib/utils/utils.js +16 -9
  153. package/dist/esm/cli/lib/utils/utils.js.map +2 -2
  154. package/dist/esm/cli/login.js +225 -112
  155. package/dist/esm/cli/login.js.map +3 -3
  156. package/dist/esm/cli/logout.js +110 -13
  157. package/dist/esm/cli/logout.js.map +2 -2
  158. package/dist/esm/cli/mcp.js +34 -10
  159. package/dist/esm/cli/mcp.js.map +2 -2
  160. package/dist/esm/cli/plugins.js +99 -0
  161. package/dist/esm/cli/plugins.js.map +7 -0
  162. package/dist/esm/cli/program.js +3 -1
  163. package/dist/esm/cli/program.js.map +2 -2
  164. package/dist/esm/index.js +1 -1
  165. package/dist/esm/index.js.map +1 -1
  166. package/dist/esm/server/impl/actions_impl.js.map +2 -2
  167. package/dist/esm-types/bundler/context.d.ts +5 -0
  168. package/dist/esm-types/bundler/context.d.ts.map +1 -1
  169. package/dist/esm-types/bundler/fs.d.ts +7 -2
  170. package/dist/esm-types/bundler/fs.d.ts.map +1 -1
  171. package/dist/esm-types/cli/account.d.ts +6 -0
  172. package/dist/esm-types/cli/account.d.ts.map +1 -0
  173. package/dist/esm-types/cli/configure.d.ts.map +1 -1
  174. package/dist/esm-types/cli/lib/accountManagement.d.ts +10 -0
  175. package/dist/esm-types/cli/lib/accountManagement.d.ts.map +1 -0
  176. package/dist/esm-types/cli/lib/accounts.d.ts +59 -0
  177. package/dist/esm-types/cli/lib/accounts.d.ts.map +1 -0
  178. package/dist/esm-types/cli/lib/deployKeyWarning.d.ts +3 -0
  179. package/dist/esm-types/cli/lib/deployKeyWarning.d.ts.map +1 -0
  180. package/dist/esm-types/cli/lib/deployment.d.ts +2 -0
  181. package/dist/esm-types/cli/lib/deployment.d.ts.map +1 -1
  182. package/dist/esm-types/cli/lib/deploymentSelection.d.ts +2 -1
  183. package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  184. package/dist/esm-types/cli/lib/envvars.d.ts +6 -0
  185. package/dist/esm-types/cli/lib/envvars.d.ts.map +1 -1
  186. package/dist/esm-types/cli/lib/fsUtils.d.ts +1 -0
  187. package/dist/esm-types/cli/lib/fsUtils.d.ts.map +1 -1
  188. package/dist/esm-types/cli/lib/login.d.ts +3 -1
  189. package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
  190. package/dist/esm-types/cli/lib/mcp/requestContext.d.ts.map +1 -1
  191. package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +2 -2
  192. package/dist/esm-types/cli/lib/plugins/claudeCode.d.ts +25 -0
  193. package/dist/esm-types/cli/lib/plugins/claudeCode.d.ts.map +1 -0
  194. package/dist/esm-types/cli/lib/plugins/codex.d.ts +25 -0
  195. package/dist/esm-types/cli/lib/plugins/codex.d.ts.map +1 -0
  196. package/dist/esm-types/cli/lib/plugins/harness.d.ts +33 -0
  197. package/dist/esm-types/cli/lib/plugins/harness.d.ts.map +1 -0
  198. package/dist/esm-types/cli/lib/plugins/marketplaces.d.ts +40 -0
  199. package/dist/esm-types/cli/lib/plugins/marketplaces.d.ts.map +1 -0
  200. package/dist/esm-types/cli/lib/plugins/skills.d.ts +18 -0
  201. package/dist/esm-types/cli/lib/plugins/skills.d.ts.map +1 -0
  202. package/dist/esm-types/cli/lib/plugins/utils.d.ts +21 -0
  203. package/dist/esm-types/cli/lib/plugins/utils.d.ts.map +1 -0
  204. package/dist/esm-types/cli/lib/utils/utils.d.ts +5 -0
  205. package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
  206. package/dist/esm-types/cli/login.d.ts +21 -0
  207. package/dist/esm-types/cli/login.d.ts.map +1 -1
  208. package/dist/esm-types/cli/logout.d.ts +8 -1
  209. package/dist/esm-types/cli/logout.d.ts.map +1 -1
  210. package/dist/esm-types/cli/mcp.d.ts.map +1 -1
  211. package/dist/esm-types/cli/plugins.d.ts +3 -0
  212. package/dist/esm-types/cli/plugins.d.ts.map +1 -0
  213. package/dist/esm-types/cli/program.d.ts.map +1 -1
  214. package/dist/esm-types/index.d.ts +1 -1
  215. package/dist/esm-types/index.d.ts.map +1 -1
  216. package/dist/esm-types/server/impl/actions_impl.d.ts +3 -2
  217. package/dist/esm-types/server/impl/actions_impl.d.ts.map +1 -1
  218. package/dist/react.bundle.js +1 -1
  219. package/dist/react.bundle.js.map +1 -1
  220. package/package.json +2 -2
  221. package/src/bundler/context.ts +9 -5
  222. package/src/bundler/fs.ts +54 -5
  223. package/src/cli/account.ts +320 -0
  224. package/src/cli/configure.ts +18 -2
  225. package/src/cli/lib/accountManagement.ts +262 -0
  226. package/src/cli/lib/accounts.ts +400 -0
  227. package/src/cli/lib/deployKeyWarning.ts +43 -0
  228. package/src/cli/lib/deployment.ts +2 -2
  229. package/src/cli/lib/deploymentSelection.ts +37 -18
  230. package/src/cli/lib/envvars.ts +43 -1
  231. package/src/cli/lib/fsUtils.ts +5 -2
  232. package/src/cli/lib/generateDocs.ts +1 -1
  233. package/src/cli/lib/login.ts +64 -3
  234. package/src/cli/lib/mcp/requestContext.ts +7 -1
  235. package/src/cli/lib/plugins/claudeCode.ts +201 -0
  236. package/src/cli/lib/plugins/codex.ts +177 -0
  237. package/src/cli/lib/plugins/harness.ts +49 -0
  238. package/src/cli/lib/plugins/marketplaces.ts +94 -0
  239. package/src/cli/lib/plugins/skills.ts +101 -0
  240. package/src/cli/lib/plugins/utils.ts +121 -0
  241. package/src/cli/lib/utils/utils.ts +19 -15
  242. package/src/cli/login.ts +293 -168
  243. package/src/cli/logout.ts +142 -17
  244. package/src/cli/mcp.ts +48 -11
  245. package/src/cli/plugins.ts +154 -0
  246. package/src/cli/program.ts +4 -0
  247. package/src/index.ts +1 -1
  248. package/src/server/impl/actions_impl.ts +3 -2
@@ -102,7 +102,7 @@ export async function eraseDeploymentEnvVar(ctx: Context): Promise<boolean> {
102
102
  return true;
103
103
  }
104
104
 
105
- async function gitIgnoreEnvVarFile(ctx: Context): Promise<boolean> {
105
+ export async function gitIgnoreEnvVarFile(ctx: Context): Promise<boolean> {
106
106
  const gitIgnorePath = ".gitignore";
107
107
  const gitIgnoreContents = ctx.fs.exists(gitIgnorePath)
108
108
  ? ctx.fs.readUtf8File(gitIgnorePath)
@@ -186,7 +186,7 @@ export async function deploymentNameFromAdminKeyOrCrash(
186
186
  return deploymentName;
187
187
  }
188
188
 
189
- function deploymentNameFromAdminKey(adminKey: string) {
189
+ export function deploymentNameFromAdminKey(adminKey: string) {
190
190
  const parts = adminKey.split("|");
191
191
  if (parts.length === 1) {
192
192
  return null;
@@ -31,18 +31,26 @@ import {
31
31
  targetProjectForLocalSelector,
32
32
  } from "./localDeployment/projectMismatch.js";
33
33
  import { chalkStderr } from "chalk";
34
- import { getBuildEnvironment } from "./envvars.js";
34
+ import {
35
+ getBuildEnvironment,
36
+ loadProjectEnvironmentVariables,
37
+ } from "./envvars.js";
35
38
  import { readGlobalConfig } from "./utils/globalConfig.js";
39
+ import {
40
+ getAccountSelection,
41
+ AccountSelection,
42
+ readAccountAuth,
43
+ } from "./accounts.js";
36
44
  import {
37
45
  CONVEX_DEPLOYMENT_ENV_VAR_NAME,
38
46
  CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME,
39
47
  CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
40
48
  CONVEX_SELF_HOSTED_ADMIN_KEY_VAR_NAME,
41
49
  CONVEX_SELF_HOSTED_URL_VAR_NAME,
42
- ENV_VAR_FILE_PATH,
43
50
  bigBrainAPI,
44
51
  processDeployKeyValue,
45
52
  readDeployKeyFromEnv,
53
+ readDeployKeyEnvVar,
46
54
  typedPlatformClient,
47
55
  } from "./utils/utils.js";
48
56
  import * as dotenv from "dotenv";
@@ -83,10 +91,11 @@ export async function initializeBigBrainAuth(
83
91
  if (initialArgs.url !== undefined && initialArgs.adminKey !== undefined) {
84
92
  // Do not check any env vars if `url` and `adminKey` are specified via CLI
85
93
  ctx._updateBigBrainAuth(
86
- getBigBrainAuth(ctx, {
94
+ await getBigBrainAuth(ctx, {
87
95
  previewDeployKey: null,
88
96
  projectKey: null,
89
97
  deploymentKey: null,
98
+ skipAccount: true,
90
99
  }),
91
100
  );
92
101
  return;
@@ -106,30 +115,31 @@ export async function initializeBigBrainAuth(
106
115
  const rawDeployKey = readDeployKeyFromEnv((name) => config[name]);
107
116
  const deployKey = await processDeployKeyValue(ctx, rawDeployKey);
108
117
  if (deployKey !== undefined) {
109
- const bigBrainAuth = getBigBrainAuth(ctx, {
118
+ const bigBrainAuth = await getBigBrainAuth(ctx, {
110
119
  previewDeployKey: isPreviewDeployKey(deployKey) ? deployKey : null,
111
120
  projectKey: isProjectKey(deployKey) ? deployKey : null,
112
121
  deploymentKey: isDeploymentKey(deployKey) ? deployKey : null,
122
+ envFile: initialArgs.envFile,
113
123
  });
114
124
  ctx._updateBigBrainAuth(bigBrainAuth);
115
125
  return;
116
126
  }
117
127
  // No deploy key was found in the env file, so fall back on using the global config
118
128
  ctx._updateBigBrainAuth(
119
- getBigBrainAuth(ctx, {
129
+ await getBigBrainAuth(ctx, {
120
130
  previewDeployKey: null,
121
131
  projectKey: null,
122
132
  deploymentKey: null,
133
+ envFile: initialArgs.envFile,
123
134
  }),
124
135
  );
125
136
  return;
126
137
  }
127
- dotenv.config({ path: ENV_VAR_FILE_PATH });
128
- dotenv.config();
138
+ loadProjectEnvironmentVariables();
129
139
  const rawDeployKey = readDeployKeyFromEnv((name) => process.env[name]);
130
140
  const deployKey = await processDeployKeyValue(ctx, rawDeployKey);
131
141
  if (deployKey !== undefined) {
132
- const bigBrainAuth = getBigBrainAuth(ctx, {
142
+ const bigBrainAuth = await getBigBrainAuth(ctx, {
133
143
  previewDeployKey: isPreviewDeployKey(deployKey) ? deployKey : null,
134
144
  projectKey: isProjectKey(deployKey) ? deployKey : null,
135
145
  deploymentKey: isDeploymentKey(deployKey) ? deployKey : null,
@@ -138,7 +148,7 @@ export async function initializeBigBrainAuth(
138
148
  return;
139
149
  }
140
150
  ctx._updateBigBrainAuth(
141
- getBigBrainAuth(ctx, {
151
+ await getBigBrainAuth(ctx, {
142
152
  previewDeployKey: null,
143
153
  projectKey: null,
144
154
  deploymentKey: null,
@@ -150,6 +160,7 @@ export async function initializeBigBrainAuth(
150
160
  export async function updateBigBrainAuthAfterLogin(
151
161
  ctx: Context,
152
162
  accessToken: string,
163
+ account?: AccountSelection,
153
164
  ) {
154
165
  const existingAuth = ctx.bigBrainAuth();
155
166
  if (existingAuth !== null && existingAuth.kind === "projectKey") {
@@ -162,6 +173,7 @@ export async function updateBigBrainAuthAfterLogin(
162
173
  accessToken: accessToken,
163
174
  kind: "accessToken",
164
175
  header: `Bearer ${accessToken}`,
176
+ ...(account ? { account } : {}),
165
177
  });
166
178
  }
167
179
 
@@ -169,19 +181,22 @@ export async function clearBigBrainAuth(ctx: Context) {
169
181
  ctx._updateBigBrainAuth(null);
170
182
  }
171
183
 
172
- function getBigBrainAuth(
184
+ async function getBigBrainAuth(
173
185
  ctx: Context,
174
186
  opts: {
175
187
  previewDeployKey: string | null;
176
188
  projectKey: string | null;
177
189
  deploymentKey: string | null;
190
+ envFile?: string;
191
+ skipAccount?: boolean;
178
192
  },
179
- ): BigBrainAuth | null {
193
+ ): Promise<BigBrainAuth | null> {
180
194
  if (process.env.CONVEX_OVERRIDE_ACCESS_TOKEN) {
181
195
  return {
182
196
  accessToken: process.env.CONVEX_OVERRIDE_ACCESS_TOKEN,
183
197
  kind: "accessToken",
184
198
  header: `Bearer ${process.env.CONVEX_OVERRIDE_ACCESS_TOKEN}`,
199
+ account: null,
185
200
  };
186
201
  }
187
202
  if (opts.projectKey !== null) {
@@ -200,12 +215,19 @@ function getBigBrainAuth(
200
215
  deploymentKey: opts.deploymentKey,
201
216
  };
202
217
  }
218
+ const account = opts.skipAccount
219
+ ? null
220
+ : await getAccountSelection(ctx, { envFile: opts.envFile });
221
+ if (account !== null) {
222
+ return await readAccountAuth(ctx, account);
223
+ }
203
224
  const globalConfig = readGlobalConfig(ctx);
204
225
  if (globalConfig) {
205
226
  return {
206
227
  kind: "accessToken",
207
228
  header: `Bearer ${globalConfig.accessToken}`,
208
229
  accessToken: globalConfig.accessToken,
230
+ account: null,
209
231
  };
210
232
  }
211
233
  if (opts.previewDeployKey !== null) {
@@ -272,9 +294,9 @@ export async function ensureAuthCanCreateDeployment(
272
294
  )} and try again.`,
273
295
  });
274
296
  }
275
- const envVar = process.env[CONVEX_DEPLOY_KEY_ENV_VAR_NAME]
276
- ? CONVEX_DEPLOY_KEY_ENV_VAR_NAME
277
- : CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME;
297
+ const envVar =
298
+ readDeployKeyEnvVar((name) => process.env[name])?.name ??
299
+ CONVEX_DEPLOY_KEY_ENV_VAR_NAME;
278
300
  return await ctx.crash({
279
301
  exitCode: 1,
280
302
  errorType: "fatal",
@@ -569,10 +591,7 @@ async function resolveBaseDeploymentSelection(
569
591
  }
570
592
  return result.metadata;
571
593
  }
572
- // start with .env.local (but doesn't override existing)
573
- dotenv.config({ path: ENV_VAR_FILE_PATH });
574
- // for variables not already set, use .env values
575
- dotenv.config();
594
+ loadProjectEnvironmentVariables();
576
595
  const result = await getDeploymentSelectionFromEnv(
577
596
  ctx,
578
597
  selectionWithinProject,
@@ -6,7 +6,49 @@ import * as dotenv from "dotenv";
6
6
 
7
7
  import { Context } from "../../bundler/context.js";
8
8
  import { logWarning } from "../../bundler/log.js";
9
- import { loadPackageJson } from "./utils/utils.js";
9
+ import {
10
+ CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
11
+ readDeployKeyEnvVar,
12
+ ENV_VAR_FILE_PATH,
13
+ loadPackageJson,
14
+ } from "./utils/utils.js";
15
+
16
+ export const CONVEX_ACCOUNT_ENV_VAR = "CONVEX_ACCOUNT";
17
+
18
+ const loadedEnvironment = new Map<string, { value: string; source: string }>();
19
+
20
+ export function loadProjectEnvironmentVariables() {
21
+ // Only remove values supplied by our previous load; later shell overrides win.
22
+ for (const [name, { value }] of loadedEnvironment) {
23
+ if (process.env[name] === value) delete process.env[name];
24
+ }
25
+ loadedEnvironment.clear();
26
+ const environment: Record<string, string> = {};
27
+ for (const [name, value] of Object.entries(process.env)) {
28
+ if (value !== undefined) environment[name] = value;
29
+ }
30
+ for (const file of [ENV_VAR_FILE_PATH, ".env"]) {
31
+ dotenv.config({ path: file, processEnv: environment });
32
+ for (const [name, value] of Object.entries(environment)) {
33
+ // Account selection reads the current project's files directly.
34
+ if (name !== CONVEX_ACCOUNT_ENV_VAR && process.env[name] === undefined) {
35
+ process.env[name] = value;
36
+ loadedEnvironment.set(name, { value, source: file });
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ export function getDeployKeySource(key: string) {
43
+ const envVarName =
44
+ readDeployKeyEnvVar((name) => process.env[name])?.name ??
45
+ CONVEX_DEPLOY_KEY_ENV_VAR_NAME;
46
+ const loaded = loadedEnvironment.get(envVarName);
47
+ return {
48
+ envVarName,
49
+ source: loaded?.value === key ? loaded.source : "shell environment",
50
+ };
51
+ }
10
52
 
11
53
  const _FRAMEWORKS = [
12
54
  "create-react-app",
@@ -6,12 +6,15 @@ import { NodeFs } from "../../bundler/fs.js";
6
6
  export function recursivelyDelete(
7
7
  ctx: Context,
8
8
  deletePath: string,
9
- opts?: { force?: boolean; dryRun?: boolean },
9
+ opts?: { force?: boolean; dryRun?: boolean; followSymlinks?: boolean },
10
10
  ) {
11
11
  const dryRun = !!opts?.dryRun;
12
12
  let st;
13
13
  try {
14
- st = ctx.fs.stat(deletePath);
14
+ st =
15
+ opts?.followSymlinks === false
16
+ ? ctx.fs.lstat(deletePath)
17
+ : ctx.fs.stat(deletePath);
15
18
  } catch (err: any) {
16
19
  if (err.code === "ENOENT" && opts?.force) {
17
20
  return;
@@ -299,7 +299,7 @@ function renderCopyableCommands(text: string): string {
299
299
  .map((line) =>
300
300
  /^- /.test(line)
301
301
  ? line.replace(
302
- /`(npx convex [^`]*)`/g,
302
+ /`(npx convex(?:@[^\s`]+)? [^`]*)`/g,
303
303
  (_, command) =>
304
304
  `<CodeWithCopyButton text={${JSON.stringify(command)}} />`,
305
305
  )
@@ -20,6 +20,7 @@ import {
20
20
  logMessage,
21
21
  logOutput,
22
22
  logVerbose,
23
+ logWarning,
23
24
  showSpinner,
24
25
  } from "../../bundler/log.js";
25
26
  import { Issuer } from "openid-client";
@@ -32,6 +33,18 @@ import {
32
33
  modifyGlobalConfig,
33
34
  } from "./utils/globalConfig.js";
34
35
  import { updateBigBrainAuthAfterLogin } from "./deploymentSelection.js";
36
+ import {
37
+ getAccountSelection,
38
+ AccountSelection,
39
+ accountConfigPath,
40
+ writeAccountCredentials,
41
+ readAccountCredentials,
42
+ } from "./accounts.js";
43
+ import {
44
+ accountApi,
45
+ accountLoginMetadata,
46
+ revokeReplacedAccountToken,
47
+ } from "./accountManagement.js";
35
48
 
36
49
  // Per https://github.com/panva/node-openid-client/tree/main/docs#customizing
37
50
  custom.setHttpOptionsDefaults({
@@ -291,6 +304,7 @@ export async function performLogin(
291
304
  anonymousId,
292
305
  vercel,
293
306
  vercelOverride,
307
+ account: selectedAccount,
294
308
  }: {
295
309
  overrideAuthUrl?: string | undefined;
296
310
  overrideAuthClient?: string | undefined;
@@ -307,8 +321,16 @@ export async function performLogin(
307
321
  anonymousId?: string | undefined;
308
322
  vercel?: boolean | undefined;
309
323
  vercelOverride?: string | undefined;
324
+ account?: AccountSelection | null;
310
325
  } = {},
311
326
  ) {
327
+ const auth = ctx.bigBrainAuth();
328
+ const account =
329
+ selectedAccount !== undefined
330
+ ? selectedAccount
331
+ : auth?.kind === "accessToken" && auth.account !== undefined
332
+ ? auth.account
333
+ : await getAccountSelection(ctx);
312
334
  loginFlow = loginFlow || "auto";
313
335
  // Get access token from big-brain
314
336
  // Default the device name to the hostname, but allow the user to change this if the terminal is interactive.
@@ -400,6 +422,12 @@ export async function performLogin(
400
422
  }
401
423
 
402
424
  // Exchange the WorkOS access token for a Convex personal access token.
425
+ const previous =
426
+ account === null ? null : await readAccountCredentials(ctx, account, true);
427
+ const metadata =
428
+ account === null
429
+ ? {}
430
+ : await accountLoginMetadata(ctx, account, accessToken, previous);
403
431
  ctx._updateBigBrainAuth({
404
432
  accessToken: accessToken,
405
433
  kind: "accessToken",
@@ -417,8 +445,34 @@ export async function performLogin(
417
445
  const newAccessToken = response.data!.accessToken;
418
446
  const globalConfig = { accessToken: newAccessToken };
419
447
  try {
420
- await modifyGlobalConfig(ctx, globalConfig);
421
- const path = globalConfigPath();
448
+ if (account !== null) {
449
+ await writeAccountCredentials(
450
+ ctx,
451
+ account.name,
452
+ newAccessToken,
453
+ metadata,
454
+ previous,
455
+ async () => {
456
+ try {
457
+ const response = await accountApi(
458
+ newAccessToken,
459
+ "/v1/delete_personal_access_token",
460
+ { id: newAccessToken },
461
+ );
462
+ if (response.ok) return;
463
+ } catch {
464
+ // Report cleanup failure alongside the account conflict below.
465
+ }
466
+ logWarning(
467
+ "The unused login token could not be revoked. You can remove it in the dashboard's account settings.",
468
+ );
469
+ },
470
+ );
471
+ } else {
472
+ await modifyGlobalConfig(ctx, globalConfig);
473
+ }
474
+ const path =
475
+ account !== null ? accountConfigPath(account.name) : globalConfigPath();
422
476
  logFinishedStep(`Saved credentials to ${formatPathForPrinting(path)}`);
423
477
  } catch (err: unknown) {
424
478
  return await ctx.crash({
@@ -429,8 +483,15 @@ export async function performLogin(
429
483
  });
430
484
  }
431
485
 
486
+ if (account !== null) {
487
+ await revokeReplacedAccountToken(
488
+ ctx,
489
+ previous?.accessToken,
490
+ newAccessToken,
491
+ );
492
+ }
432
493
  logVerbose(`performLogin: updating big brain auth after login`);
433
- await updateBigBrainAuthAfterLogin(ctx, newAccessToken);
494
+ await updateBigBrainAuthAfterLogin(ctx, newAccessToken, account ?? undefined);
434
495
 
435
496
  logVerbose(`performLogin: checking opt ins, acceptOptIns: ${acceptOptIns}`);
436
497
  // Do opt in to TOS and Privacy Policy stuff
@@ -149,7 +149,13 @@ export function encodeDeploymentSelector(
149
149
  projectDir,
150
150
  deployment,
151
151
  };
152
- return `${deployment.kind}:${btoa(JSON.stringify(payload))}`;
152
+ // ASCII JSON escapes support Unicode paths with the existing Latin-1 decoder.
153
+ const serialized = JSON.stringify(payload).replace(
154
+ /[\u0080-\uffff]/g,
155
+ (character) =>
156
+ `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
157
+ );
158
+ return `${deployment.kind}:${btoa(serialized)}`;
153
159
  }
154
160
 
155
161
  const payloadSchema = z.object({
@@ -0,0 +1,201 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import * as Sentry from "@sentry/node";
4
+ import { z } from "zod";
5
+ import type { Cli, Places } from "./harness.js";
6
+ import {
7
+ convexMarketplaces,
8
+ type Marketplace,
9
+ type Plugin,
10
+ type Source,
11
+ } from "./marketplaces.js";
12
+ import {
13
+ fetchGitHubJson,
14
+ onPath,
15
+ parseList,
16
+ subfolders,
17
+ versionOf,
18
+ } from "./utils.js";
19
+
20
+ /** Builds `getClaudeCodeLocations` for the given places, so tests can point it at fixtures. */
21
+ export const createGetClaudeCodeLocations =
22
+ (places: Omit<Places, "applications">) => async (): Promise<Cli[]> => {
23
+ const found: Omit<Cli, "version">[] = [
24
+ ...onPath("claude", places.path).map((p) => ({
25
+ source: "terminal" as const,
26
+ path: p,
27
+ })),
28
+ // The desktop app keeps every version it downloaded, as
29
+ // `<version>/<hash>/claude.app`, next to its own state files.
30
+ ...subfolders(
31
+ path.join(
32
+ places.home,
33
+ "Library",
34
+ "Application Support",
35
+ "Claude",
36
+ "claude-code",
37
+ ),
38
+ )
39
+ .flatMap(subfolders)
40
+ .map((dir) => ({
41
+ source: "desktop" as const,
42
+ path: path.join(dir, "claude.app", "Contents", "MacOS", "claude"),
43
+ })),
44
+ ...subfolders(path.join(places.home, ".vscode", "extensions"))
45
+ .filter((dir) =>
46
+ path.basename(dir).startsWith("anthropic.claude-code-"),
47
+ )
48
+ .map((dir) => ({
49
+ source: "vscode" as const,
50
+ path: path.join(dir, "resources", "native-binary", "claude"),
51
+ })),
52
+ ];
53
+ return Promise.all(
54
+ found.map(async (cli) => ({
55
+ ...cli,
56
+ version: await versionOf(cli.path),
57
+ })),
58
+ );
59
+ };
60
+
61
+ /** Every copy of Claude Code on this machine: the terminal CLI, the Claude desktop app's copies and the VS Code extension's copies. */
62
+ export const getClaudeCodeLocations = createGetClaudeCodeLocations({
63
+ home: os.homedir(),
64
+ path: process.env.PATH ?? "",
65
+ });
66
+
67
+ /** The newest Convex plugin a Claude Code user can install, which is the commit Anthropic's marketplace pins, not our main. */
68
+ export async function claudeCodeLatest(): Promise<Record<string, string>> {
69
+ const marketplace = await fetchGitHubJson<{
70
+ plugins: { name: string; source: { url: string; sha: string } }[];
71
+ }>("anthropics/claude-plugins-official/main/.claude-plugin/marketplace.json");
72
+ if (marketplace === null) return {};
73
+ const source = marketplace.plugins.find((p) => p.name === "convex")?.source;
74
+ if (source === undefined) {
75
+ Sentry.captureMessage(
76
+ "Anthropic's marketplace has no convex plugin",
77
+ "error",
78
+ );
79
+ return {};
80
+ }
81
+ const repo = source.url
82
+ .replace(/^https:\/\/github\.com\//, "")
83
+ .replace(/\.git$/, "");
84
+ const plugin = await fetchGitHubJson<{ version: string }>(
85
+ `${repo}/${source.sha}/.claude-plugin/plugin.json`,
86
+ );
87
+ return plugin === null ? {} : { convex: plugin.version };
88
+ }
89
+
90
+ /** `claude plugin marketplace list --json` */
91
+ const claudeCodeMarketplaceList = z.array(
92
+ z.union([
93
+ z.object({
94
+ name: z.string(),
95
+ source: z.literal("github"),
96
+ repo: z.string(),
97
+ }),
98
+ z.object({ name: z.string(), source: z.literal("git"), url: z.string() }),
99
+ z.object({
100
+ name: z.string(),
101
+ source: z.literal("directory"),
102
+ path: z.string(),
103
+ }),
104
+ // The list holds other people's marketplaces too, so a source type we
105
+ // haven't seen is kept by its type name instead of rejecting the list.
106
+ z.object({ name: z.string(), source: z.string() }),
107
+ ]),
108
+ );
109
+
110
+ /** Where a Claude Code marketplace comes from. zod keeps only the fields of the shape that matched. */
111
+ function claudeCodeSource(
112
+ m: z.infer<typeof claudeCodeMarketplaceList>[number],
113
+ ): Source {
114
+ if ("repo" in m) return { kind: "github", repo: m.repo };
115
+ if ("url" in m) return { kind: "git", url: m.url };
116
+ if ("path" in m) return { kind: "folder", path: m.path };
117
+ return { kind: "other", type: m.source };
118
+ }
119
+
120
+ /** `claude plugin list --json` */
121
+ const claudeCodePluginList = z.array(
122
+ z.object({
123
+ id: z.string(),
124
+ version: z.string(),
125
+ scope: z.string(),
126
+ enabled: z.boolean(),
127
+ folderVersion: z.string().optional(),
128
+ }),
129
+ );
130
+
131
+ /** Reads the plugins in `claude plugin list --json`, or returns null when it doesn't match. */
132
+ function claudeCodePlugins(pluginList: string): Plugin[] | null {
133
+ const plugins = parseList(
134
+ claudeCodePluginList,
135
+ pluginList,
136
+ "claude plugin list --json",
137
+ );
138
+ if (plugins === null) return null;
139
+ return plugins.map((p) => {
140
+ const [name, marketplace] = p.id.split("@");
141
+ // A folder install runs from the folder, and `version` is the copy cached at install time.
142
+ return {
143
+ name,
144
+ marketplace,
145
+ version: p.folderVersion ?? p.version,
146
+ scope: p.scope,
147
+ enabled: p.enabled,
148
+ cachedVersion:
149
+ p.folderVersion !== undefined && p.folderVersion !== p.version
150
+ ? p.version
151
+ : undefined,
152
+ };
153
+ });
154
+ }
155
+
156
+ /** Reads Claude Code's Convex marketplaces from `claude plugin marketplace list --json` and `claude plugin list --json`, or returns null when either doesn't match. */
157
+ export function claudeCodeMarketplaces(
158
+ marketplaceList: string,
159
+ pluginList: string,
160
+ ): Marketplace[] | null {
161
+ const marketplaces = parseList(
162
+ claudeCodeMarketplaceList,
163
+ marketplaceList,
164
+ "claude plugin marketplace list --json",
165
+ );
166
+ const plugins = claudeCodePlugins(pluginList);
167
+ if (marketplaces === null || plugins === null) return null;
168
+ return convexMarketplaces(
169
+ marketplaces.map((m) => ({ name: m.name, source: claudeCodeSource(m) })),
170
+ plugins,
171
+ );
172
+ }
173
+
174
+ const pluginId = "convex@claude-plugins-official";
175
+ /** The install step that brings an installed plugin to the latest version. When it fails, the user keeps an old version. */
176
+ const upgradeStep = ["plugin", "update", pluginId, "--scope", "user"];
177
+
178
+ /** Claude Code as `npx convex plugins` installs and reads it. */
179
+ export const claudeCode = {
180
+ id: "claudeCode" as const,
181
+ name: "Claude Code",
182
+ plugin: pluginId,
183
+ upgradeStep,
184
+ installSteps: [
185
+ // Notes:
186
+ //
187
+ // 1. Claude Code updates its local copy of the marketplace when it
188
+ // installs, but `plugin install` skips a plugin that's already
189
+ // installed, so an old version stays until the plugin is updated.
190
+
191
+ // Add the marketplace
192
+ ["plugin", "marketplace", "add", "anthropics/claude-plugins-official"],
193
+ // Install the plugin if it isn't installed yet
194
+ ["plugin", "install", pluginId, "--scope", "user"],
195
+ // Update an installed plugin to the latest version
196
+ upgradeStep,
197
+ ],
198
+ plugins: claudeCodePlugins,
199
+ marketplaces: claudeCodeMarketplaces,
200
+ latest: claudeCodeLatest,
201
+ };