@cursor/july 0.1.16 → 0.1.18

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 (273) hide show
  1. package/README.md +9 -4
  2. package/dist/bin/agent-serve.js +60 -2
  3. package/dist/channels/slack/api.d.ts.map +1 -1
  4. package/dist/channels/slack/api.js +13 -3
  5. package/dist/channels/slack/auth.d.ts +7 -1
  6. package/dist/channels/slack/auth.d.ts.map +1 -1
  7. package/dist/channels/slack/auth.js +14 -0
  8. package/dist/channels/slack/blocks.d.ts +61 -0
  9. package/dist/channels/slack/blocks.d.ts.map +1 -0
  10. package/dist/channels/slack/blocks.js +90 -0
  11. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  12. package/dist/channels/slack/cursor-account.js +1 -1
  13. package/dist/channels/slack/defaults.d.ts +2 -0
  14. package/dist/channels/slack/defaults.d.ts.map +1 -1
  15. package/dist/channels/slack/defaults.js +29 -3
  16. package/dist/channels/slack/dispatch.d.ts +3 -17
  17. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  18. package/dist/channels/slack/dispatch.js +4 -1
  19. package/dist/channels/slack/inbound.d.ts.map +1 -1
  20. package/dist/channels/slack/inbound.js +6 -1
  21. package/dist/channels/slack/index.d.ts +2 -0
  22. package/dist/channels/slack/index.d.ts.map +1 -1
  23. package/dist/channels/slack/index.js +2 -0
  24. package/dist/channels/slack/init.d.ts.map +1 -1
  25. package/dist/channels/slack/init.js +4 -1
  26. package/dist/channels/slack/interactive.d.ts +39 -3
  27. package/dist/channels/slack/interactive.d.ts.map +1 -1
  28. package/dist/channels/slack/interactive.js +153 -7
  29. package/dist/channels/slack/manifest.d.ts +13 -0
  30. package/dist/channels/slack/manifest.d.ts.map +1 -1
  31. package/dist/channels/slack/manifest.js +36 -14
  32. package/dist/channels/slack/message-body.d.ts +13 -0
  33. package/dist/channels/slack/message-body.d.ts.map +1 -0
  34. package/dist/channels/slack/message-body.js +82 -0
  35. package/dist/channels/slack/setup.d.ts +4 -1
  36. package/dist/channels/slack/setup.d.ts.map +1 -1
  37. package/dist/channels/slack/setup.js +16 -2
  38. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  39. package/dist/channels/slack/slack-channel.js +24 -3
  40. package/dist/channels/slack/thread-context.d.ts +0 -2
  41. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  42. package/dist/channels/slack/thread-context.js +1 -65
  43. package/dist/channels/slack/types.d.ts +93 -1
  44. package/dist/channels/slack/types.d.ts.map +1 -1
  45. package/dist/connections.d.ts +4 -1
  46. package/dist/connections.d.ts.map +1 -1
  47. package/dist/connections.js +4 -1
  48. package/dist/docs/404.html +2 -2
  49. package/dist/docs/ab.html +3 -3
  50. package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
  51. package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
  52. package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
  53. package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
  55. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
  56. package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
  57. package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
  58. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
  59. package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.js} +1 -1
  60. package/dist/docs/building-with-agents.html +3 -3
  61. package/dist/docs/concepts.html +3 -3
  62. package/dist/docs/deployment.html +3 -3
  63. package/dist/docs/evals.html +3 -3
  64. package/dist/docs/example-agents/approval-buddy.html +3 -3
  65. package/dist/docs/example-agents/benny.html +3 -3
  66. package/dist/docs/example-agents/bugbot.html +3 -3
  67. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  68. package/dist/docs/example-agents/codeowners-review.html +3 -3
  69. package/dist/docs/example-agents/concierge.html +3 -3
  70. package/dist/docs/example-agents/fsd.html +3 -3
  71. package/dist/docs/example-agents/index.html +3 -3
  72. package/dist/docs/example-agents/knowledge-base.html +3 -3
  73. package/dist/docs/example-agents/oncall.html +3 -3
  74. package/dist/docs/example-agents/security-reviewer.html +5 -5
  75. package/dist/docs/example-agents/slack-agent.html +3 -3
  76. package/dist/docs/example-agents/weather-agent.html +3 -3
  77. package/dist/docs/guides/agent-to-agent.html +3 -3
  78. package/dist/docs/guides/cloud-runtime.html +3 -3
  79. package/dist/docs/guides/github.html +3 -3
  80. package/dist/docs/guides/human-in-the-loop.html +3 -3
  81. package/dist/docs/guides/mcp-oauth.html +6 -5
  82. package/dist/docs/guides/slack.html +5 -5
  83. package/dist/docs/guides/webhooks.html +3 -3
  84. package/dist/docs/hashmap.json +1 -1
  85. package/dist/docs/hillclimbing.html +3 -3
  86. package/dist/docs/index.html +3 -3
  87. package/dist/docs/quickstart.html +3 -3
  88. package/dist/docs/reference/agent-config.html +3 -3
  89. package/dist/docs/reference/channels.html +3 -3
  90. package/dist/docs/reference/cli.html +3 -3
  91. package/dist/docs/reference/connections.html +14 -7
  92. package/dist/docs/reference/hooks.html +3 -3
  93. package/dist/docs/reference/http-api.html +3 -3
  94. package/dist/docs/reference/instructions.html +3 -3
  95. package/dist/docs/reference/playground.html +3 -3
  96. package/dist/docs/reference/project-layout.html +3 -3
  97. package/dist/docs/reference/prompt.html +3 -3
  98. package/dist/docs/reference/schedules.html +3 -3
  99. package/dist/docs/reference/sessions.html +3 -3
  100. package/dist/docs/reference/skills.html +3 -3
  101. package/dist/docs/reference/subagents.html +3 -3
  102. package/dist/docs/reference/tools.html +3 -3
  103. package/dist/docs/scaffolding-agents.html +3 -3
  104. package/dist/docs/storage.html +3 -3
  105. package/dist/docs/troubleshooting.html +3 -3
  106. package/dist/evals/assertions.d.ts +190 -0
  107. package/dist/evals/assertions.d.ts.map +1 -0
  108. package/dist/evals/assertions.js +461 -0
  109. package/dist/evals/expect.d.ts +95 -0
  110. package/dist/evals/expect.d.ts.map +1 -0
  111. package/dist/evals/expect.js +194 -0
  112. package/dist/evals/judge.d.ts +133 -0
  113. package/dist/evals/judge.d.ts.map +1 -0
  114. package/dist/evals/judge.js +228 -0
  115. package/dist/evals/loaders.d.ts +51 -0
  116. package/dist/evals/loaders.d.ts.map +1 -0
  117. package/dist/evals/loaders.js +124 -0
  118. package/dist/evals/matchers.d.ts +95 -0
  119. package/dist/evals/matchers.d.ts.map +1 -0
  120. package/dist/evals/matchers.js +145 -0
  121. package/dist/evals/reporters.d.ts +70 -0
  122. package/dist/evals/reporters.d.ts.map +1 -0
  123. package/dist/evals/reporters.js +221 -0
  124. package/dist/evals/results.d.ts +103 -0
  125. package/dist/evals/results.d.ts.map +1 -0
  126. package/dist/evals/results.js +28 -0
  127. package/dist/evals/run-facts.d.ts +77 -0
  128. package/dist/evals/run-facts.d.ts.map +1 -0
  129. package/dist/evals/run-facts.js +187 -0
  130. package/dist/evals.d.ts +118 -31
  131. package/dist/evals.d.ts.map +1 -1
  132. package/dist/evals.js +46 -12
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/internal/builtin-tools/index.d.ts +10 -0
  137. package/dist/internal/builtin-tools/index.d.ts.map +1 -0
  138. package/dist/internal/builtin-tools/index.js +27 -0
  139. package/dist/internal/builtin-tools/reminders.d.ts +9 -0
  140. package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
  141. package/dist/internal/builtin-tools/reminders.js +138 -0
  142. package/dist/internal/cli-ax.d.ts +27 -0
  143. package/dist/internal/cli-ax.d.ts.map +1 -1
  144. package/dist/internal/cli-ax.js +90 -12
  145. package/dist/internal/cli-mcp.d.ts +8 -0
  146. package/dist/internal/cli-mcp.d.ts.map +1 -1
  147. package/dist/internal/cli-mcp.js +98 -0
  148. package/dist/internal/cli-slack.d.ts +29 -1
  149. package/dist/internal/cli-slack.d.ts.map +1 -1
  150. package/dist/internal/cli-slack.js +799 -13
  151. package/dist/internal/cursor-event-relay.d.ts +30 -0
  152. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  153. package/dist/internal/cursor-event-relay.js +37 -0
  154. package/dist/internal/deploy-client.d.ts +7 -0
  155. package/dist/internal/deploy-client.d.ts.map +1 -1
  156. package/dist/internal/deploy-client.js +9 -0
  157. package/dist/internal/discovery.d.ts.map +1 -1
  158. package/dist/internal/discovery.js +70 -9
  159. package/dist/internal/distribution.d.ts.map +1 -1
  160. package/dist/internal/distribution.js +2 -0
  161. package/dist/internal/eval-judge-model.d.ts +59 -0
  162. package/dist/internal/eval-judge-model.d.ts.map +1 -0
  163. package/dist/internal/eval-judge-model.js +131 -0
  164. package/dist/internal/eval-run-store.d.ts.map +1 -1
  165. package/dist/internal/eval-run-store.js +12 -20
  166. package/dist/internal/eval-runner.d.ts +31 -25
  167. package/dist/internal/eval-runner.d.ts.map +1 -1
  168. package/dist/internal/eval-runner.js +329 -126
  169. package/dist/internal/evals-client.d.ts.map +1 -1
  170. package/dist/internal/evals-client.js +4 -1
  171. package/dist/internal/local-env.d.ts +8 -0
  172. package/dist/internal/local-env.d.ts.map +1 -0
  173. package/dist/internal/local-env.js +47 -0
  174. package/dist/internal/playground/static.d.ts +3 -0
  175. package/dist/internal/playground/static.d.ts.map +1 -1
  176. package/dist/internal/playground/static.js +3 -0
  177. package/dist/internal/reminder-runner.d.ts +7 -0
  178. package/dist/internal/reminder-runner.d.ts.map +1 -1
  179. package/dist/internal/reminder-runner.js +21 -0
  180. package/dist/internal/run-client.d.ts +15 -0
  181. package/dist/internal/run-client.d.ts.map +1 -1
  182. package/dist/internal/run-client.js +3 -2
  183. package/dist/internal/sdk-runner.d.ts +51 -1
  184. package/dist/internal/sdk-runner.d.ts.map +1 -1
  185. package/dist/internal/sdk-runner.js +176 -36
  186. package/dist/internal/server.d.ts.map +1 -1
  187. package/dist/internal/server.js +11 -1
  188. package/dist/internal/session-engine.d.ts +18 -5
  189. package/dist/internal/session-engine.d.ts.map +1 -1
  190. package/dist/internal/session-engine.js +81 -19
  191. package/dist/internal/slack-provision-client.d.ts +133 -0
  192. package/dist/internal/slack-provision-client.d.ts.map +1 -0
  193. package/dist/internal/slack-provision-client.js +286 -0
  194. package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
  195. package/dist/playground/assets/index-DRjS5klQ.js +85 -0
  196. package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
  197. package/dist/playground/index.html +2 -2
  198. package/dist/types.d.ts +98 -4
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +26 -0
  201. package/docs/example-agents/security-reviewer.md +1 -1
  202. package/docs/guides/mcp-oauth.md +10 -5
  203. package/docs/guides/slack.md +3 -2
  204. package/docs/reference/connections.md +28 -18
  205. package/package.json +20 -2
  206. package/skills/create-agent/SKILL.md +15 -7
  207. package/skills/debug/SKILL.md +3 -1
  208. package/skills/mcp-auth/SKILL.md +1 -1
  209. package/skills/setup-slack/SKILL.md +177 -11
  210. package/src/bin/agent-serve.ts +63 -2
  211. package/src/channels/slack/api.ts +16 -3
  212. package/src/channels/slack/auth.ts +16 -1
  213. package/src/channels/slack/blocks.ts +139 -0
  214. package/src/channels/slack/cursor-account.ts +1 -0
  215. package/src/channels/slack/defaults.ts +32 -4
  216. package/src/channels/slack/dispatch.ts +6 -19
  217. package/src/channels/slack/inbound.ts +6 -1
  218. package/src/channels/slack/index.ts +2 -0
  219. package/src/channels/slack/init.ts +3 -0
  220. package/src/channels/slack/interactive.ts +184 -19
  221. package/src/channels/slack/manifest.ts +37 -6
  222. package/src/channels/slack/message-body.ts +90 -0
  223. package/src/channels/slack/setup.ts +16 -2
  224. package/src/channels/slack/slack-channel.ts +37 -6
  225. package/src/channels/slack/thread-context.ts +1 -69
  226. package/src/channels/slack/types.ts +105 -1
  227. package/src/connections.ts +4 -1
  228. package/src/evals/assertions.ts +704 -0
  229. package/src/evals/expect.ts +272 -0
  230. package/src/evals/judge.ts +346 -0
  231. package/src/evals/loaders.ts +113 -0
  232. package/src/evals/matchers.ts +246 -0
  233. package/src/evals/reporters.ts +274 -0
  234. package/src/evals/results.ts +132 -0
  235. package/src/evals/run-facts.ts +274 -0
  236. package/src/evals.ts +205 -42
  237. package/src/index.ts +2 -0
  238. package/src/internal/builtin-tools/index.ts +40 -0
  239. package/src/internal/builtin-tools/reminders.ts +158 -0
  240. package/src/internal/cli-ax.ts +134 -15
  241. package/src/internal/cli-mcp.ts +118 -5
  242. package/src/internal/cli-slack.ts +1017 -15
  243. package/src/internal/cursor-event-relay.ts +67 -0
  244. package/src/internal/deploy-client.ts +10 -0
  245. package/src/internal/discovery.ts +83 -5
  246. package/src/internal/distribution.ts +2 -0
  247. package/src/internal/eval-judge-model.ts +148 -0
  248. package/src/internal/eval-run-store.ts +10 -14
  249. package/src/internal/eval-runner.ts +460 -214
  250. package/src/internal/evals-client.ts +5 -0
  251. package/src/internal/local-env.ts +53 -0
  252. package/src/internal/playground/static.ts +3 -0
  253. package/src/internal/reminder-runner.ts +30 -0
  254. package/src/internal/run-client.ts +17 -1
  255. package/src/internal/sdk-runner.ts +234 -35
  256. package/src/internal/server.ts +12 -0
  257. package/src/internal/session-engine.ts +103 -21
  258. package/src/internal/slack-provision-client.ts +442 -0
  259. package/src/types.ts +125 -4
  260. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  261. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  262. package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
  263. package/dist/internal/json-dir-store.d.ts +0 -32
  264. package/dist/internal/json-dir-store.d.ts.map +0 -1
  265. package/dist/internal/persistence-coordinator.d.ts +0 -127
  266. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  267. package/dist/persistence.d.ts +0 -184
  268. package/dist/persistence.d.ts.map +0 -1
  269. package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
  270. package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
  271. package/dist/playground/assets/index-ByV4nfch.css +0 -1
  272. /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
  273. /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  /**
2
- * `agent-serve slack …` CLI — manifests, setup, doctor (Socket Mode).
2
+ * `agent-serve slack …` CLI — manifests, setup, doctor (Socket Mode), and
3
+ * managed provisioning (`create` / `destroy`) through Cursor's
4
+ * "Add to Slack" API (`slack-provision-client.ts`).
3
5
  */
