@cursor/july 0.1.49 → 0.1.51

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 (119) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +7 -14
  3. package/dist/bin/agent-serve.js +22 -8
  4. package/dist/channels/slack/init.d.ts +2 -3
  5. package/dist/channels/slack/init.d.ts.map +1 -1
  6. package/dist/channels/slack/init.js +8 -43
  7. package/dist/channels/slack/manifest.d.ts +3 -3
  8. package/dist/channels/slack/manifest.js +3 -3
  9. package/dist/channels/slack/setup.d.ts +2 -11
  10. package/dist/channels/slack/setup.d.ts.map +1 -1
  11. package/dist/channels/slack/setup.js +26 -94
  12. package/dist/channels/slack/types.d.ts +3 -4
  13. package/dist/channels/slack/types.d.ts.map +1 -1
  14. package/dist/docs/404.html +2 -2
  15. package/dist/docs/ab.html +3 -3
  16. package/dist/docs/assets/{app.BpWAgjaR.js → app.C51TX1Bw.js} +1 -1
  17. package/dist/docs/assets/chunks/@localSearchIndexroot.D2DpXdZN.js +1 -0
  18. package/dist/docs/assets/chunks/{VPLocalSearchBox.CnAktZtG.js → VPLocalSearchBox.BB9HCJPq.js} +1 -1
  19. package/dist/docs/assets/chunks/{theme.B5IvUxOB.js → theme.Dp-0SZcu.js} +2 -2
  20. package/dist/docs/assets/{deployment.md.Cdp4rTE4.js → deployment.md.B8kW-h7P.js} +1 -1
  21. package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.js → example-agents_benny.md.B-LIDGja.js} +1 -1
  22. package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.js → example-agents_oncall.md.CBmyuAKc.js} +2 -2
  23. package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.js → guides_human-in-the-loop.md.Cf8kIIqC.js} +2 -2
  24. package/dist/docs/assets/{guides_human-in-the-loop.md.BWvT7UqY.lean.js → guides_human-in-the-loop.md.Cf8kIIqC.lean.js} +1 -1
  25. package/dist/docs/assets/{guides_slack.md.zriQpU_9.js → guides_slack.md.D2nTRu9M.js} +7 -12
  26. package/dist/docs/assets/guides_slack.md.D2nTRu9M.lean.js +1 -0
  27. package/dist/docs/assets/{reference_cli.md.BDmjPPAm.js → reference_cli.md.CGKfz_Yj.js} +3 -7
  28. package/dist/docs/assets/{reference_cli.md.BDmjPPAm.lean.js → reference_cli.md.CGKfz_Yj.lean.js} +1 -1
  29. package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.js → troubleshooting.md.C6n3wCr6.js} +1 -1
  30. package/dist/docs/building-with-agents.html +3 -3
  31. package/dist/docs/concepts.html +3 -3
  32. package/dist/docs/deployment.html +5 -5
  33. package/dist/docs/evals.html +3 -3
  34. package/dist/docs/example-agents/approval-buddy.html +3 -3
  35. package/dist/docs/example-agents/benny.html +5 -5
  36. package/dist/docs/example-agents/bugbot.html +3 -3
  37. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  38. package/dist/docs/example-agents/codeowners-review.html +3 -3
  39. package/dist/docs/example-agents/concierge.html +3 -3
  40. package/dist/docs/example-agents/fsd.html +3 -3
  41. package/dist/docs/example-agents/index.html +3 -3
  42. package/dist/docs/example-agents/knowledge-base.html +3 -3
  43. package/dist/docs/example-agents/oncall.html +6 -6
  44. package/dist/docs/example-agents/security-reviewer.html +3 -3
  45. package/dist/docs/example-agents/slack-agent.html +3 -3
  46. package/dist/docs/example-agents/weather-agent.html +3 -3
  47. package/dist/docs/guides/agent-to-agent.html +3 -3
  48. package/dist/docs/guides/cloud-runtime.html +3 -3
  49. package/dist/docs/guides/github.html +3 -3
  50. package/dist/docs/guides/human-in-the-loop.html +5 -5
  51. package/dist/docs/guides/mcp-oauth.html +3 -3
  52. package/dist/docs/guides/opentelemetry.html +3 -3
  53. package/dist/docs/guides/slack.html +11 -16
  54. package/dist/docs/guides/webhooks.html +3 -3
  55. package/dist/docs/hashmap.json +1 -1
  56. package/dist/docs/hillclimbing.html +3 -3
  57. package/dist/docs/index.html +3 -3
  58. package/dist/docs/quickstart.html +3 -3
  59. package/dist/docs/reference/agent-config.html +3 -3
  60. package/dist/docs/reference/artifacts.html +3 -3
  61. package/dist/docs/reference/channels.html +3 -3
  62. package/dist/docs/reference/cli.html +6 -10
  63. package/dist/docs/reference/connections.html +3 -3
  64. package/dist/docs/reference/hooks.html +3 -3
  65. package/dist/docs/reference/http-api.html +3 -3
  66. package/dist/docs/reference/instructions.html +3 -3
  67. package/dist/docs/reference/playground.html +3 -3
  68. package/dist/docs/reference/project-layout.html +3 -3
  69. package/dist/docs/reference/prompt.html +3 -3
  70. package/dist/docs/reference/schedules.html +3 -3
  71. package/dist/docs/reference/sessions.html +3 -3
  72. package/dist/docs/reference/skills.html +3 -3
  73. package/dist/docs/reference/subagents.html +3 -3
  74. package/dist/docs/reference/tools.html +3 -3
  75. package/dist/docs/scaffolding-agents.html +3 -3
  76. package/dist/docs/storage.html +3 -3
  77. package/dist/docs/templates/triage.html +3 -3
  78. package/dist/docs/troubleshooting.html +5 -5
  79. package/dist/internal/cli-ax.js +3 -3
  80. package/dist/internal/cli-cursor.d.ts +18 -0
  81. package/dist/internal/cli-cursor.d.ts.map +1 -1
  82. package/dist/internal/cli-cursor.js +25 -0
  83. package/dist/internal/cli-slack.d.ts +3 -4
  84. package/dist/internal/cli-slack.d.ts.map +1 -1
  85. package/dist/internal/cli-slack.js +126 -322
  86. package/dist/internal/convert-automation/convert-workflow.js +2 -2
  87. package/dist/internal/slack-provision-client.d.ts +37 -2
  88. package/dist/internal/slack-provision-client.d.ts.map +1 -1
  89. package/dist/internal/slack-provision-client.js +46 -0
  90. package/dist/playground/assets/{index-Dq_HqyU-.js → index-BKMz70wx.js} +39 -39
  91. package/dist/playground/assets/index-BmMrulSc.css +1 -0
  92. package/dist/playground/index.html +2 -2
  93. package/docs/deployment.md +1 -1
  94. package/docs/example-agents/benny.md +2 -3
  95. package/docs/example-agents/oncall.md +4 -5
  96. package/docs/guides/human-in-the-loop.md +1 -1
  97. package/docs/guides/slack.md +51 -112
  98. package/docs/reference/cli.md +14 -37
  99. package/docs/troubleshooting.md +2 -2
  100. package/package.json +1 -1
  101. package/skills/create-agent/SKILL.md +4 -5
  102. package/skills/setup-slack/SKILL.md +73 -131
  103. package/src/bin/agent-serve.ts +26 -9
  104. package/src/channels/slack/init.ts +10 -71
  105. package/src/channels/slack/manifest.ts +3 -3
  106. package/src/channels/slack/setup.ts +25 -103
  107. package/src/channels/slack/types.ts +3 -4
  108. package/src/internal/cli-ax.ts +3 -3
  109. package/src/internal/cli-cursor.ts +34 -0
  110. package/src/internal/cli-slack.ts +136 -389
  111. package/src/internal/convert-automation/convert-workflow.ts +2 -2
  112. package/src/internal/slack-provision-client.ts +83 -2
  113. package/dist/docs/assets/chunks/@localSearchIndexroot.D00d772K.js +0 -1
  114. package/dist/docs/assets/guides_slack.md.zriQpU_9.lean.js +0 -1
  115. package/dist/playground/assets/index-Dox74yWJ.css +0 -1
  116. /package/dist/docs/assets/{deployment.md.Cdp4rTE4.lean.js → deployment.md.B8kW-h7P.lean.js} +0 -0
  117. /package/dist/docs/assets/{example-agents_benny.md.l7JTmm8X.lean.js → example-agents_benny.md.B-LIDGja.lean.js} +0 -0
  118. /package/dist/docs/assets/{example-agents_oncall.md.ZE0n6ZFN.lean.js → example-agents_oncall.md.CBmyuAKc.lean.js} +0 -0
  119. /package/dist/docs/assets/{troubleshooting.md.CkgwmVI0.lean.js → troubleshooting.md.C6n3wCr6.lean.js} +0 -0
