convex 1.34.0 → 1.34.1

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 (243) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser.bundle.js +6 -9
  3. package/dist/browser.bundle.js.map +2 -2
  4. package/dist/cjs/browser/sync/authentication_manager.js +4 -1
  5. package/dist/cjs/browser/sync/authentication_manager.js.map +2 -2
  6. package/dist/cjs/browser/sync/web_socket_manager.js +1 -7
  7. package/dist/cjs/browser/sync/web_socket_manager.js.map +2 -2
  8. package/dist/cjs/cli/aiFiles.js +15 -14
  9. package/dist/cjs/cli/aiFiles.js.map +2 -2
  10. package/dist/cjs/cli/configure.js +15 -10
  11. package/dist/cjs/cli/configure.js.map +2 -2
  12. package/dist/cjs/cli/lib/aiFiles/agentsmd.js +69 -0
  13. package/dist/cjs/cli/lib/aiFiles/agentsmd.js.map +7 -0
  14. package/dist/cjs/cli/lib/aiFiles/claudemd.js +69 -0
  15. package/dist/cjs/cli/lib/aiFiles/claudemd.js.map +7 -0
  16. package/dist/cjs/cli/lib/{ai → aiFiles}/config.js +73 -46
  17. package/dist/cjs/cli/lib/aiFiles/config.js.map +7 -0
  18. package/dist/cjs/cli/lib/aiFiles/cursorrules.js +48 -0
  19. package/dist/cjs/cli/lib/aiFiles/cursorrules.js.map +7 -0
  20. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js +51 -0
  21. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  22. package/dist/cjs/cli/lib/aiFiles/index.js +231 -0
  23. package/dist/cjs/cli/lib/aiFiles/index.js.map +7 -0
  24. package/dist/cjs/cli/lib/aiFiles/paths.js.map +7 -0
  25. package/dist/cjs/cli/lib/aiFiles/skills.js +180 -0
  26. package/dist/cjs/cli/lib/aiFiles/skills.js.map +7 -0
  27. package/dist/cjs/cli/lib/aiFiles/status.js +195 -0
  28. package/dist/cjs/cli/lib/aiFiles/status.js.map +7 -0
  29. package/dist/cjs/cli/lib/aiFiles/utils.js +111 -0
  30. package/dist/cjs/cli/lib/aiFiles/utils.js.map +7 -0
  31. package/dist/cjs/cli/lib/command.js +6 -1
  32. package/dist/cjs/cli/lib/command.js.map +2 -2
  33. package/dist/cjs/cli/lib/config.js +3 -4
  34. package/dist/cjs/cli/lib/config.js.map +2 -2
  35. package/dist/cjs/cli/lib/localDeployment/anonymous.js +2 -2
  36. package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
  37. package/dist/cjs/cli/lib/updates.js +8 -8
  38. package/dist/cjs/cli/lib/updates.js.map +2 -2
  39. package/dist/cjs/cli/lib/versionApi.js +7 -4
  40. package/dist/cjs/cli/lib/versionApi.js.map +2 -2
  41. package/dist/cjs/cli/lib/workos/workos.js +4 -6
  42. package/dist/cjs/cli/lib/workos/workos.js.map +2 -2
  43. package/dist/cjs/index.js +1 -1
  44. package/dist/cjs/index.js.map +1 -1
  45. package/dist/cjs-types/browser/sync/authentication_manager.d.ts.map +1 -1
  46. package/dist/cjs-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  47. package/dist/cjs-types/cli/aiFiles.d.ts.map +1 -1
  48. package/dist/cjs-types/cli/configure.d.ts.map +1 -1
  49. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  50. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  51. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  52. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  53. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  54. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  55. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  56. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  57. package/dist/cjs-types/cli/lib/aiFiles/config.d.ts +46 -0
  58. package/dist/cjs-types/cli/lib/aiFiles/config.d.ts.map +1 -0
  59. package/dist/cjs-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
  60. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  61. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  62. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  63. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  64. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  65. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  66. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts +40 -0
  67. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  68. package/dist/cjs-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  69. package/dist/cjs-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  70. package/dist/cjs-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  71. package/dist/cjs-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  72. package/dist/cjs-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  73. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts +18 -0
  74. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  75. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts +3 -0
  76. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  77. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts +46 -0
  78. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  79. package/dist/cjs-types/cli/lib/config.d.ts +1 -0
  80. package/dist/cjs-types/cli/lib/config.d.ts.map +1 -1
  81. package/dist/cjs-types/cli/lib/versionApi.d.ts +7 -1
  82. package/dist/cjs-types/cli/lib/versionApi.d.ts.map +1 -1
  83. package/dist/cjs-types/cli/lib/workos/workos.d.ts.map +1 -1
  84. package/dist/cjs-types/index.d.ts +1 -1
  85. package/dist/cli.bundle.cjs +1605 -1548
  86. package/dist/cli.bundle.cjs.map +4 -4
  87. package/dist/esm/browser/sync/authentication_manager.js +4 -1
  88. package/dist/esm/browser/sync/authentication_manager.js.map +2 -2
  89. package/dist/esm/browser/sync/web_socket_manager.js +1 -7
  90. package/dist/esm/browser/sync/web_socket_manager.js.map +2 -2
  91. package/dist/esm/cli/aiFiles.js +17 -17
  92. package/dist/esm/cli/aiFiles.js.map +2 -2
  93. package/dist/esm/cli/configure.js +15 -10
  94. package/dist/esm/cli/configure.js.map +2 -2
  95. package/dist/esm/cli/lib/aiFiles/agentsmd.js +52 -0
  96. package/dist/esm/cli/lib/aiFiles/agentsmd.js.map +7 -0
  97. package/dist/esm/cli/lib/aiFiles/claudemd.js +52 -0
  98. package/dist/esm/cli/lib/aiFiles/claudemd.js.map +7 -0
  99. package/dist/esm/cli/lib/{ai → aiFiles}/config.js +71 -45
  100. package/dist/esm/cli/lib/aiFiles/config.js.map +7 -0
  101. package/dist/esm/cli/lib/aiFiles/cursorrules.js +16 -0
  102. package/dist/esm/cli/lib/aiFiles/cursorrules.js.map +7 -0
  103. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js +28 -0
  104. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  105. package/dist/esm/cli/lib/aiFiles/index.js +210 -0
  106. package/dist/esm/cli/lib/aiFiles/index.js.map +7 -0
  107. package/dist/esm/cli/lib/aiFiles/paths.js.map +7 -0
  108. package/dist/esm/cli/lib/aiFiles/skills.js +147 -0
  109. package/dist/esm/cli/lib/aiFiles/skills.js.map +7 -0
  110. package/dist/esm/cli/lib/aiFiles/status.js +175 -0
  111. package/dist/esm/cli/lib/aiFiles/status.js.map +7 -0
  112. package/dist/esm/cli/lib/aiFiles/utils.js +82 -0
  113. package/dist/esm/cli/lib/aiFiles/utils.js.map +7 -0
  114. package/dist/esm/cli/lib/command.js +6 -1
  115. package/dist/esm/cli/lib/command.js.map +2 -2
  116. package/dist/esm/cli/lib/config.js +3 -4
  117. package/dist/esm/cli/lib/config.js.map +2 -2
  118. package/dist/esm/cli/lib/localDeployment/anonymous.js +2 -2
  119. package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
  120. package/dist/esm/cli/lib/updates.js +8 -8
  121. package/dist/esm/cli/lib/updates.js.map +2 -2
  122. package/dist/esm/cli/lib/versionApi.js +7 -4
  123. package/dist/esm/cli/lib/versionApi.js.map +2 -2
  124. package/dist/esm/cli/lib/workos/workos.js +4 -6
  125. package/dist/esm/cli/lib/workos/workos.js.map +2 -2
  126. package/dist/esm/index.js +1 -1
  127. package/dist/esm/index.js.map +1 -1
  128. package/dist/esm-types/browser/sync/authentication_manager.d.ts.map +1 -1
  129. package/dist/esm-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  130. package/dist/esm-types/cli/aiFiles.d.ts.map +1 -1
  131. package/dist/esm-types/cli/configure.d.ts.map +1 -1
  132. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  133. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  134. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  135. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  136. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  137. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  138. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  139. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  140. package/dist/esm-types/cli/lib/aiFiles/config.d.ts +46 -0
  141. package/dist/esm-types/cli/lib/aiFiles/config.d.ts.map +1 -0
  142. package/dist/esm-types/cli/lib/aiFiles/config.test.d.ts.map +1 -0
  143. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  144. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  145. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  146. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  147. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  148. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  149. package/dist/esm-types/cli/lib/aiFiles/index.d.ts +40 -0
  150. package/dist/esm-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  151. package/dist/esm-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  152. package/dist/esm-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  153. package/dist/esm-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  154. package/dist/esm-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  155. package/dist/esm-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  156. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts +18 -0
  157. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  158. package/dist/esm-types/cli/lib/aiFiles/status.d.ts +3 -0
  159. package/dist/esm-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  160. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts +46 -0
  161. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  162. package/dist/esm-types/cli/lib/config.d.ts +1 -0
  163. package/dist/esm-types/cli/lib/config.d.ts.map +1 -1
  164. package/dist/esm-types/cli/lib/versionApi.d.ts +7 -1
  165. package/dist/esm-types/cli/lib/versionApi.d.ts.map +1 -1
  166. package/dist/esm-types/cli/lib/workos/workos.d.ts.map +1 -1
  167. package/dist/esm-types/index.d.ts +1 -1
  168. package/dist/react.bundle.js +6 -9
  169. package/dist/react.bundle.js.map +2 -2
  170. package/package.json +1 -1
  171. package/schemas/convex.schema.json +7 -1
  172. package/src/browser/sync/authentication_manager.ts +9 -4
  173. package/src/browser/sync/client_node.test.ts +125 -0
  174. package/src/browser/sync/web_socket_manager.ts +1 -7
  175. package/src/cli/aiFiles.ts +20 -27
  176. package/src/cli/configure.ts +17 -11
  177. package/src/cli/deploymentSelection.test.ts +56 -2
  178. package/src/cli/lib/{ai → aiFiles}/MANUAL_TESTING.md +6 -2
  179. package/src/cli/lib/aiFiles/agentsmd.test.ts +133 -0
  180. package/src/cli/lib/aiFiles/agentsmd.ts +77 -0
  181. package/src/cli/lib/aiFiles/claudemd.test.ts +92 -0
  182. package/src/cli/lib/aiFiles/claudemd.ts +77 -0
  183. package/src/cli/lib/{ai → aiFiles}/config.test.ts +181 -59
  184. package/src/cli/lib/{ai → aiFiles}/config.ts +92 -63
  185. package/src/cli/lib/aiFiles/cursorrules.ts +25 -0
  186. package/src/cli/lib/aiFiles/guidelinesmd.test.ts +40 -0
  187. package/src/cli/lib/aiFiles/guidelinesmd.ts +41 -0
  188. package/src/cli/lib/{ai → aiFiles}/index.test.ts +200 -339
  189. package/src/cli/lib/aiFiles/index.ts +303 -0
  190. package/src/cli/lib/{ai → aiFiles}/integration.test.ts +117 -147
  191. package/src/cli/lib/{ai → aiFiles}/paths.ts +5 -0
  192. package/src/cli/lib/{ai → aiFiles}/prompt.test.ts +78 -30
  193. package/src/cli/lib/aiFiles/skills.ts +213 -0
  194. package/src/cli/lib/aiFiles/status.ts +240 -0
  195. package/src/cli/lib/aiFiles/utils.ts +163 -0
  196. package/src/cli/lib/command.ts +6 -1
  197. package/src/cli/lib/config.test.ts +1 -1
  198. package/src/cli/lib/config.ts +6 -5
  199. package/src/cli/lib/localDeployment/anonymous.ts +2 -2
  200. package/src/cli/lib/updates.test.ts +40 -30
  201. package/src/cli/lib/updates.ts +8 -8
  202. package/src/cli/lib/versionApi.test.ts +13 -10
  203. package/src/cli/lib/versionApi.ts +13 -5
  204. package/src/cli/lib/workos/workos.ts +4 -5
  205. package/src/index.ts +1 -1
  206. package/src/values/.claude/settings.local.json +10 -0
  207. package/dist/cjs/cli/lib/ai/config.js.map +0 -7
  208. package/dist/cjs/cli/lib/ai/index.js +0 -704
  209. package/dist/cjs/cli/lib/ai/index.js.map +0 -7
  210. package/dist/cjs/cli/lib/ai/paths.js.map +0 -7
  211. package/dist/cjs-types/cli/lib/ai/config.d.ts +0 -50
  212. package/dist/cjs-types/cli/lib/ai/config.d.ts.map +0 -1
  213. package/dist/cjs-types/cli/lib/ai/config.test.d.ts.map +0 -1
  214. package/dist/cjs-types/cli/lib/ai/index.d.ts +0 -56
  215. package/dist/cjs-types/cli/lib/ai/index.d.ts.map +0 -1
  216. package/dist/cjs-types/cli/lib/ai/index.test.d.ts.map +0 -1
  217. package/dist/cjs-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  218. package/dist/cjs-types/cli/lib/ai/paths.d.ts.map +0 -1
  219. package/dist/cjs-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  220. package/dist/esm/cli/lib/ai/config.js.map +0 -7
  221. package/dist/esm/cli/lib/ai/index.js +0 -684
  222. package/dist/esm/cli/lib/ai/index.js.map +0 -7
  223. package/dist/esm/cli/lib/ai/paths.js.map +0 -7
  224. package/dist/esm-types/cli/lib/ai/config.d.ts +0 -50
  225. package/dist/esm-types/cli/lib/ai/config.d.ts.map +0 -1
  226. package/dist/esm-types/cli/lib/ai/config.test.d.ts.map +0 -1
  227. package/dist/esm-types/cli/lib/ai/index.d.ts +0 -56
  228. package/dist/esm-types/cli/lib/ai/index.d.ts.map +0 -1
  229. package/dist/esm-types/cli/lib/ai/index.test.d.ts.map +0 -1
  230. package/dist/esm-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  231. package/dist/esm-types/cli/lib/ai/paths.d.ts.map +0 -1
  232. package/dist/esm-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  233. package/src/cli/lib/ai/index.ts +0 -1006
  234. /package/dist/cjs/cli/lib/{ai → aiFiles}/paths.js +0 -0
  235. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
  236. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  237. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  238. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
  239. /package/dist/esm/cli/lib/{ai → aiFiles}/paths.js +0 -0
  240. /package/dist/esm-types/cli/lib/{ai → aiFiles}/config.test.d.ts +0 -0
  241. /package/dist/esm-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  242. /package/dist/esm-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  243. /package/dist/esm-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