4
6
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
7
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -10,23 +12,56 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
12
  step((generator = generator.apply(thisArg, _arguments || [])).next());
11
13
  });
12
14
  };
13
- import { mkdir, writeFile } from "node:fs/promises";
14
- import { basename, join, resolve as resolvePath } from "node:path";
15
- import { buildSlackManifestPair, formatSlackSetupGuide, initSlackChannel, runSlackDoctor, } from "../channels/slack/index.js";
15
+ import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
16
+ import { basename, extname, join, resolve as resolvePath } from "node:path";
17
+ import { buildSlackManifest, buildSlackManifestPair, envPrefixFromName, formatSlackSetupGuide, initSlackChannel, runSlackDoctor, slackEnvKeys, } from "../channels/slack/index.js";
18
+ import { resolveDeployAuth } from "./cli-deploy.js";
19
+ import { cursorExternalApiUrl } from "./cursor/credentials.js";
20
+ import { slugFromDirectoryName } from "./deploy-client.js";
21
+ import { describeError } from "./describe-error.js";
22
+ import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
23
+ import { loadAgentLocalEnv } from "./local-env.js";
24
+ import { openBrowser } from "./open-browser.js";
25
+ import { AgentServeSlackClient, SlackProvisionApiError, } from "./slack-provision-client.js";
16
26
  const SLACK_HELP = `agent-serve slack — Slack channel pack helpers (Socket Mode)
17
27
 
18
28
  Usage:
19
29
  agent-serve slack setup
30
+ agent-serve slack create [--dir .] [--name NAME] [--slack-team T0123ABCD]
31
+ [--prod] [--team <id>] [--icon URL-or-FILE]
32
+ [--force] [--prefix PREFIX | --no-prefix] [--json]
33
+ agent-serve slack destroy [--dir .] [--slack-team T0123ABCD] [--prod]
34
+ [--team <id>] [--json]
35
+ agent-serve slack icon <URL-or-FILE> [--dir .] [--slack-team T0123ABCD]
36
+ [--prod] [--team <id>] [--json]
20
37
  agent-serve slack init [--dir .] [--name NAME] [--prefix PREFIX | --no-prefix]
21
38
  [--channel-posts]
22
39
  agent-serve slack manifest --env dev|prod|both [--dir .] [--name NAME]
23
40
  [--print]
24
- agent-serve slack doctor [--prefix PREFIX] [--json]
41
+ agent-serve slack doctor [--dir .] [--prefix PREFIX | --no-prefix] [--json]
42
+
43
+ create/destroy/icon — Cursor "Add to Slack" managed provisioning:
44
+ create One-command Path B setup: connects the Slack workspace (browser
45
+ consent) when needed, creates + installs this agent's dedicated
46
+ Socket Mode app from the generated manifest, writes the token
47
+ pair into <dir>/.env.local, then runs doctor (prose mode; --json
48
+ reports without the doctor pass). Requires
49
+ \`agent-serve login\` (or CURSOR_API_KEY) and the feature gate.
50
+ --prod provisions the prod app (default dev); --slack-team picks
51
+ the workspace when several are connected. --icon sets the app's
52
+ icon (best-effort: a bad icon warns, never fails the create).
53
+ A re-run refuses to overwrite manifest edits made by hand at
54
+ api.slack.com (manifest_drift); --force overwrites them.
55
+ destroy Delete the provisioned Slack app for this agent (+env). Tokens
56
+ already in .env.local are left in place (they just stop working).
57
+ icon Set the provisioned app's icon: an https image URL or a local
58
+ png/jpg/gif file (at most 512KB).
25
59
 
26
60
  Env tokens:
27
61
  single agent: SLACK_BOT_TOKEN + SLACK_APP_TOKEN
28
62
  multi-agent: <PREFIX>_SLACK_BOT_TOKEN + <PREFIX>_SLACK_APP_TOKEN
29
- (init defaults PREFIX from the directory name)
63
+ (init/create default PREFIX from the directory name;
64
+ doctor defaults to it too when those keys are set)
30
65
 
31
66
  See also: skills/setup-slack/SKILL.md
32
67
  `;