@@ -1,13 +1,11 @@
1
1
  /**
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`).
2
+ * `agent-serve slack …` CLI — doctor, destroy/icon, and `create` as a
3
+ * dashboard-wizard launcher (`slack-provision-client.ts`).
5
4
  */
6
5
 
7
6
  import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
8
7
  import { basename, extname, join, resolve as resolvePath } from "node:path";
9
8
  import {
10
- buildSlackManifest,
11
9
  buildSlackManifestPair,
12
10
  envPrefixFromName,
13
11
  formatSlackSetupGuide,
@@ -19,6 +17,7 @@ import {
19
17
  import { resolveDeployAuth } from "./cli-deploy.js";
20
18
  import { cursorExternalApiUrl } from "./cursor/credentials.js";
21
19
  import { slugFromDirectoryName } from "./deploy-client.js";
20
+ import { inferDeployGitSource } from "./deploy-source.js";
22
21
  import { describeError } from "./describe-error.js";
23
22
  import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
24
23
  import { loadAgentLocalEnv } from "./local-env.js";
@@ -28,11 +27,11 @@ import {
28
27
  type SlackAppEnv,
29
28
  type SlackAppIconContentType,
30
29
  type SlackAppIconInput,
31
- type SlackAppProvisionResult,
32
- type SlackConnectedWorkspace,
33
30
  type SlackManagedAppInfo,
34
31
  SlackProvisionApiError,
35
32
  type SlackProvisionStatus,
33
+ type SlackWizardMintResult,
34
+ type SlackWizardPollResult,
36
35
  } from "./slack-provision-client.js";
37
36
 
38
37
  export interface SlackCliOptions {
@@ -57,7 +56,7 @@ export interface SlackCliOptions {
57
56
  apiKey?: string;
58
57
  /** create: app icon — an https image URL or a local png/jpg/gif ≤512KB. */
59
58
  icon?: string;
60
- /** create: overwrite manifest edits made in Slack (skip the drift check). */
59
+ /** create: accepted for compatibility; live-app updates always overwrite. */
61
60
  force?: boolean;
62
61
  /** `slack icon <source>` positional (same URL-or-file forms as --icon). */
63
62
  iconSource?: string;
@@ -82,33 +81,26 @@ Usage:
82
81
  [--team <id>] [--json]
83
82
  ${CLI} slack icon <URL-or-FILE> [--dir .] [--slack-team T0123ABCD]
84
83
  [--prod] [--team <id>] [--json]
85
- ${CLI} slack init [--dir .] [--name NAME] [--prefix PREFIX | --no-prefix]
86
- [--channel-posts]
87
- ${CLI} slack manifest --env dev|prod|both [--dir .] [--name NAME]
88
- [--print]
89
84
  ${CLI} slack doctor [--dir .] [--prefix PREFIX | --no-prefix] [--json]
90
85
 
91
- create/destroy/icon — Cursor "Add to Slack" managed provisioning:
92
- create One-command Path B setup: connects the Slack workspace (browser
93
- consent) when needed, creates + installs this agent's dedicated
94
- Socket Mode app from the generated manifest, writes the token
95
- pair into <dir>/.env.local, then runs doctor (prose mode; --json
96
- reports without the doctor pass). Requires
97
- \`${CLI} login\` (or CURSOR_API_KEY) and the feature gate.
98
- --prod provisions the prod app (default dev); --slack-team picks
99
- the workspace when several are connected. --icon sets the app's
100
- icon (best-effort: a bad icon warns, never fails the create).
101
- A re-run refuses to overwrite manifest edits made by hand at
102
- api.slack.com (manifest_drift); --force overwrites them.
86
+ create/destroy/icon — dedicated Socket Mode app via the Cursor dashboard:
87
+ create Opens the signed-in dashboard wizard. Finish name, workspace,
88
+ and Slack consent there. The CLI writes the token pair into
89
+ <dir>/.env.local and runs doctor. Requires \`${CLI} login\`
90
+ (or CURSOR_API_KEY). --prod provisions the prod app (default
91
+ dev). --name / --icon / --channel-posts prefill the wizard.
103
92
  destroy Delete the provisioned Slack app for this agent (+env). Tokens
104
93
  already in .env.local are left in place (they just stop working).
105
94
  icon Set the provisioned app's icon: an https image URL or a local
106
95
  png/jpg/gif file (at most 512KB).
107
96
 
97
+ \`slack init\` is removed. Use \`slack create\`. Do not paste a manifest
98
+ at api.slack.com.
99
+
108
100
  Env tokens:
109
101
  single agent: SLACK_BOT_TOKEN + SLACK_APP_TOKEN
110
102
  multi-agent: <PREFIX>_SLACK_BOT_TOKEN + <PREFIX>_SLACK_APP_TOKEN
111
- (init/create default PREFIX from the directory name;
103
+ (create default PREFIX from the directory name;
112
104
  doctor defaults to it too when those keys are set)
113
105
 
114
106
  See also: skills/setup-slack/SKILL.md
@@ -148,48 +140,15 @@ export async function cmdSlack(
148
140
  }
149
141
 
150
142
  async function cmdSlackInit(options: SlackCliOptions): Promise<number> {
151
- const dir = resolvePath(options.dir);
152
- try {
153
- const result = await initSlackChannel({
154
- dir,
155
- ...(options.name === undefined ? {} : { name: options.name }),
156
- ...(options.noPrefix === true
157
- ? { envPrefix: "" }
158
- : options.prefix === undefined
159
- ? {}
160
- : { envPrefix: options.prefix }),
161
- ...(options.channelPosts === true ? { channelPosts: true } : {}),
162
- });
163
- process.stdout.write(
164
- `${JSON.stringify(
165
- {
166
- ok: true,
167
- directory: result.rootDir,
168
- created: result.files,
169
- envPrefix: result.envPrefix ?? null,
170
- envKeys: result.envKeys,
171
- next: [
172
- "Open https://api.slack.com/apps → Create from manifest",
173
- "Import .agent-serve/slack/manifest.dev.json (then prod)",
174
- `Install apps; set ${result.envKeys.bot} + ${result.envKeys.app}`,
175
- result.envPrefix === undefined
176
- ? "agent-serve slack doctor"
177
- : `agent-serve slack doctor --prefix ${result.envPrefix}`,
178
- "agent-serve serve --dir . --dev",
179
- "agent-serve slack setup",
180
- ],
181
- },
182
- null,
183
- 2
184
- )}\n`
185
- );
186
- return 0;
187
- } catch (error) {
188
- process.stderr.write(
189
- `${error instanceof Error ? error.message : String(error)}\n`
190
- );
191
- return 1;
192
- }
143
+ errStream(options)(
144
+ `\`slack init\` is removed. Socket Mode apps are created with:
145
+
146
+ ${CLI} slack create --dir .
147
+
148
+ That opens the Cursor dashboard wizard. Do not paste a manifest at api.slack.com.
149
+ `
150
+ );
151
+ return 1;
193
152
  }
194
153
 
195
154
  async function runManifest(options: SlackCliOptions): Promise<number> {
@@ -198,10 +157,8 @@ async function runManifest(options: SlackCliOptions): Promise<number> {
198
157
  const name = options.name ?? basename(dir);
199
158
  const pair = buildSlackManifestPair({
200
159
  name,
201
- // These files are pasted into api.slack.com by hand, so they must not
202
- // carry the manager-token-only managed_app_settings block.
203
160
  managedByCursor: false,
204
- ...(options.channelPosts === true ? { channelPosts: true } : {}),
161
+ channelPosts: options.channelPosts === true ? true : undefined,
205
162
  });
206
163
 
207
164
  if (options.print) {
@@ -276,8 +233,8 @@ function resolveDoctorEnvPrefix(
276
233
  // create / destroy — Cursor "Add to Slack" managed provisioning
277
234
  // ============================================================================
278
235
 
279
- const DEFAULT_CONNECT_POLL_INTERVAL_MS = 3_000;
280
- const DEFAULT_CONNECT_POLL_TIMEOUT_MS = 5 * 60 * 1000;
236
+ const DEFAULT_WIZARD_POLL_INTERVAL_MS = 3_000;
237
+ const DEFAULT_WIZARD_POLL_TIMEOUT_MS = 60 * 60 * 1000;
281
238
 
282
239
  interface SlackProvisionContext {
283
240
  client: AgentServeSlackClient;
@@ -402,41 +359,30 @@ function toSlackAppIconInput(source: SlackIconSource): SlackAppIconInput {
402
359
  }
403
360
 
404
361
  /**
405
- * URL icons ride the provision request; file icons post to the icon
406
- * endpoint afterwards. Best-effort: failures warn, never fail the create.
362
+ * File icons post after the wizard finishes. URL icons ride the mint
363
+ * payload. Best-effort: failures warn, never fail the create.
407
364
  */
408
- async function applySlackCreateIcon(
365
+ async function applySlackCreateFileIcon(
409
366
  ctx: SlackProvisionContext,
410
367
  source: SlackIconSource | undefined,
411
- result: SlackAppProvisionResult
368
+ appId: string
412
369
  ): Promise<"set" | "failed" | undefined> {
413
- if (source === undefined) {
370
+ if (source === undefined || source.kind === "url") {
414
371
  return undefined;
415
372
  }
416
- let failure: string | undefined;
417
- if (source.kind === "url") {
418
- if (result.iconStatus !== "set") {
419
- failure = "Slack did not accept the icon URL";
420
- }
421
- } else {
422
- try {
423
- await ctx.client.setAppIcon({
424
- teamId: ctx.teamId,
425
- slackAppId: result.appId,
426
- icon: toSlackAppIconInput(source),
427
- });
428
- } catch (error) {
429
- failure = describeError(error);
430
- }
431
- }
432
- if (failure === undefined) {
373
+ try {
374
+ await ctx.client.setAppIcon({
375
+ teamId: ctx.teamId,
376
+ slackAppId: appId,
377
+ icon: toSlackAppIconInput(source),
378
+ });
433
379
  return "set";
380
+ } catch (error) {
381
+ ctx.err(
382
+ `warning: setting the Slack app icon failed: ${describeError(error)}\nRetry with \`${CLI} slack icon ${source.path}\`.\n`
383
+ );
384
+ return "failed";
434
385
  }
435
- const retryWith = source.kind === "url" ? source.url : source.path;
436
- ctx.err(
437
- `warning: setting the Slack app icon failed: ${failure}\nRetry with \`${CLI} slack icon ${retryWith}\`.\n`
438
- );
439
- return "failed";
440
386
  }
441
387
 
442
388
  /**
@@ -473,123 +419,12 @@ async function prepareProvision(
473
419
  err,
474
420
  openBrowserImpl: options.openBrowserImpl ?? openBrowser,
475
421
  pollIntervalMs:
476
- options.connectPollIntervalMs ?? DEFAULT_CONNECT_POLL_INTERVAL_MS,
422
+ options.connectPollIntervalMs ?? DEFAULT_WIZARD_POLL_INTERVAL_MS,
477
423
  pollTimeoutMs:
478
- options.connectPollTimeoutMs ?? DEFAULT_CONNECT_POLL_TIMEOUT_MS,
424
+ options.connectPollTimeoutMs ?? DEFAULT_WIZARD_POLL_TIMEOUT_MS,
479
425
  };
480
426
  }
481
427
 
482
- /** What the connect poll waits for in `GET /status`. */
483
- interface ConnectWaitTarget {
484
- /** Wait for this workspace to be connected (else: any workspace). */
485
- slackTeamId?: string;
486
- /**
487
- * Wait for connectedAt to move past this value — a fresh grant. Used
488
- * whenever the workspace row already exists and only the new approval
489
- * bumps its timestamp.
490
- */
491
- notConnectedAt?: string;
492
- }
493
-
494
- /**
495
- * Slack's team-scoped admin page listing pending app-install requests.
496
- * Provisioned apps have no OAuth flow (no redirect URLs, install-from-Slack
497
- * disabled), so the oauth_authorize_url Slack returns from
498
- * apps.manifest.create dead-ends; approval happens here instead.
499
- */
500
- function slackAppRequestsUrl(slackTeamId: string): string {
501
- return `https://app.slack.com/apps-manage/${encodeURIComponent(slackTeamId)}/integrations/requests`;
502
- }
503
-
504
- /** "Name (T0123ABCD)", or the bare id when the backend has no name. */
505
- function formatWorkspace(workspace: SlackConnectedWorkspace): string {
506
- const name = workspace.slackTeamName ?? "";
507
- return name === ""
508
- ? workspace.slackTeamId
509
- : `${name} (${workspace.slackTeamId})`;
510
- }
511
-
512
- function matchConnectedWorkspace(
513
- workspaces: SlackConnectedWorkspace[],
514
- wait: ConnectWaitTarget
515
- ): SlackConnectedWorkspace | undefined {
516
- const candidates =
517
- wait.slackTeamId === undefined
518
- ? workspaces
519
- : workspaces.filter(
520
- (workspace) => workspace.slackTeamId === wait.slackTeamId
521
- );
522
- if (wait.notConnectedAt === undefined) {
523
- return candidates[0];
524
- }
525
- return candidates.find(
526
- (workspace) => workspace.connectedAt !== wait.notConnectedAt
527
- );
528
- }
529
-
530
- /**
531
- * Mint the browser consent URL, open it, and poll `GET /status` until the
532
- * awaited workspace shows up (or the poll times out). Returns the
533
- * connected workspace, or an exit code with the error already rendered.
534
- */
535
- async function runConnectFlow(
536
- ctx: SlackProvisionContext,
537
- wait: ConnectWaitTarget
538
- ): Promise<SlackConnectedWorkspace | number> {
539
- let url: string;
540
- try {
541
- url = await ctx.client.connect(ctx.teamId);
542
- } catch (error) {
543
- return renderSlackApiError(error, ctx.err);
544
- }
545
- ctx.err(
546
- `\nConnect your Slack workspace to Cursor — approve in the browser:\n\n ${url}\n\n` +
547
- `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` +
548
- `(waiting for the Slack consent to complete, up to ${Math.round(ctx.pollTimeoutMs / 60_000)} minutes)\n`
549
- );
550
- ctx.openBrowserImpl(url);
551
- const deadline = Date.now() + ctx.pollTimeoutMs;
552
- let lastPollFailure: string | undefined;
553
- for (;;) {
554
- let workspace: SlackConnectedWorkspace | undefined;
555
- let sleepMs = ctx.pollIntervalMs;
556
- try {
557
- const status = await ctx.client.status(ctx.teamId);
558
- workspace = matchConnectedWorkspace(status.workspaces, wait);
559
- lastPollFailure = undefined;
560
- } catch (error) {
561
- // Transient poll failures must not abort a minted consent flow; only
562
- // a still-failing poll at the deadline surfaces the error.
563
- if (Date.now() >= deadline) {
564
- return renderSlackApiError(error, ctx.err);
565
- }
566
- const failure = describeError(error);
567
- if (failure !== lastPollFailure) {
568
- lastPollFailure = failure;
569
- ctx.err(`(status poll failed, retrying: ${failure})\n`);
570
- }
571
- if (
572
- error instanceof SlackProvisionApiError &&
573
- error.retryAfterSeconds !== undefined
574
- ) {
575
- // Respect backend backpressure instead of hammering at the poll rate.
576
- sleepMs = Math.max(sleepMs, error.retryAfterSeconds * 1000);
577
- }
578
- }
579
- if (workspace !== undefined) {
580
- ctx.err(`Connected Slack workspace ${workspace.slackTeamId}.\n`);
581
- return workspace;
582
- }
583
- if (Date.now() >= deadline) {
584
- ctx.err(
585
- `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`
586
- );
587
- return 1;
588
- }
589
- await sleep(sleepMs);
590
- }
591
- }
592
-
593
428
  export async function cmdSlackCreate(
594
429
  options: SlackCliOptions
595
430
  ): Promise<number> {
@@ -597,7 +432,6 @@ export async function cmdSlackCreate(
597
432
  if (typeof target === "number") {
598
433
  return target;
599
434
  }
600
- // Resolved before any network I/O so a bad path/type/size fails fast.
601
435
  let iconSource: SlackIconSource | undefined;
602
436
  if (options.icon !== undefined) {
603
437
  const resolved = await resolveSlackIconSource(
@@ -614,43 +448,6 @@ export async function cmdSlackCreate(
614
448
  return ctx;
615
449
  }
616
450
 
617
- // Workspace: --slack-team wins; one → use it; none → connect; several → ask.
618
- let status: SlackProvisionStatus;
619
- try {
620
- status = await ctx.client.status(ctx.teamId);
621
- } catch (error) {
622
- return renderSlackApiError(error, ctx.err);
623
- }
624
- let workspace: SlackConnectedWorkspace | undefined;
625
- if (options.slackTeam !== undefined) {
626
- workspace = status.workspaces.find(
627
- (candidate) => candidate.slackTeamId === options.slackTeam
628
- );
629
- } else if (status.workspaces.length === 1) {
630
- workspace = status.workspaces[0];
631
- } else if (status.workspaces.length > 1) {
632
- ctx.err(
633
- `Multiple Slack workspaces are connected:\n` +
634
- status.workspaces
635
- .map(
636
- (candidate) =>
637
- ` - ${formatWorkspace(candidate)} (Slack user ${candidate.slackUserId}, connected ${candidate.connectedAt})\n`
638
- )
639
- .join("") +
640
- `Pass --slack-team <id> to pick one.\n`
641
- );
642
- return 1;
643
- }
644
- if (workspace === undefined) {
645
- const connected = await runConnectFlow(ctx, {
646
- slackTeamId: options.slackTeam,
647
- });
648
- if (typeof connected === "number") {
649
- return connected;
650
- }
651
- workspace = connected;
652
- }
653
-
654
451
  let wrote: string[];
655
452
  try {
656
453
  wrote = await scaffoldSlackChannelFiles(options, target);
@@ -661,106 +458,93 @@ export async function cmdSlackCreate(
661
458
  if (wrote.length > 0 && !ctx.json) {
662
459
  ctx.err(`Scaffolded the Slack channel: ${wrote.join(", ")}\n`);
663
460
  }
664
- // A re-run rebuilds this manifest from flags, so backend-side
665
- // customizations reset to it (documented in the skill).
666
- const manifest = buildSlackManifest({
667
- name: target.name,
668
- env: target.env,
669
- channelPosts: options.channelPosts === true,
670
- });
671
- const manifestJson = JSON.stringify(manifest);
672
461
 
673
- // One connect-and-retry for not_connected / insufficient_scopes /
674
- // manager_reauth_required (all fixed by a fresh consent grant).
675
- let result: SlackAppProvisionResult;
676
- let connectRetried = false;
462
+ const source = await inferDeployGitSource(target.dir);
463
+ let minted: SlackWizardMintResult;
464
+ try {
465
+ minted = await ctx.client.mintWizard({
466
+ teamId: ctx.teamId,
467
+ slug: target.slug,
468
+ env: target.env,
469
+ envPrefix: target.envPrefix,
470
+ name: options.name ?? target.name,
471
+ gitRepoUrl: source.gitRepoUrl,
472
+ gitRef: source.gitRef,
473
+ agentPath: source.agentPath,
474
+ channelPosts: options.channelPosts === true ? true : undefined,
475
+ slackTeamId: options.slackTeam,
476
+ iconUrl: iconSource?.kind === "url" ? iconSource.url : undefined,
477
+ force: options.force === true ? true : undefined,
478
+ });
479
+ } catch (error) {
480
+ return renderSlackApiError(error, ctx.err);
481
+ }
482
+
483
+ ctx.err(
484
+ `Finish Slack setup in the browser:\n\n ${minted.url}\n\n` +
485
+ `Sign in as the same Cursor account that ran this command. ` +
486
+ `The CLI waits here (up to ${Math.round(ctx.pollTimeoutMs / 60_000)} minutes).\n`
487
+ );
488
+ ctx.openBrowserImpl(minted.url);
489
+
490
+ const deadline = Date.now() + ctx.pollTimeoutMs;
491
+ let sawPendingAdmin = false;
492
+ let poll: SlackWizardPollResult | undefined;
677
493
  for (;;) {
678
494
  try {
679
- result = await ctx.client.provisionApp({
680
- teamId: ctx.teamId,
681
- slackTeamId: workspace.slackTeamId,
682
- slug: target.slug,
683
- env: target.env,
684
- manifest: manifestJson,
685
- icon: iconSource?.kind === "url" ? { url: iconSource.url } : undefined,
686
- force: options.force === true ? true : undefined,
687
- });
688
- break;
495
+ poll = await ctx.client.pollWizard(minted.wizardId);
689
496
  } catch (error) {
497
+ if (Date.now() >= deadline) {
498
+ return renderSlackApiError(error, ctx.err);
499
+ }
500
+ await sleep(ctx.pollIntervalMs);
501
+ continue;
502
+ }
503
+ if (poll.status === "complete") {
504
+ break;
505
+ }
506
+ if (poll.status === "failed") {
507
+ ctx.err(`${poll.error ?? "Slack setup failed"}\n`);
508
+ return 1;
509
+ }
510
+ if (poll.status === "pending_admin_approval" && !sawPendingAdmin) {
511
+ sawPendingAdmin = true;
512
+ const appSuffix = poll.appId === undefined ? "" : ` (${poll.appId})`;
513
+ ctx.err(
514
+ `Waiting for a Slack admin to approve the app${appSuffix}. Keep this command running.\n` +
515
+ `Managed install does not file the request. Open Slack to submit it; that same page lets you send a reminder to workspace owners.\n`
516
+ );
690
517
  if (
691
- error instanceof SlackProvisionApiError &&
692
- (error.code === "not_connected" ||
693
- error.code === "insufficient_scopes" ||
694
- error.code === "manager_reauth_required") &&
695
- !connectRetried
518
+ poll.oauthAuthorizeUrl !== undefined &&
519
+ poll.oauthAuthorizeUrl.length > 0
696
520
  ) {
697
- connectRetried = true;
698
- // All three codes arrive holding a row that GET /status already
699
- // lists, so the poll waits for a fresh grant instead of re-matching
700
- // that row.
701
- const wait: ConnectWaitTarget = {
702
- slackTeamId: workspace.slackTeamId,
703
- notConnectedAt: workspace.connectedAt,
704
- };
705
- if (error.code === "insufficient_scopes") {
706
- ctx.err(
707
- `The Slack connection is missing scopes provisioning needs; approving the consent link again grants them.\n`
708
- );
709
- } else if (error.code === "manager_reauth_required") {
710
- ctx.err(
711
- `Slack revoked Cursor's authorization for workspace ${workspace.slackTeamId}; re-approving the consent link restores it.\n`
712
- );
713
- } else {
714
- ctx.err(
715
- `The Slack workspace is not connected to Cursor; starting the connect flow.\n`
716
- );
717
- }
718
- const connected = await runConnectFlow(ctx, wait);
719
- if (typeof connected === "number") {
720
- return connected;
721
- }
722
- workspace = connected;
723
- continue;
521
+ ctx.err(` ${poll.oauthAuthorizeUrl}\n`);
724
522
  }
725
- return renderCreateProvisionError(error, ctx.err);
523
+ ctx.err("After an admin approves, click Retry in the dashboard.\n");
726
524
  }
727
- }
728
-
729
- const iconStatus = await applySlackCreateIcon(ctx, iconSource, result);
730
-
731
- if (result.status === "pending_admin_approval") {
732
- const approveUrl = slackAppRequestsUrl(workspace.slackTeamId);
733
- if (ctx.json) {
734
- ctx.out(
735
- `${JSON.stringify(
736
- {
737
- ok: false,
738
- appId: result.appId,
739
- status: result.status,
740
- slackTeamId: workspace.slackTeamId,
741
- envKeys: target.envKeys,
742
- wrote,
743
- approveUrl,
744
- iconStatus,
745
- },
746
- null,
747
- 2
748
- )}\n`
749
- );
750
- } else {
525
+ if (Date.now() >= deadline) {
751
526
  ctx.err(
752
- `The Slack app was created (app ${result.appId}), but installing it needs a Slack workspace-admin approval.\n` +
753
- `\n A workspace admin can approve it here:\n\n ${approveUrl}\n\n` +
754
- `Once an admin approves the app, re-run \`${CLI} slack create\` to finish the install and mint the tokens.\n`
527
+ `Timed out waiting for Slack setup. Finish it in the browser, then re-run \`${CLI} slack create\`.\n ${minted.url}\n`
755
528
  );
529
+ return 1;
756
530
  }
531
+ await sleep(ctx.pollIntervalMs);
532
+ }
533
+
534
+ if (poll === undefined || poll.localEnv === undefined) {
535
+ ctx.err(
536
+ poll.localEnvClaimed === true
537
+ ? "Tokens were already claimed by another poll; re-run create to rotate them.\n"
538
+ : "Slack setup finished but no tokens were returned; re-run create.\n"
539
+ );
757
540
  return 1;
758
541
  }
759
542
 
760
- // Tokens come back exactly once and are never printed.
761
- const envEntries = [{ key: target.envKeys.bot, value: result.botToken }];
762
- if (result.appToken !== undefined) {
763
- envEntries.push({ key: target.envKeys.app, value: result.appToken });
543
+ const envEntries = [
544
+ { key: target.envKeys.bot, value: poll.localEnv.botToken },
545
+ ];
546
+ if (poll.localEnv.appToken !== undefined) {
547
+ envEntries.push({ key: target.envKeys.app, value: poll.localEnv.appToken });
764
548
  }
765
549
  const envFile = join(target.dir, ".env.local");
766
550
  try {
@@ -776,20 +560,24 @@ export async function cmdSlackCreate(
776
560
  for (const { key, value } of envEntries) {
777
561
  process.env[key] = value;
778
562
  }
779
- if (result.appToken === undefined) {
563
+ if (poll.localEnv.appToken === undefined) {
780
564
  ctx.err(
781
- `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`
565
+ `warning: Slack did not return an app-level token, so ${target.envKeys.app} was not written; re-run \`${CLI} slack create\` to recover it.\n`
782
566
  );
783
567
  }
784
568
 
569
+ let iconStatus: "set" | "failed" | undefined;
570
+ if (poll.appId !== undefined) {
571
+ iconStatus = await applySlackCreateFileIcon(ctx, iconSource, poll.appId);
572
+ }
573
+
785
574
  if (ctx.json) {
786
575
  ctx.out(
787
576
  `${JSON.stringify(
788
577
  {
789
578
  ok: true,
790
- appId: result.appId,
791
- status: result.status,
792
- slackTeamId: workspace.slackTeamId,
579
+ appId: poll.appId,
580
+ status: poll.status,
793
581
  envKeys: target.envKeys,
794
582
  wrote,
795
583
  iconStatus,
@@ -802,8 +590,9 @@ export async function cmdSlackCreate(
802
590
  }
803
591
 
804
592
  ctx.out(
805
- `Provisioned the ${target.env} Slack app for ${target.slug} (app ${result.appId}) in workspace ${workspace.slackTeamId}.\n` +
806
- ` wrote ${envEntries.map((entry) => entry.key).join(" + ")} to ${envFile}\n\n`
593
+ `Slack is connected for ${target.slug}` +
594
+ (poll.appId === undefined ? "" : ` (app ${poll.appId})`) +
595
+ `.\n wrote ${envEntries.map((entry) => entry.key).join(" + ")} to ${envFile}\n\n`
807
596
  );
808
597
 
809
598
  const doctor = options.doctorImpl ?? runSlackDoctor;
@@ -811,11 +600,10 @@ export async function cmdSlackCreate(
811
600
  target.envPrefix === undefined ? {} : { envPrefix: target.envPrefix }
812
601
  );
813
602
  ctx.out(renderDoctorReport(doctorResult));
814
-
815
603
  ctx.out(
816
604
  `\nNext steps:\n` +
817
- ` 1. ${CLI} dev ${options.dir}\n` +
818
- ` 2. In Slack, invite @${manifest.features.bot_user.display_name} to a channel and @mention it (or DM it).\n`
605
+ ` 1. ${CLI} serve --dir ${options.dir} --dev\n` +
606
+ ` 2. Invite the bot in Slack and @mention it.\n`
819
607
  );
820
608
  return 0;
821
609
  }
@@ -988,10 +776,8 @@ function formatAppList(
988
776
  }
989
777
 
990
778
  /**
991
- * Scaffold the channel files when the channel file is absent. Existing
992
- * files are never overwritten: initSlackChannel writes with `wx`, and
993
- * EEXIST means "already scaffolded" (provisioning builds its manifest in
994
- * memory and does not need the on-disk copies).
779
+ * Scaffold `agent/channels/slack.ts` when absent. Existing files are
780
+ * never overwritten: initSlackChannel writes with `wx`.
995
781
  */
996
782
  async function scaffoldSlackChannelFiles(
997
783
  options: SlackCliOptions,
@@ -1109,43 +895,6 @@ function renderDoctorReport(result: SlackDoctorResult): string {
1109
895
  return text;
1110
896
  }
1111
897
 
1112
- /**
1113
- * Actionable rendering for `POST /apps` failures the create flow does not
1114
- * retry itself (the connect-and-retry codes are handled by the caller).
1115
- */
1116
- function renderCreateProvisionError(
1117
- error: unknown,
1118
- err: (text: string) => void
1119
- ): number {
1120
- if (!(error instanceof SlackProvisionApiError)) {
1121
- throw error;
1122
- }
1123
- switch (error.code) {
1124
- case "app_conflict":
1125
- err(
1126
- `${error.message}\nA concurrent \`${CLI} slack create\` for this agent won the race; re-run create to pick up that app.\n`
1127
- );
1128
- return 1;
1129
- case "ratelimited":
1130
- err(`${error.message}\nRe-run \`${CLI} slack create\` after the wait.\n`);
1131
- return 1;
1132
- case "invalid_app_id":
1133
- err(
1134
- `${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`
1135
- );
1136
- return 1;
1137
- case "manifest_drift":
1138
- err(
1139
- `${error.message}\n` +
1140
- (error.changedPaths ?? []).map((path) => ` - ${path}\n`).join("") +
1141
- `Re-run \`${CLI} slack create --force\` to overwrite those edits.\n`
1142
- );
1143
- return 1;
1144
- default:
1145
- return renderSlackApiError(error, err);
1146
- }
1147
- }
1148
-
1149
898
  function renderSlackApiError(
1150
899
  error: unknown,
1151
900
  err: (text: string) => void
@@ -1155,9 +904,7 @@ function renderSlackApiError(
1155
904
  }
1156
905
  err(
1157
906
  error.code === "manager_reauth_required"
1158
- ? // There is no standalone connect command: create runs the consent
1159
- // flow before provisioning.
1160
- `${error.message}\nRe-approve the Slack consent by re-running \`${CLI} slack create\`, which reconnects the workspace first.\n`
907
+ ? `${error.message}\nRe-run \`${CLI} slack create\` and finish Slack consent in the dashboard.\n`
1161
908
  : `${error.message}\n`
1162
909
  );
1163
910
  return 1;