@@ -1,11 +1,21 @@
1
1
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
- import { PassThrough, Writable } from "stream";
3
2
  import fs from "fs";
4
3
  import os from "os";
5
4
  import path from "path";
6
5
  import type { Context } from "../../../bundler/context.js";
6
+ import {
7
+ AGENTS_MD_END_MARKER,
8
+ AGENTS_MD_START_MARKER,
9
+ } from "../../codegen_templates/agentsmd.js";
7
10
 
8
- let testInput: PassThrough;
11
+ const { mockPromptYesNo } = vi.hoisted(() => {
12
+ return { mockPromptYesNo: vi.fn() };
13
+ });
14
+
15
+ vi.mock("@sentry/node", () => ({
16
+ captureException: vi.fn(),
17
+ captureMessage: vi.fn(),
18
+ }));
9
19
 
10
20
  vi.mock("../../../bundler/log.js", () => ({
11
21
  logMessage: vi.fn(),
@@ -16,10 +26,13 @@ vi.mock("../versionApi.js", () => ({
16
26
  downloadGuidelines: vi.fn(async () => "prompt test guidelines content"),
17
27
  fetchAgentSkillsSha: vi.fn(async () => "prompt-test-sha"),
18
28
  getVersion: vi.fn(async () => ({
19
- message: null,
20
- guidelinesHash: "prompt-test-guidelines-hash",
21
- agentSkillsSha: "prompt-test-agent-skills-sha",
22
- disableSkillsCli: false,
29
+ kind: "ok",
30
+ data: {
31
+ message: null,
32
+ guidelinesHash: "prompt-test-guidelines-hash",
33
+ agentSkillsSha: "prompt-test-agent-skills-sha",
34
+ disableSkillsCli: false,
35
+ },
23
36
  })),
24
37
  }));
25
38
 
@@ -35,18 +48,11 @@ vi.mock("child_process", () => ({
35
48
  },
36
49
  }));
37
50
 
38
- vi.mock("@inquirer/confirm", async (importOriginal) => {
39
- const actual = await importOriginal<typeof import("@inquirer/confirm")>();
51
+ vi.mock("../utils/prompts.js", async (importOriginal) => {
52
+ const actual = await importOriginal<typeof import("../utils/prompts.js")>();
40
53
  return {
41
54
  ...actual,
42
- default: (config: any, context: any) => {
43
- const output = new Writable({
44
- write(_c, _e, cb) {
45
- cb();
46
- },
47
- });
48
- return actual.default(config, { ...context, input: testInput, output });
49
- },
55
+ promptYesNo: mockPromptYesNo,
50
56
  };
51
57
  });
52
58
 
@@ -92,7 +98,7 @@ describe("maybeSetupAiFiles interactive prompt", () => {
92
98
  fs.writeFileSync(path.join(convexDir, "schema.ts"), "");
93
99
  originalIsTTY = process.stdin.isTTY;
94
100
  process.stdin.isTTY = true;
95
- testInput = new PassThrough();
101
+ mockPromptYesNo.mockResolvedValue(true);
96
102
  });
97
103
 
98
104
  afterEach(() => {
@@ -103,11 +109,9 @@ describe("maybeSetupAiFiles interactive prompt", () => {
103
109
  });
104
110
 
105
111
  test("user accepts prompt: AI files are installed", async () => {
106
- const result = maybeSetupAiFiles(fakeCtx, convexDir, tmpDir);
112
+ mockPromptYesNo.mockResolvedValue(true);
107
113
 
108
- // Simulate pressing Enter to accept the default (yes)
109
- testInput.emit("keypress", null, { name: "enter" });
110
- await result;
114
+ await maybeSetupAiFiles({ ctx: fakeCtx, convexDir, projectDir: tmpDir });
111
115
 
112
116
  expect(fs.existsSync(guidelinesPath())).toBe(true);
113
117
  expect(fs.existsSync(statePath())).toBe(true);
@@ -119,13 +123,9 @@ describe("maybeSetupAiFiles interactive prompt", () => {
119
123
  });
120
124
 
121
125
  test("user declines prompt: no config and no AI files are written", async () => {
122
- const result = maybeSetupAiFiles(fakeCtx, convexDir, tmpDir);
126
+ mockPromptYesNo.mockResolvedValue(false);
123
127
 
124
- // Type "n" then Enter to decline
125
- testInput.write("n");
126
- testInput.emit("keypress", null, { name: "n" });
127
- testInput.emit("keypress", null, { name: "enter" });
128
- await result;
128
+ await maybeSetupAiFiles({ ctx: fakeCtx, convexDir, projectDir: tmpDir });
129
129
 
130
130
  expect(fs.existsSync(guidelinesPath())).toBe(false);
131
131
  expect(fs.existsSync(path.join(tmpDir, "AGENTS.md"))).toBe(false);
@@ -133,10 +133,10 @@ describe("maybeSetupAiFiles interactive prompt", () => {
133
133
  expect(fs.existsSync(projectConfigPath())).toBe(false);
134
134
  });
135
135
 
136
- test("agent mode skips the prompt and does not install AI files", async () => {
137
- vi.stubEnv("CONVEX_AGENT_MODE", "anonymous");
136
+ test("non-interactive terminal skips the prompt and does not install AI files", async () => {
137
+ process.stdin.isTTY = false;
138
138
 
139
- await maybeSetupAiFiles(fakeCtx, convexDir, tmpDir);
139
+ await maybeSetupAiFiles({ ctx: fakeCtx, convexDir, projectDir: tmpDir });
140
140
 
141
141
  expect(fs.existsSync(guidelinesPath())).toBe(false);
142
142
  expect(fs.existsSync(statePath())).toBe(false);
@@ -144,4 +144,52 @@ describe("maybeSetupAiFiles interactive prompt", () => {
144
144
  expect(fs.existsSync(path.join(tmpDir, "CLAUDE.md"))).toBe(false);
145
145
  expect(fs.existsSync(projectConfigPath())).toBe(false);
146
146
  });
147
+
148
+ test("existing state file updates AI files without prompting", async () => {
149
+ const stateDir = path.join(convexDir, "_generated", "ai");
150
+ fs.mkdirSync(stateDir, { recursive: true });
151
+ fs.writeFileSync(
152
+ path.join(stateDir, "ai-files.state.json"),
153
+ JSON.stringify(
154
+ {
155
+ guidelinesHash: "hash",
156
+ agentsMdSectionHash: "hash",
157
+ claudeMdHash: "hash",
158
+ agentSkillsSha: "sha",
159
+ installedSkillNames: [],
160
+ },
161
+ null,
162
+ 2,
163
+ ),
164
+ );
165
+
166
+ await maybeSetupAiFiles({ ctx: fakeCtx, convexDir, projectDir: tmpDir });
167
+
168
+ expect(fs.existsSync(path.join(stateDir, "ai-files.state.json"))).toBe(
169
+ true,
170
+ );
171
+ expect(fs.existsSync(guidelinesPath())).toBe(true);
172
+ expect(fs.readFileSync(guidelinesPath(), "utf8")).toBe(
173
+ "prompt test guidelines content",
174
+ );
175
+ });
176
+
177
+ test("existing AGENTS.md managed section rebuilds state without prompting", async () => {
178
+ fs.writeFileSync(
179
+ path.join(tmpDir, "AGENTS.md"),
180
+ [
181
+ "# Team notes",
182
+ "",
183
+ AGENTS_MD_START_MARKER,
184
+ "Managed section",
185
+ AGENTS_MD_END_MARKER,
186
+ "",
187
+ ].join("\n"),
188
+ );
189
+
190
+ await maybeSetupAiFiles({ ctx: fakeCtx, convexDir, projectDir: tmpDir });
191
+
192
+ expect(fs.existsSync(statePath())).toBe(true);
193
+ expect(fs.existsSync(guidelinesPath())).toBe(true);
194
+ });
147
195
  });
@@ -0,0 +1,213 @@
1
+ import child_process from "child_process";
2
+ import path from "path";
3
+ // eslint-disable-next-line no-restricted-imports
4
+ import { promises as fs } from "fs";
5
+ import { chalkStderr } from "chalk";
6
+ import { logMessage } from "../../../bundler/log.js";
7
+ import { getVersion, fetchAgentSkillsSha } from "../versionApi.js";
8
+ import { type AiFilesConfig } from "./config.js";
9
+ import { iife, readFileSafe } from "./utils.js";
10
+
11
+ /**
12
+ * Read the frontmatter `name:` values from skills installed by the skills CLI.
13
+ */
14
+ async function readInstalledSkillNames(projectDir: string): Promise<string[]> {
15
+ const skillsDir = path.join(projectDir, ".agents", "skills");
16
+ const entries = await iife(async () => {
17
+ try {
18
+ const dirents = await fs.readdir(skillsDir, { withFileTypes: true });
19
+ return dirents
20
+ .filter((d) => d.isDirectory() || d.isSymbolicLink())
21
+ .map((d) => d.name);
22
+ } catch {
23
+ return [] as string[];
24
+ }
25
+ });
26
+ if (entries.length === 0) return [];
27
+
28
+ const names: string[] = [];
29
+ for (const entry of entries) {
30
+ const skillMdPath = path.join(skillsDir, entry, "SKILL.md");
31
+ const content = await readFileSafe(skillMdPath);
32
+ if (content === null) continue;
33
+ const match = content.match(/^---[\s\S]*?^name:\s*(.+?)\s*$/m);
34
+ if (match) {
35
+ names.push(match[1]);
36
+ }
37
+ }
38
+ return names;
39
+ }
40
+
41
+ /**
42
+ * Runs `npx skills add get-convex/agent-skills --yes` in the given directory.
43
+ * Returns true on success, false if the process fails or cannot be started.
44
+ */
45
+ function runSkillsAdd(cwd: string): Promise<boolean> {
46
+ return runSkillsCommand(cwd, ["add", "get-convex/agent-skills", "--yes"]);
47
+ }
48
+
49
+ /**
50
+ * Runs `npx skills remove <name...> --yes` to surgically remove only the
51
+ * Convex-managed skills, leaving any skills from other sources intact.
52
+ */
53
+ function runSkillsRemove({
54
+ cwd,
55
+ skillNames,
56
+ }: {
57
+ cwd: string;
58
+ skillNames: string[];
59
+ }): Promise<boolean> {
60
+ return runSkillsCommand(cwd, ["remove", ...skillNames, "--yes"]);
61
+ }
62
+
63
+ /**
64
+ * This function exists so we have a way to disable skills installs without pushing a new
65
+ * version of the convex CLI
66
+ */
67
+ async function shouldRunSkillsCli(): Promise<boolean> {
68
+ const versionData = await getVersion();
69
+
70
+ if (versionData.kind === "error") return true;
71
+
72
+ if (versionData.data.disableSkillsCli) {
73
+ logMessage(chalkStderr.yellow(`Agent skills are temporarily disabled.`));
74
+ return false;
75
+ }
76
+
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Remove the skills-lock.json file if it only contains skills that we
82
+ * are removing. The `npx skills remove` command leaves the lockfile behind
83
+ * even when it's logically empty.
84
+ */
85
+ async function removeSkillsLockIfEmpty({
86
+ projectDir,
87
+ removedSkillNames,
88
+ }: {
89
+ projectDir: string;
90
+ removedSkillNames: string[];
91
+ }): Promise<boolean> {
92
+ const lockPath = path.join(projectDir, "skills-lock.json");
93
+ try {
94
+ const content = await fs.readFile(lockPath, "utf8");
95
+ const lock = JSON.parse(content);
96
+
97
+ if (
98
+ !lock ||
99
+ typeof lock !== "object" ||
100
+ !lock.skills ||
101
+ typeof lock.skills !== "object"
102
+ ) {
103
+ return false;
104
+ }
105
+
106
+ const remainingSkills = Object.keys(lock.skills).filter(
107
+ (name) => !removedSkillNames.includes(name),
108
+ );
109
+
110
+ if (remainingSkills.length === 0) {
111
+ await fs.unlink(lockPath);
112
+ return true;
113
+ }
114
+ return false;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Install Convex agent skills and record the SHA and names into the config.
122
+ * Handles the kill-switch check and all logging internally.
123
+ */
124
+ export async function installSkills({
125
+ projectDir,
126
+ config,
127
+ }: {
128
+ projectDir: string;
129
+ config: AiFilesConfig;
130
+ }): Promise<void> {
131
+ if (!(await shouldRunSkillsCli())) return;
132
+
133
+ logMessage("Installing Convex agent skills...");
134
+ const skillsOk = await runSkillsAdd(projectDir);
135
+ if (!skillsOk) {
136
+ logMessage(
137
+ chalkStderr.yellow(
138
+ "Could not install agent skills. You can retry manually with: npx skills add get-convex/agent-skills",
139
+ ),
140
+ );
141
+ return;
142
+ }
143
+
144
+ const sha = await fetchAgentSkillsSha();
145
+ if (sha) config.agentSkillsSha = sha;
146
+
147
+ const names = await readInstalledSkillNames(projectDir);
148
+ if (names.length > 0) config.installedSkillNames = names;
149
+ }
150
+
151
+ /**
152
+ * Remove Convex-managed agent skills and clean up the lock file if empty.
153
+ * Returns true if any removal occurred.
154
+ */
155
+ export async function removeInstalledSkills({
156
+ projectDir,
157
+ skillNames,
158
+ }: {
159
+ projectDir: string;
160
+ skillNames: string[];
161
+ }): Promise<boolean> {
162
+ if (skillNames.length === 0 || !(await shouldRunSkillsCli())) return false;
163
+
164
+ logMessage(`Removing Convex agent skills: ${skillNames.join(", ")}`);
165
+ const skillsOk = await runSkillsRemove({ cwd: projectDir, skillNames });
166
+ if (!skillsOk) {
167
+ logMessage(
168
+ chalkStderr.yellow(
169
+ "Could not remove agent skills automatically. Remove them manually with: npx skills remove",
170
+ ),
171
+ );
172
+ return false;
173
+ }
174
+
175
+ const lockRemoved = await removeSkillsLockIfEmpty({
176
+ projectDir,
177
+ removedSkillNames: skillNames,
178
+ });
179
+ if (lockRemoved)
180
+ logMessage(`${chalkStderr.green("✔")} Deleted skills-lock.json.`);
181
+ return true;
182
+ }
183
+
184
+ function runSkillsCommand(cwd: string, args: string[]): Promise<boolean> {
185
+ return new Promise((resolve) => {
186
+ const proc = child_process.spawn(
187
+ "npx",
188
+ ["--yes", "skills@latest", ...args],
189
+ {
190
+ cwd,
191
+ stdio: "pipe",
192
+ // .cmd files on Windows require shell execution.
193
+ shell: process.platform === "win32",
194
+ },
195
+ );
196
+ let capturedOutput = "";
197
+ proc.stdout?.on("data", (chunk) => {
198
+ capturedOutput += chunk.toString();
199
+ });
200
+ proc.stderr?.on("data", (chunk) => {
201
+ capturedOutput += chunk.toString();
202
+ });
203
+ proc.on("close", (code) => {
204
+ if (code !== 0 && capturedOutput.trim().length > 0) {
205
+ const lines = capturedOutput.trim().split(/\r?\n/);
206
+ const tail = lines.slice(-10).join("\n");
207
+ logMessage(chalkStderr.gray(`skills output (tail):\n${tail}`));
208
+ }
209
+ resolve(code === 0);
210
+ });
211
+ proc.on("error", () => resolve(false));
212
+ });
213
+ }
@@ -0,0 +1,240 @@
1
+ import path from "path";
2
+ import { chalkStderr } from "chalk";
3
+ import { logMessage } from "../../../bundler/log.js";
4
+ import {
5
+ AGENTS_MD_START_MARKER,
6
+ AGENTS_MD_END_MARKER,
7
+ agentsMdConvexSection,
8
+ } from "../../codegen_templates/agentsmd.js";
9
+ import {
10
+ CLAUDE_MD_START_MARKER,
11
+ CLAUDE_MD_END_MARKER,
12
+ claudeMdConvexSection,
13
+ } from "../../codegen_templates/claudemd.js";
14
+ import { getVersion } from "../versionApi.js";
15
+ import { hashSha256 } from "../utils/hash.js";
16
+ import {
17
+ type AiFilesPaths,
18
+ agentsMdPath,
19
+ claudeMdPath,
20
+ guidelinesPathForConvexDir,
21
+ } from "./paths.js";
22
+ import { type AiFilesConfig, readAiConfig } from "./config.js";
23
+ import { readFileSafe } from "./utils.js";
24
+
25
+ function logGuidelinesStatus({
26
+ guidelinesFile,
27
+ guidelinesRelPath,
28
+ config,
29
+ canonicalGuidelinesHash,
30
+ networkAvailable,
31
+ }: {
32
+ guidelinesFile: string | null;
33
+ guidelinesRelPath: string;
34
+ config: AiFilesConfig;
35
+ canonicalGuidelinesHash: string | null;
36
+ networkAvailable: boolean;
37
+ }): void {
38
+ if (guidelinesFile === null) {
39
+ logMessage(
40
+ ` ${chalkStderr.yellow("⚠")} ${guidelinesRelPath}: not on disk — run ${chalkStderr.bold("npx convex ai-files install")} to reinstall`,
41
+ );
42
+ return;
43
+ }
44
+
45
+ const isLocallyModified =
46
+ config.guidelinesHash !== null &&
47
+ hashSha256(guidelinesFile) !== config.guidelinesHash;
48
+
49
+ if (isLocallyModified) {
50
+ logMessage(
51
+ ` ${chalkStderr.yellow("⚠")} ${guidelinesRelPath}: installed, modified locally (changes will be overwritten on next update)`,
52
+ );
53
+ return;
54
+ }
55
+
56
+ const isOutOfDate =
57
+ networkAvailable &&
58
+ canonicalGuidelinesHash !== null &&
59
+ config.guidelinesHash !== null &&
60
+ config.guidelinesHash !== canonicalGuidelinesHash;
61
+
62
+ if (isOutOfDate) {
63
+ logMessage(
64
+ ` ${chalkStderr.yellow("⚠")} ${guidelinesRelPath}: installed, out of date — run ${chalkStderr.bold("npx convex ai-files update")}`,
65
+ );
66
+ return;
67
+ }
68
+
69
+ logMessage(
70
+ ` ${chalkStderr.green("✔")} ${guidelinesRelPath}: installed${networkAvailable ? ", up to date" : ""}`,
71
+ );
72
+ }
73
+
74
+ function logAgentsMdStatus({
75
+ agentsContent,
76
+ config,
77
+ convexDirName,
78
+ }: {
79
+ agentsContent: string | null;
80
+ config: AiFilesConfig;
81
+ convexDirName: string;
82
+ }): void {
83
+ const hasSection =
84
+ agentsContent !== null &&
85
+ agentsContent.includes(AGENTS_MD_START_MARKER) &&
86
+ agentsContent.includes(AGENTS_MD_END_MARKER);
87
+
88
+ if (!hasSection) {
89
+ logMessage(
90
+ ` ${chalkStderr.yellow("⚠")} AGENTS.md: Convex section missing — run ${chalkStderr.bold("npx convex ai-files install")} to reinstall`,
91
+ );
92
+ return;
93
+ }
94
+
95
+ const currentHash = hashSha256(agentsMdConvexSection(convexDirName));
96
+ if (
97
+ config.agentsMdSectionHash !== null &&
98
+ config.agentsMdSectionHash !== currentHash
99
+ ) {
100
+ logMessage(
101
+ ` ${chalkStderr.yellow("⚠")} AGENTS.md: Convex section out of date — run ${chalkStderr.bold("npx convex ai-files update")}`,
102
+ );
103
+ } else {
104
+ logMessage(
105
+ ` ${chalkStderr.green("✔")} AGENTS.md: Convex section present, up to date`,
106
+ );
107
+ }
108
+ }
109
+
110
+ function logClaudeMdStatus({
111
+ claudeContent,
112
+ config,
113
+ convexDirName,
114
+ }: {
115
+ claudeContent: string | null;
116
+ config: AiFilesConfig;
117
+ convexDirName: string;
118
+ }): void {
119
+ const hasSection =
120
+ claudeContent !== null &&
121
+ claudeContent.includes(CLAUDE_MD_START_MARKER) &&
122
+ claudeContent.includes(CLAUDE_MD_END_MARKER);
123
+
124
+ if (!hasSection) {
125
+ if (claudeContent === null) {
126
+ logMessage(
127
+ ` ${chalkStderr.yellow("⚠")} CLAUDE.md: missing - run ${chalkStderr.bold("npx convex ai-files install")} to create it`,
128
+ );
129
+ } else {
130
+ logMessage(
131
+ ` ${chalkStderr.yellow("⚠")} CLAUDE.md: no Convex section present - run ${chalkStderr.bold("npx convex ai-files update")} to add it`,
132
+ );
133
+ }
134
+ return;
135
+ }
136
+
137
+ const currentHash = hashSha256(claudeMdConvexSection(convexDirName));
138
+ if (config.claudeMdHash !== null && config.claudeMdHash !== currentHash) {
139
+ logMessage(
140
+ ` ${chalkStderr.yellow("⚠")} CLAUDE.md: Convex section out of date - run ${chalkStderr.bold("npx convex ai-files update")}`,
141
+ );
142
+ } else {
143
+ logMessage(
144
+ ` ${chalkStderr.green("✔")} CLAUDE.md: Convex section present, up to date`,
145
+ );
146
+ }
147
+ }
148
+
149
+ function logSkillsStatus({
150
+ config,
151
+ canonicalAgentSkillsSha,
152
+ networkAvailable,
153
+ }: {
154
+ config: AiFilesConfig;
155
+ canonicalAgentSkillsSha: string | null;
156
+ networkAvailable: boolean;
157
+ }): void {
158
+ if (config.installedSkillNames.length === 0) {
159
+ logMessage(
160
+ ` ${chalkStderr.yellow("⚠")} Agent skills: not installed — run ${chalkStderr.bold("npx convex ai-files install")} to install`,
161
+ );
162
+ return;
163
+ }
164
+
165
+ const skillsList = config.installedSkillNames.join(", ");
166
+ const isStale =
167
+ networkAvailable &&
168
+ canonicalAgentSkillsSha !== null &&
169
+ config.agentSkillsSha !== null &&
170
+ config.agentSkillsSha !== canonicalAgentSkillsSha;
171
+
172
+ if (isStale) {
173
+ logMessage(
174
+ ` ${chalkStderr.yellow("⚠")} Agent skills: ${skillsList} — out of date, run ${chalkStderr.bold("npx convex ai-files update")}`,
175
+ );
176
+ } else {
177
+ logMessage(
178
+ ` ${chalkStderr.green("✔")} Agent skills: ${skillsList}${networkAvailable ? " (up to date)" : ""}`,
179
+ );
180
+ }
181
+ }
182
+
183
+ export async function statusAiFiles({
184
+ projectDir,
185
+ convexDir,
186
+ }: AiFilesPaths): Promise<void> {
187
+ const convexDirName = path.relative(projectDir, convexDir);
188
+ const guidelinesRelPath = path.relative(
189
+ projectDir,
190
+ guidelinesPathForConvexDir(convexDir),
191
+ );
192
+
193
+ const config = await readAiConfig({ projectDir, convexDir });
194
+
195
+ if (config === null) {
196
+ logMessage(`Convex AI files: ${chalkStderr.yellow("not installed")}`);
197
+ logMessage(
198
+ ` Run ${chalkStderr.bold("npx convex ai-files install")} to get started, ` +
199
+ `or ${chalkStderr.bold("npx convex ai-files disable")} to opt out.`,
200
+ );
201
+ return;
202
+ }
203
+
204
+ if (!config.enabled) {
205
+ logMessage(`Convex AI files: ${chalkStderr.yellow("disabled")}`);
206
+ logMessage(
207
+ ` Run ${chalkStderr.bold("npx convex ai-files enable")} to re-enable.`,
208
+ );
209
+ return;
210
+ }
211
+
212
+ logMessage(`Convex AI files: ${chalkStderr.green("enabled")}`);
213
+
214
+ const [versionData, guidelinesFile, agentsContent, claudeContent] =
215
+ await Promise.all([
216
+ getVersion(),
217
+ readFileSafe(guidelinesPathForConvexDir(convexDir)),
218
+ readFileSafe(agentsMdPath(projectDir)),
219
+ readFileSafe(claudeMdPath(projectDir)),
220
+ ]);
221
+
222
+ const networkAvailable = versionData.kind === "ok";
223
+ const canonicalGuidelinesHash = networkAvailable
224
+ ? versionData.data.guidelinesHash
225
+ : null;
226
+ const canonicalAgentSkillsSha = networkAvailable
227
+ ? versionData.data.agentSkillsSha
228
+ : null;
229
+
230
+ logGuidelinesStatus({
231
+ guidelinesFile,
232
+ guidelinesRelPath,
233
+ config,
234
+ canonicalGuidelinesHash,
235
+ networkAvailable,
236
+ });
237
+ logAgentsMdStatus({ agentsContent, config, convexDirName });
238
+ logClaudeMdStatus({ claudeContent, config, convexDirName });
239
+ logSkillsStatus({ config, canonicalAgentSkillsSha, networkAvailable });
240
+ }