@@ -42,6 +77,12 @@ export function cmdSlack(subcommand, options) {
42
77
  case "setup":
43
78
  process.stdout.write(formatSlackSetupGuide());
44
79
  return 0;
80
+ case "create":
81
+ return cmdSlackCreate(options);
82
+ case "destroy":
83
+ return cmdSlackDestroy(options);
84
+ case "icon":
85
+ return cmdSlackIcon(options);
45
86
  case "init":
46
87
  return cmdSlackInit(options);
47
88
  case "manifest":
@@ -96,7 +137,10 @@ function runManifest(options) {
96
137
  const env = (_a = options.env) !== null && _a !== void 0 ? _a : "both";
97
138
  const dir = resolvePath(options.dir);
98
139
  const name = (_b = options.name) !== null && _b !== void 0 ? _b : basename(dir);
99
- const pair = buildSlackManifestPair(Object.assign({ name }, (options.channelPosts === true ? { channelPosts: true } : {})));
140
+ const pair = buildSlackManifestPair(Object.assign({ name,
141
+ // These files are pasted into api.slack.com by hand, so they must not
142
+ // carry the manager-token-only managed_app_settings block.
143
+ managedByCursor: false }, (options.channelPosts === true ? { channelPosts: true } : {})));
100
144
  if (options.print) {
101
145
  if (env === "dev" || env === "both") {
102
146
  process.stdout.write(`${JSON.stringify(pair.dev, null, 2)}\n`);
@@ -128,17 +172,759 @@ function runManifest(options) {
128
172
  }
129
173
  function runDoctor(options) {
130
174
  return __awaiter(this, void 0, void 0, function* () {
131
- const result = yield runSlackDoctor(options.prefix === undefined ? {} : { envPrefix: options.prefix });
175
+ var _a;
176
+ const dir = resolvePath(options.dir);
177
+ // Tokens usually live in the agent's .env.local; real env still wins.
178
+ loadAgentLocalEnv(dir);
179
+ const envPrefix = resolveDoctorEnvPrefix(options, dir);
180
+ const doctor = (_a = options.doctorImpl) !== null && _a !== void 0 ? _a : runSlackDoctor;
181
+ const result = yield doctor(envPrefix === undefined ? {} : { envPrefix });
132
182
  if (options.json) {
133
183
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
134
184
  }
135
185
  else {
136
- process.stdout.write(`env: ${result.envKeys.bot} / ${result.envKeys.app}\n`);
137
- for (const check of result.checks) {
138
- process.stdout.write(`${check.ok ? "ok" : "!!"} ${check.id}: ${check.message}\n`);
139
- }
140
- process.stdout.write(result.ok ? "doctor: pass\n" : "doctor: fail\n");
186
+ process.stdout.write(renderDoctorReport(result));
141
187
  }
142
188
  return result.ok ? 0 : 1;
143
189
  });
144
190
  }
191
+ /**
192
+ * --prefix / --no-prefix win; otherwise the directory-derived prefix
193
+ * create/init use, but only when one of its keys is set — unprefixed
194
+ * setups keep the bare SLACK_* default.
195
+ */
196
+ function resolveDoctorEnvPrefix(options, dir) {
197
+ if (options.noPrefix === true) {
198
+ return undefined;
199
+ }
200
+ if (options.prefix !== undefined) {
201
+ return options.prefix;
202
+ }
203
+ const derived = envPrefixFromName(basename(dir));
204
+ const keys = slackEnvKeys(derived);
205
+ return process.env[keys.bot] !== undefined ||
206
+ process.env[keys.app] !== undefined
207
+ ? derived
208
+ : undefined;
209
+ }
210
+ // ============================================================================
211
+ // create / destroy — Cursor "Add to Slack" managed provisioning
212
+ // ============================================================================
213
+ const DEFAULT_CONNECT_POLL_INTERVAL_MS = 3000;
214
+ const DEFAULT_CONNECT_POLL_TIMEOUT_MS = 5 * 60 * 1000;
215
+ function resolveSlackAppTarget(options, err) {
216
+ var _a, _b;
217
+ if (options.env === "both" && options.prod !== true) {
218
+ err("Provisioning targets one env at a time; pass --prod for the prod app (default dev).\n");
219
+ return 1;
220
+ }
221
+ const env = options.prod === true || options.env === "prod" ? "prod" : "dev";
222
+ const dir = resolvePath(options.dir);
223
+ // Backend slug rule, mirrored so the error names the real problem
224
+ // instead of a generic 400.
225
+ const slug = slugFromDirectoryName(basename(dir));
226
+ if (slug === undefined || !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(slug)) {
227
+ err(`Could not derive a valid app slug from the directory name ${JSON.stringify(basename(dir))} (slugs start with a letter or digit; lowercase letters, digits, '-', '_'); rename the directory or pass --dir <agent>.\n`);
228
+ return 1;
229
+ }
230
+ const name = (_a = options.name) !== null && _a !== void 0 ? _a : basename(dir);
231
+ const envPrefix = options.noPrefix === true
232
+ ? undefined
233
+ : ((_b = options.prefix) !== null && _b !== void 0 ? _b : envPrefixFromName(basename(dir)));
234
+ return { dir, name, slug, env, envPrefix, envKeys: slackEnvKeys(envPrefix) };
235
+ }
236
+ function errStream(options) {
237
+ var _a;
238
+ return (_a = options.err) !== null && _a !== void 0 ? _a : ((text) => void process.stderr.write(text));
239
+ }
240
+ const SLACK_ICON_MAX_BYTES = 524288;
241
+ const SLACK_ICON_URL_MAX_LENGTH = 3000;
242
+ const SLACK_ICON_CONTENT_TYPES = {
243
+ ".png": "image/png",
244
+ ".jpg": "image/jpeg",
245
+ ".jpeg": "image/jpeg",
246
+ ".gif": "image/gif",
247
+ };
248
+ function resolveSlackIconSource(raw, err) {
249
+ return __awaiter(this, void 0, void 0, function* () {
250
+ if (raw.startsWith("https://")) {
251
+ if (raw.length > SLACK_ICON_URL_MAX_LENGTH) {
252
+ err(`Icon URLs must be at most ${SLACK_ICON_URL_MAX_LENGTH} characters (got ${raw.length}).\n`);
253
+ return 1;
254
+ }
255
+ return { kind: "url", url: raw };
256
+ }
257
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
258
+ err(`Icon URLs must be https (got ${JSON.stringify(raw)}).\n`);
259
+ return 1;
260
+ }
261
+ const path = resolvePath(raw);
262
+ const contentType = SLACK_ICON_CONTENT_TYPES[extname(path).toLowerCase()];
263
+ if (contentType === undefined) {
264
+ err(`Icon files must be .png, .jpg/.jpeg, or .gif (got ${JSON.stringify(basename(path))}).\n`);
265
+ return 1;
266
+ }
267
+ let data;
268
+ try {
269
+ data = yield readFile(path);
270
+ }
271
+ catch (error) {
272
+ err(`Could not read the icon file ${path}: ${describeError(error)}\n`);
273
+ return 1;
274
+ }
275
+ if (data.length > SLACK_ICON_MAX_BYTES) {
276
+ err(`Icon files must be at most ${SLACK_ICON_MAX_BYTES} bytes (${path} is ${data.length}).\n`);
277
+ return 1;
278
+ }
279
+ return { kind: "file", base64: data.toString("base64"), contentType, path };
280
+ });
281
+ }
282
+ function toSlackAppIconInput(source) {
283
+ return source.kind === "url"
284
+ ? { url: source.url }
285
+ : { base64: source.base64, contentType: source.contentType };
286
+ }
287
+ /**
288
+ * URL icons ride the provision request; file icons post to the icon
289
+ * endpoint afterwards. Best-effort: failures warn, never fail the create.
290
+ */
291
+ function applySlackCreateIcon(ctx, source, result) {
292
+ return __awaiter(this, void 0, void 0, function* () {
293
+ if (source === undefined) {
294
+ return undefined;
295
+ }
296
+ let failure;
297
+ if (source.kind === "url") {
298
+ if (result.iconStatus !== "set") {
299
+ failure = "Slack did not accept the icon URL";
300
+ }
301
+ }
302
+ else {
303
+ try {
304
+ yield ctx.client.setAppIcon({
305
+ teamId: ctx.teamId,
306
+ slackAppId: result.appId,
307
+ icon: toSlackAppIconInput(source),
308
+ });
309
+ }
310
+ catch (error) {
311
+ failure = describeError(error);
312
+ }
313
+ }
314
+ if (failure === undefined) {
315
+ return "set";
316
+ }
317
+ const retryWith = source.kind === "url" ? source.url : source.path;
318
+ ctx.err(`warning: setting the Slack app icon failed: ${failure}\nRetry with \`${CLI} slack icon ${retryWith}\`.\n`);
319
+ return "failed";
320
+ });
321
+ }
322
+ /**
323
+ * Credentials → provisioning client → team id, sharing the deploy CLI's
324
+ * resolution (stored login / CURSOR_API_KEY, `--team` override, GetMe
325
+ * default-team fallback). Returns an exit code (error already rendered)
326
+ * on failure.
327
+ */
328
+ function prepareProvision(options) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ var _a, _b, _c, _d;
331
+ const out = (_a = options.out) !== null && _a !== void 0 ? _a : ((text) => void process.stdout.write(text));
332
+ const err = errStream(options);
333
+ const auth = yield resolveDeployAuth({
334
+ team: options.team,
335
+ apiKey: options.apiKey,
336
+ fetchImpl: options.fetchImpl,
337
+ err,
338
+ });
339
+ if (typeof auth === "number") {
340
+ return auth;
341
+ }
342
+ // Provisioning routes live on the external API host beside deploys.
343
+ return {
344
+ client: new AgentServeSlackClient({
345
+ backendUrl: cursorExternalApiUrl(),
346
+ apiKey: auth.apiKey,
347
+ fetchImpl: options.fetchImpl,
348
+ }),
349
+ teamId: auth.teamId,
350
+ json: options.json === true,
351
+ out,
352
+ err,
353
+ openBrowserImpl: (_b = options.openBrowserImpl) !== null && _b !== void 0 ? _b : openBrowser,
354
+ pollIntervalMs: (_c = options.connectPollIntervalMs) !== null && _c !== void 0 ? _c : DEFAULT_CONNECT_POLL_INTERVAL_MS,
355
+ pollTimeoutMs: (_d = options.connectPollTimeoutMs) !== null && _d !== void 0 ? _d : DEFAULT_CONNECT_POLL_TIMEOUT_MS,
356
+ };
357
+ });
358
+ }
359
+ /** "Name (T0123ABCD)", or the bare id when the backend has no name. */
360
+ function formatWorkspace(workspace) {
361
+ var _a;
362
+ const name = (_a = workspace.slackTeamName) !== null && _a !== void 0 ? _a : "";
363
+ return name === ""
364
+ ? workspace.slackTeamId
365
+ : `${name} (${workspace.slackTeamId})`;
366
+ }
367
+ function matchConnectedWorkspace(workspaces, wait) {
368
+ const candidates = wait.slackTeamId === undefined
369
+ ? workspaces
370
+ : workspaces.filter((workspace) => workspace.slackTeamId === wait.slackTeamId);
371
+ if (wait.notConnectedAt === undefined) {
372
+ return candidates[0];
373
+ }
374
+ return candidates.find((workspace) => workspace.connectedAt !== wait.notConnectedAt);
375
+ }
376
+ /**
377
+ * Mint the browser consent URL, open it, and poll `GET /status` until the
378
+ * awaited workspace shows up (or the poll times out). Returns the
379
+ * connected workspace, or an exit code with the error already rendered.
380
+ */
381
+ function runConnectFlow(ctx, wait) {
382
+ return __awaiter(this, void 0, void 0, function* () {
383
+ let url;
384
+ try {
385
+ url = yield ctx.client.connect(ctx.teamId);
386
+ }
387
+ catch (error) {
388
+ return renderSlackApiError(error, ctx.err);
389
+ }
390
+ ctx.err(`\nConnect your Slack workspace to Cursor — approve in the browser:\n\n ${url}\n\n` +
391
+ `After approving in Slack you land on cursor.com, which links the workspace to your Cursor account — sign in there if the browser is not already signed in as you.\n\n` +
392
+ `(waiting for the Slack consent to complete, up to ${Math.round(ctx.pollTimeoutMs / 60000)} minutes)\n`);
393
+ ctx.openBrowserImpl(url);
394
+ const deadline = Date.now() + ctx.pollTimeoutMs;
395
+ let lastPollFailure;
396
+ for (;;) {
397
+ let workspace;
398
+ let sleepMs = ctx.pollIntervalMs;
399
+ try {
400
+ const status = yield ctx.client.status(ctx.teamId);
401
+ workspace = matchConnectedWorkspace(status.workspaces, wait);
402
+ lastPollFailure = undefined;
403
+ }
404
+ catch (error) {
405
+ // Transient poll failures must not abort a minted consent flow; only
406
+ // a still-failing poll at the deadline surfaces the error.
407
+ if (Date.now() >= deadline) {
408
+ return renderSlackApiError(error, ctx.err);
409
+ }
410
+ const failure = describeError(error);
411
+ if (failure !== lastPollFailure) {
412
+ lastPollFailure = failure;
413
+ ctx.err(`(status poll failed, retrying: ${failure})\n`);
414
+ }
415
+ if (error instanceof SlackProvisionApiError &&
416
+ error.retryAfterSeconds !== undefined) {
417
+ // Respect backend backpressure instead of hammering at the poll rate.
418
+ sleepMs = Math.max(sleepMs, error.retryAfterSeconds * 1000);
419
+ }
420
+ }
421
+ if (workspace !== undefined) {
422
+ ctx.err(`Connected Slack workspace ${workspace.slackTeamId}.\n`);
423
+ return workspace;
424
+ }
425
+ if (Date.now() >= deadline) {
426
+ ctx.err(`Timed out waiting for the Slack workspace connection. The consent link stays valid for ~10 minutes — approve it, finish the sign-in on cursor.com, then re-run \`${CLI} slack create\`.\n`);
427
+ return 1;
428
+ }
429
+ yield sleep(sleepMs);
430
+ }
431
+ });
432
+ }
433
+ export function cmdSlackCreate(options) {
434
+ return __awaiter(this, void 0, void 0, function* () {
435
+ var _a, _b;
436
+ const target = resolveSlackAppTarget(options, errStream(options));
437
+ if (typeof target === "number") {
438
+ return target;
439
+ }
440
+ // Resolved before any network I/O so a bad path/type/size fails fast.
441
+ let iconSource;
442
+ if (options.icon !== undefined) {
443
+ const resolved = yield resolveSlackIconSource(options.icon, errStream(options));
444
+ if (typeof resolved === "number") {
445
+ return resolved;
446
+ }
447
+ iconSource = resolved;
448
+ }
449
+ const ctx = yield prepareProvision(options);
450
+ if (typeof ctx === "number") {
451
+ return ctx;
452
+ }
453
+ // Workspace: --slack-team wins; one → use it; none → connect; several → ask.
454
+ let status;
455
+ try {
456
+ status = yield ctx.client.status(ctx.teamId);
457
+ }
458
+ catch (error) {
459
+ return renderSlackApiError(error, ctx.err);
460
+ }
461
+ let workspace;
462
+ if (options.slackTeam !== undefined) {
463
+ workspace = status.workspaces.find((candidate) => candidate.slackTeamId === options.slackTeam);
464
+ }
465
+ else if (status.workspaces.length === 1) {
466
+ workspace = status.workspaces[0];
467
+ }
468
+ else if (status.workspaces.length > 1) {
469
+ ctx.err(`Multiple Slack workspaces are connected:\n` +
470
+ status.workspaces
471
+ .map((candidate) => ` - ${formatWorkspace(candidate)} (Slack user ${candidate.slackUserId}, connected ${candidate.connectedAt})\n`)
472
+ .join("") +
473
+ `Pass --slack-team <id> to pick one.\n`);
474
+ return 1;
475
+ }
476
+ if (workspace === undefined) {
477
+ const connected = yield runConnectFlow(ctx, {
478
+ slackTeamId: options.slackTeam,
479
+ });
480
+ if (typeof connected === "number") {
481
+ return connected;
482
+ }
483
+ workspace = connected;
484
+ }
485
+ let wrote;
486
+ try {
487
+ wrote = yield scaffoldSlackChannelFiles(options, target);
488
+ }
489
+ catch (error) {
490
+ ctx.err(`${describeError(error)}\n`);
491
+ return 1;
492
+ }
493
+ if (wrote.length > 0 && !ctx.json) {
494
+ ctx.err(`Scaffolded the Slack channel: ${wrote.join(", ")}\n`);
495
+ }
496
+ // A re-run rebuilds this manifest from flags, so backend-side
497
+ // customizations reset to it (documented in the skill).
498
+ const manifest = buildSlackManifest({
499
+ name: target.name,
500
+ env: target.env,
501
+ channelPosts: options.channelPosts === true,
502
+ });
503
+ const manifestJson = JSON.stringify(manifest);
504
+ // One connect-and-retry for not_connected / insufficient_scopes /
505
+ // manager_reauth_required (all fixed by a fresh consent grant).
506
+ let result;
507
+ let connectRetried = false;
508
+ for (;;) {
509
+ try {
510
+ result = yield ctx.client.provisionApp({
511
+ teamId: ctx.teamId,
512
+ slackTeamId: workspace.slackTeamId,
513
+ slug: target.slug,
514
+ env: target.env,
515
+ manifest: manifestJson,
516
+ icon: (iconSource === null || iconSource === void 0 ? void 0 : iconSource.kind) === "url" ? { url: iconSource.url } : undefined,
517
+ force: options.force === true ? true : undefined,
518
+ });
519
+ break;
520
+ }
521
+ catch (error) {
522
+ if (error instanceof SlackProvisionApiError &&
523
+ (error.code === "not_connected" ||
524
+ error.code === "insufficient_scopes" ||
525
+ error.code === "manager_reauth_required") &&
526
+ !connectRetried) {
527
+ connectRetried = true;
528
+ // All three codes arrive holding a row that GET /status already
529
+ // lists, so the poll waits for a fresh grant instead of re-matching
530
+ // that row.
531
+ const wait = {
532
+ slackTeamId: workspace.slackTeamId,
533
+ notConnectedAt: workspace.connectedAt,
534
+ };
535
+ if (error.code === "insufficient_scopes") {
536
+ ctx.err(`The Slack connection is missing scopes provisioning needs; approving the consent link again grants them.\n`);
537
+ }
538
+ else if (error.code === "manager_reauth_required") {
539
+ ctx.err(`Slack revoked Cursor's authorization for workspace ${workspace.slackTeamId}; re-approving the consent link restores it.\n`);
540
+ }
541
+ else {
542
+ ctx.err(`The Slack workspace is not connected to Cursor; starting the connect flow.\n`);
543
+ }
544
+ const connected = yield runConnectFlow(ctx, wait);
545
+ if (typeof connected === "number") {
546
+ return connected;
547
+ }
548
+ workspace = connected;
549
+ continue;
550
+ }
551
+ return renderCreateProvisionError(error, ctx.err);
552
+ }
553
+ }
554
+ const iconStatus = yield applySlackCreateIcon(ctx, iconSource, result);
555
+ if (result.status === "pending_admin_approval") {
556
+ if (ctx.json) {
557
+ ctx.out(`${JSON.stringify({
558
+ ok: false,
559
+ appId: result.appId,
560
+ status: result.status,
561
+ slackTeamId: workspace.slackTeamId,
562
+ envKeys: target.envKeys,
563
+ wrote,
564
+ oauthAuthorizeUrl: (_a = result.oauthAuthorizeUrl) !== null && _a !== void 0 ? _a : null,
565
+ iconStatus,
566
+ }, null, 2)}\n`);
567
+ }
568
+ else {
569
+ ctx.err(`The Slack app was created (app ${result.appId}), but installing it needs a Slack workspace-admin approval.\n` +
570
+ (result.oauthAuthorizeUrl === undefined
571
+ ? ""
572
+ : `\n Approve it (or send this to a workspace admin):\n\n ${result.oauthAuthorizeUrl}\n\n`) +
573
+ `Once an admin approves the app, re-run \`${CLI} slack create\` to finish the install and mint the tokens.\n`);
574
+ }
575
+ return 1;
576
+ }
577
+ // Tokens come back exactly once and are never printed.
578
+ const envEntries = [{ key: target.envKeys.bot, value: result.botToken }];
579
+ if (result.appToken !== undefined) {
580
+ envEntries.push({ key: target.envKeys.app, value: result.appToken });
581
+ }
582
+ const envFile = join(target.dir, ".env.local");
583
+ try {
584
+ yield upsertEnvFile(envFile, envEntries);
585
+ if (yield ensureEnvLocalGitignored(target.dir)) {
586
+ wrote.push(".gitignore (+.env.local)");
587
+ }
588
+ }
589
+ catch (error) {
590
+ ctx.err(`Could not write ${envFile}: ${describeError(error)}\n`);
591
+ return 1;
592
+ }
593
+ wrote.push(".env.local");
594
+ for (const { key, value } of envEntries) {
595
+ process.env[key] = value;
596
+ }
597
+ if (result.appToken === undefined) {
598
+ ctx.err(`warning: Slack did not return an app-level token, so ${target.envKeys.app} was not written (an existing value was left as-is and may belong to an older app); re-run \`${CLI} slack create\` to recover it.\n`);
599
+ }
600
+ if (ctx.json) {
601
+ ctx.out(`${JSON.stringify({
602
+ ok: true,
603
+ appId: result.appId,
604
+ status: result.status,
605
+ slackTeamId: workspace.slackTeamId,
606
+ envKeys: target.envKeys,
607
+ wrote,
608
+ iconStatus,
609
+ }, null, 2)}\n`);
610
+ return 0;
611
+ }
612
+ ctx.out(`Provisioned the ${target.env} Slack app for ${target.slug} (app ${result.appId}) in workspace ${workspace.slackTeamId}.\n` +
613
+ ` wrote ${envEntries.map((entry) => entry.key).join(" + ")} to ${envFile}\n\n`);
614
+ const doctor = (_b = options.doctorImpl) !== null && _b !== void 0 ? _b : runSlackDoctor;
615
+ const doctorResult = yield doctor(target.envPrefix === undefined ? {} : { envPrefix: target.envPrefix });
616
+ ctx.out(renderDoctorReport(doctorResult));
617
+ ctx.out(`\nNext steps:\n` +
618
+ ` 1. ${CLI} dev ${options.dir}\n` +
619
+ ` 2. In Slack, invite @${manifest.features.bot_user.display_name} to a channel and @mention it (or DM it).\n`);
620
+ return 0;
621
+ });
622
+ }
623
+ /**
624
+ * Resolve the one provisioned app for (slug, env[, --slack-team]) from
625
+ * `GET /status`. Returns an exit code with the error already rendered
626
+ * when zero or several apps match.
627
+ */
628
+ function resolveProvisionedApp(ctx, target, options) {
629
+ return __awaiter(this, void 0, void 0, function* () {
630
+ let status;
631
+ try {
632
+ status = yield ctx.client.status(ctx.teamId);
633
+ }
634
+ catch (error) {
635
+ return renderSlackApiError(error, ctx.err);
636
+ }
637
+ const sameSlug = status.apps.filter((app) => app.slug === target.slug);
638
+ let candidates = sameSlug.filter((app) => app.env === target.env);
639
+ if (options.slackTeam !== undefined) {
640
+ candidates = candidates.filter((app) => app.slackTeamId === options.slackTeam);
641
+ }
642
+ if (candidates.length === 0) {
643
+ ctx.err(`No provisioned ${target.env} Slack app for "${target.slug}"${options.slackTeam === undefined
644
+ ? ""
645
+ : ` in workspace ${options.slackTeam}`} (team ${ctx.teamId}).\n` +
646
+ (sameSlug.length === 0
647
+ ? ""
648
+ : `Provisioned apps for this agent:\n${formatAppList(sameSlug)}`));
649
+ return 1;
650
+ }
651
+ if (candidates.length > 1) {
652
+ ctx.err(`Multiple provisioned Slack apps match "${target.slug}" (${target.env}):\n` +
653
+ formatAppList(candidates) +
654
+ `Pass --slack-team <id> to pick one.\n`);
655
+ return 1;
656
+ }
657
+ return candidates[0];
658
+ });
659
+ }
660
+ export function cmdSlackDestroy(options) {
661
+ return __awaiter(this, void 0, void 0, function* () {
662
+ const target = resolveSlackAppTarget(options, errStream(options));
663
+ if (typeof target === "number") {
664
+ return target;
665
+ }
666
+ const ctx = yield prepareProvision(options);
667
+ if (typeof ctx === "number") {
668
+ return ctx;
669
+ }
670
+ const app = yield resolveProvisionedApp(ctx, target, options);
671
+ if (typeof app === "number") {
672
+ return app;
673
+ }
674
+ try {
675
+ yield ctx.client.deleteApp({
676
+ teamId: ctx.teamId,
677
+ slackAppId: app.slackAppId,
678
+ });
679
+ }
680
+ catch (error) {
681
+ return renderSlackApiError(error, ctx.err);
682
+ }
683
+ if (ctx.json) {
684
+ ctx.out(`${JSON.stringify({
685
+ ok: true,
686
+ deleted: true,
687
+ appId: app.slackAppId,
688
+ slackTeamId: app.slackTeamId,
689
+ slug: target.slug,
690
+ env: target.env,
691
+ }, null, 2)}\n`);
692
+ return 0;
693
+ }
694
+ ctx.out(`Deleted the ${target.env} Slack app for ${target.slug} (app ${app.slackAppId}) from workspace ${app.slackTeamId}.\n` +
695
+ `Tokens in .env.local were left in place — ${target.envKeys.bot} / ${target.envKeys.app} are now inert; remove them whenever you like.\n`);
696
+ return 0;
697
+ });
698
+ }
699
+ export function cmdSlackIcon(options) {
700
+ return __awaiter(this, void 0, void 0, function* () {
701
+ const err = errStream(options);
702
+ if (options.iconSource === undefined) {
703
+ err(`Usage: ${CLI} slack icon <https-url-or-file> [--dir .] [--slack-team T0123ABCD] [--prod] [--team <id>] [--json]\n`);
704
+ return 1;
705
+ }
706
+ const source = yield resolveSlackIconSource(options.iconSource, err);
707
+ if (typeof source === "number") {
708
+ return source;
709
+ }
710
+ const target = resolveSlackAppTarget(options, err);
711
+ if (typeof target === "number") {
712
+ return target;
713
+ }
714
+ const ctx = yield prepareProvision(options);
715
+ if (typeof ctx === "number") {
716
+ return ctx;
717
+ }
718
+ const app = yield resolveProvisionedApp(ctx, target, options);
719
+ if (typeof app === "number") {
720
+ return app;
721
+ }
722
+ try {
723
+ yield ctx.client.setAppIcon({
724
+ teamId: ctx.teamId,
725
+ slackAppId: app.slackAppId,
726
+ icon: toSlackAppIconInput(source),
727
+ });
728
+ }
729
+ catch (error) {
730
+ return renderSlackApiError(error, ctx.err);
731
+ }
732
+ if (ctx.json) {
733
+ ctx.out(`${JSON.stringify({
734
+ ok: true,
735
+ appId: app.slackAppId,
736
+ slackTeamId: app.slackTeamId,
737
+ slug: target.slug,
738
+ env: target.env,
739
+ iconStatus: "set",
740
+ }, null, 2)}\n`);
741
+ return 0;
742
+ }
743
+ ctx.out(`Set the icon for the ${target.env} Slack app for ${target.slug} (app ${app.slackAppId}).\n`);
744
+ return 0;
745
+ });
746
+ }
747
+ function formatAppList(apps) {
748
+ return apps
749
+ .map((app) => ` - ${app.slackAppId} (${app.env}) in workspace ${app.slackTeamId} — ${app.installStatus}\n`)
750
+ .join("");
751
+ }
752
+ /**
753
+ * Scaffold the channel files when the channel file is absent. Existing
754
+ * files are never overwritten: initSlackChannel writes with `wx`, and
755
+ * EEXIST means "already scaffolded" (provisioning builds its manifest in
756
+ * memory and does not need the on-disk copies).
757
+ */
758
+ function scaffoldSlackChannelFiles(options, target) {
759
+ return __awaiter(this, void 0, void 0, function* () {
760
+ var _a;
761
+ const channelFile = "agent/channels/slack.ts";
762
+ if (yield fileExists(join(target.dir, channelFile))) {
763
+ return [];
764
+ }
765
+ try {
766
+ // Pass the resolved name/prefix so the channel file and the env keys
767
+ // share one derivation.
768
+ const result = yield initSlackChannel({
769
+ dir: target.dir,
770
+ name: target.name,
771
+ envPrefix: (_a = target.envPrefix) !== null && _a !== void 0 ? _a : "",
772
+ channelPosts: options.channelPosts,
773
+ });
774
+ return result.files;
775
+ }
776
+ catch (error) {
777
+ if (isErrnoException(error) && error.code === "EEXIST") {
778
+ // The channel file is written first, so it exists by the time a
779
+ // later pre-existing file aborts the scaffold.
780
+ return (yield fileExists(join(target.dir, channelFile)))
781
+ ? [channelFile]
782
+ : [];
783
+ }
784
+ throw error;
785
+ }
786
+ });
787
+ }
788
+ /**
789
+ * Append `KEY=value` lines to an env file, replacing every existing line
790
+ * for the same key instead of appending duplicates. Creates the file when
791
+ * missing.
792
+ */
793
+ function upsertEnvFile(path, entries) {
794
+ return __awaiter(this, void 0, void 0, function* () {
795
+ var _a;
796
+ let existing = "";
797
+ try {
798
+ existing = yield readFile(path, "utf8");
799
+ }
800
+ catch (error) {
801
+ if (!isErrnoException(error) || error.code !== "ENOENT") {
802
+ throw error;
803
+ }
804
+ }
805
+ const lines = existing === "" ? [] : existing.replace(/\r?\n$/, "").split(/\r?\n/);
806
+ // A stale `export KEY=…` line would shadow or duplicate the bare line
807
+ // under some loaders; treat both spellings as the same entry.
808
+ const matchesKey = (line, key) => line.startsWith(`${key}=`) || line.startsWith(`export ${key}=`);
809
+ for (const { key, value } of entries) {
810
+ const next = `${key}=${value}`;
811
+ let replaced = false;
812
+ for (let i = 0; i < lines.length;) {
813
+ if (!matchesKey((_a = lines[i]) !== null && _a !== void 0 ? _a : "", key)) {
814
+ i += 1;
815
+ continue;
816
+ }
817
+ if (replaced) {
818
+ lines.splice(i, 1);
819
+ continue;
820
+ }
821
+ lines[i] = next;
822
+ replaced = true;
823
+ i += 1;
824
+ }
825
+ if (!replaced) {
826
+ lines.push(next);
827
+ }
828
+ }
829
+ // writeFile's mode only applies on create; clamp pre-existing files too.
830
+ yield writeFile(path, `${lines.join("\n")}\n`, { mode: 0o600 });
831
+ yield chmod(path, 0o600);
832
+ });
833
+ }
834
+ /**
835
+ * Ensure the agent directory's .gitignore covers .env.local (create or
836
+ * append). A bare `.env` line does not count — that pattern matches only
837
+ * the file literally named .env.
838
+ */
839
+ function ensureEnvLocalGitignored(dir) {
840
+ return __awaiter(this, void 0, void 0, function* () {
841
+ const path = join(dir, ".gitignore");
842
+ let existing = "";
843
+ try {
844
+ existing = yield readFile(path, "utf8");
845
+ }
846
+ catch (error) {
847
+ if (!isErrnoException(error) || error.code !== "ENOENT") {
848
+ throw error;
849
+ }
850
+ }
851
+ const covered = existing
852
+ .split(/\r?\n/)
853
+ .some((line) => /^\.env(\.local\*?|\.\*|\*)$/.test(line.trim()));
854
+ if (covered) {
855
+ return false;
856
+ }
857
+ const next = existing === "" || existing.endsWith("\n")
858
+ ? `${existing}.env.local\n`
859
+ : `${existing}\n.env.local\n`;
860
+ yield writeFile(path, next);
861
+ return true;
862
+ });
863
+ }
864
+ function renderDoctorReport(result) {
865
+ let text = `env: ${result.envKeys.bot} / ${result.envKeys.app}\n`;
866
+ for (const check of result.checks) {
867
+ text += `${check.ok ? "ok" : "!!"} ${check.id}: ${check.message}\n`;
868
+ }
869
+ text += result.ok ? "doctor: pass\n" : "doctor: fail\n";
870
+ return text;
871
+ }
872
+ /**
873
+ * Actionable rendering for `POST /apps` failures the create flow does not
874
+ * retry itself (the connect-and-retry codes are handled by the caller).
875
+ */
876
+ function renderCreateProvisionError(error, err) {
877
+ var _a;
878
+ if (!(error instanceof SlackProvisionApiError)) {
879
+ throw error;
880
+ }
881
+ switch (error.code) {
882
+ case "app_conflict":
883
+ err(`${error.message}\nA concurrent \`${CLI} slack create\` for this agent won the race; re-run create to pick up that app.\n`);
884
+ return 1;
885
+ case "app_cap_reached":
886
+ err(`${error.message}\nDelete unused apps with \`${CLI} slack destroy\`, then re-run create.\n`);
887
+ return 1;
888
+ case "ratelimited":
889
+ err(`${error.message}\nRe-run \`${CLI} slack create\` after the wait.\n`);
890
+ return 1;
891
+ case "invalid_app_id":
892
+ err(`${error.message}\nThe provisioned Slack app seems to have been deleted outside Cursor. Run \`${CLI} slack destroy\` to drop the stale record, then re-run \`${CLI} slack create\`.\n`);
893
+ return 1;
894
+ case "manifest_drift":
895
+ err(`${error.message}\n` +
896
+ ((_a = error.changedPaths) !== null && _a !== void 0 ? _a : []).map((path) => ` - ${path}\n`).join("") +
897
+ `Re-run \`${CLI} slack create --force\` to overwrite those edits.\n`);
898
+ return 1;
899
+ default:
900
+ return renderSlackApiError(error, err);
901
+ }
902
+ }
903
+ function renderSlackApiError(error, err) {
904
+ if (!(error instanceof SlackProvisionApiError)) {
905
+ throw error;
906
+ }
907
+ err(error.code === "manager_reauth_required"
908
+ ? // There is no standalone connect command: create runs the consent
909
+ // flow before provisioning.
910
+ `${error.message}\nRe-approve the Slack consent by re-running \`${CLI} slack create\`, which reconnects the workspace first.\n`
911
+ : `${error.message}\n`);
912
+ return 1;
913
+ }
914
+ function fileExists(path) {
915
+ return __awaiter(this, void 0, void 0, function* () {
916
+ try {
917
+ yield access(path);
918
+ return true;
919
+ }
920
+ catch (_a) {
921
+ return false;
922
+ }
923
+ });
924
+ }
925
+ function isErrnoException(error) {
926
+ return error instanceof Error && "code" in error;
927
+ }
928
+ function sleep(ms) {
929
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
930
+ }