@cursor/july 0.1.53 → 0.1.55

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 (111) hide show
  1. package/AGENTS.md +12 -0
  2. package/dist/channels/slack/manifest.d.ts +5 -3
  3. package/dist/channels/slack/manifest.d.ts.map +1 -1
  4. package/dist/channels/slack/manifest.js +7 -3
  5. package/dist/docs/404.html +2 -2
  6. package/dist/docs/ab.html +3 -3
  7. package/dist/docs/assets/{app.DAKcDclS.js → app.CVQxhV85.js} +1 -1
  8. package/dist/docs/assets/chunks/@localSearchIndexroot.BFpWSo6D.js +1 -0
  9. package/dist/docs/assets/chunks/{VPLocalSearchBox.BNIaCSfG.js → VPLocalSearchBox.QAPzJjql.js} +1 -1
  10. package/dist/docs/assets/chunks/{theme.B47GzJjc.js → theme.CSxTCOFu.js} +2 -2
  11. package/dist/docs/assets/{reference_cli.md.-V6J7Cl8.js → reference_cli.md.D_CCfG8R.js} +2 -0
  12. package/dist/docs/assets/templates_triage.md.DWuQ1bZz.js +9 -0
  13. package/dist/docs/assets/templates_triage.md.DWuQ1bZz.lean.js +1 -0
  14. package/dist/docs/building-with-agents.html +3 -3
  15. package/dist/docs/concepts.html +3 -3
  16. package/dist/docs/deployment.html +3 -3
  17. package/dist/docs/evals.html +3 -3
  18. package/dist/docs/example-agents/approval-buddy.html +3 -3
  19. package/dist/docs/example-agents/benny.html +3 -3
  20. package/dist/docs/example-agents/bugbot.html +3 -3
  21. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  22. package/dist/docs/example-agents/codeowners-review.html +3 -3
  23. package/dist/docs/example-agents/concierge.html +3 -3
  24. package/dist/docs/example-agents/fsd.html +3 -3
  25. package/dist/docs/example-agents/index.html +3 -3
  26. package/dist/docs/example-agents/knowledge-base.html +3 -3
  27. package/dist/docs/example-agents/oncall.html +3 -3
  28. package/dist/docs/example-agents/security-reviewer.html +3 -3
  29. package/dist/docs/example-agents/slack-agent.html +3 -3
  30. package/dist/docs/example-agents/weather-agent.html +3 -3
  31. package/dist/docs/guides/agent-to-agent.html +3 -3
  32. package/dist/docs/guides/cloud-runtime.html +3 -3
  33. package/dist/docs/guides/github.html +3 -3
  34. package/dist/docs/guides/human-in-the-loop.html +3 -3
  35. package/dist/docs/guides/mcp-oauth.html +3 -3
  36. package/dist/docs/guides/opentelemetry.html +3 -3
  37. package/dist/docs/guides/slack.html +3 -3
  38. package/dist/docs/guides/webhooks.html +3 -3
  39. package/dist/docs/hashmap.json +1 -1
  40. package/dist/docs/hillclimbing.html +3 -3
  41. package/dist/docs/index.html +3 -3
  42. package/dist/docs/quickstart.html +3 -3
  43. package/dist/docs/reference/agent-config.html +3 -3
  44. package/dist/docs/reference/artifacts.html +3 -3
  45. package/dist/docs/reference/channels.html +3 -3
  46. package/dist/docs/reference/cli.html +6 -4
  47. package/dist/docs/reference/connections.html +3 -3
  48. package/dist/docs/reference/hooks.html +3 -3
  49. package/dist/docs/reference/http-api.html +3 -3
  50. package/dist/docs/reference/instructions.html +3 -3
  51. package/dist/docs/reference/playground.html +3 -3
  52. package/dist/docs/reference/project-layout.html +3 -3
  53. package/dist/docs/reference/prompt.html +3 -3
  54. package/dist/docs/reference/schedules.html +3 -3
  55. package/dist/docs/reference/sessions.html +3 -3
  56. package/dist/docs/reference/skills.html +3 -3
  57. package/dist/docs/reference/subagents.html +3 -3
  58. package/dist/docs/reference/tools.html +3 -3
  59. package/dist/docs/scaffolding-agents.html +3 -3
  60. package/dist/docs/storage.html +3 -3
  61. package/dist/docs/templates/triage.html +14 -16
  62. package/dist/docs/troubleshooting.html +3 -3
  63. package/dist/internal/cli-ax.d.ts.map +1 -1
  64. package/dist/internal/cli-ax.js +10 -1
  65. package/dist/internal/cli-deploy.js +1 -1
  66. package/dist/internal/cli-slack.d.ts +2 -0
  67. package/dist/internal/cli-slack.d.ts.map +1 -1
  68. package/dist/internal/cli-slack.js +35 -18
  69. package/dist/internal/init-project.d.ts +3 -0
  70. package/dist/internal/init-project.d.ts.map +1 -1
  71. package/dist/internal/init-project.js +77 -45
  72. package/dist/internal/slack-provision-client.js +1 -1
  73. package/dist/playground/assets/{index-BHAPd9KT.js → index-0oqXUkrR.js} +44 -44
  74. package/dist/playground/assets/index-tHCjJxv4.css +1 -0
  75. package/dist/playground/index.html +2 -2
  76. package/docs/reference/cli.md +2 -0
  77. package/docs/templates/triage.md +59 -101
  78. package/package.json +1 -1
  79. package/src/channels/slack/manifest.ts +9 -3
  80. package/src/internal/cli-ax.ts +10 -1
  81. package/src/internal/cli-deploy.ts +2 -2
  82. package/src/internal/cli-slack.ts +57 -18
  83. package/src/internal/init-project.ts +93 -48
  84. package/src/internal/slack-provision-client.ts +1 -1
  85. package/templates/triage/README.md +27 -29
  86. package/templates/triage/agent/agent.ts +1 -0
  87. package/templates/triage/agent/channels/queue.ts +47 -0
  88. package/templates/triage/agent/channels/webhook.ts +50 -0
  89. package/templates/triage/agent/instructions.md +24 -13
  90. package/templates/triage/agent/lib/actuate.ts +759 -0
  91. package/templates/triage/agent/lib/parse-queue-event.ts +168 -0
  92. package/templates/triage/agent/lib/tracker.ts +5 -0
  93. package/templates/triage/agent/lib/types.ts +52 -0
  94. package/templates/triage/agent/lib/verify-webhook.ts +81 -0
  95. package/templates/triage/agent/skills/triage-rubric.md +6 -5
  96. package/templates/triage/agent/tools/record_triage.ts +24 -5
  97. package/templates/triage/evals/triage.eval.ts +25 -3
  98. package/templates/triage/overlays/jira/README.md +21 -0
  99. package/templates/triage/overlays/jira/agent/lib/tracker.ts +8 -0
  100. package/templates/triage/overlays/jira/agent/mcp-connections/tracker.ts +7 -0
  101. package/templates/triage/overlays/linear/README.md +21 -0
  102. package/templates/triage/overlays/linear/agent/lib/tracker.ts +8 -0
  103. package/templates/triage/overlays/linear/agent/mcp-connections/tracker.ts +7 -0
  104. package/templates/triage/package.json +1 -1
  105. package/dist/docs/assets/chunks/@localSearchIndexroot.C4uxzYEU.js +0 -1
  106. package/dist/docs/assets/templates_triage.md.CConZt1z.js +0 -11
  107. package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +0 -1
  108. package/dist/playground/assets/index-DpKH0HcT.css +0 -1
  109. package/templates/triage/agent/channels/intake.ts +0 -20
  110. package/templates/triage/agent/channels/slack.ts +0 -3
  111. /package/dist/docs/assets/{reference_cli.md.-V6J7Cl8.lean.js → reference_cli.md.D_CCfG8R.lean.js} +0 -0
@@ -8,8 +8,8 @@
8
8
  />
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1" />
10
10
  <title>agent-serve playground</title>
11
- <script type="module" crossorigin src="./assets/index-BHAPd9KT.js"></script>
12
- <link rel="stylesheet" crossorigin href="./assets/index-DpKH0HcT.css">
11
+ <script type="module" crossorigin src="./assets/index-0oqXUkrR.js"></script>
12
+ <link rel="stylesheet" crossorigin href="./assets/index-tHCjJxv4.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -420,6 +420,8 @@ non-zero when the reconstructed trajectory failed.
420
420
 
421
421
  ```bash
422
422
  agent-sdk init ./my-agent # scaffold package.json, tsconfig.json, agent/ + a demo tool
423
+ agent-sdk init ./my-triage --template triage-linear # comment on Linear issues
424
+ agent-sdk init ./my-triage --template triage-jira # comment on Jira issues
423
425
  agent-sdk init ./my-agent --json # machine-readable summary for tooling
424
426
  agent-sdk init # no directory: print the setup guide
425
427
  ```
@@ -1,147 +1,105 @@
1
1
  ---
2
- title: "Ship a triage agent from the template"
3
- description: "Scaffold the triage template, make its rubric yours, prove it with evals, and deploy it to Cursor-managed hosting."
2
+ title: "Triage Linear or Jira issues in place"
3
+ description: "Scaffold the agent, point it at an existing ticket, tune the rubric, run evals, and deploy."
4
4
  ---
5
5
 
6
- # Ship a triage agent from the template
6
+ # Triage Linear or Jira issues in place
7
7
 
8
- The triage template is a working agent you own from the first command.
9
- It reads a report, classifies it as a bug, a question, or feedback,
10
- judges severity against a rubric, records a structured verdict, and
11
- replies with the next step. This page walks the whole arc: scaffold,
12
- first verdict, customize, prove, wire Slack, deploy.
8
+ This agent comments on tickets you already filed. It classifies the
9
+ issue, writes a `Triage:` comment, sets bug priority, and links a
10
+ duplicate when titles match.
13
11
 
14
- Every file the template ships is yours to edit. The only dependency is
15
- `@cursor/july` itself, pinned to the version the template was tested
16
- against. Nothing phones home to a template registry.
12
+ File the ticket however you already do. This template starts after the
13
+ ticket exists.
17
14
 
18
- ## Scaffold it
15
+ ## Scaffold
19
16
 
20
17
  ```bash
21
- npx @cursor/july init ./triage-agent --template triage
18
+ npx @cursor/july init ./triage-agent --template triage-linear
19
+ npx @cursor/july init ./triage-agent --template triage-jira
22
20
  ```
23
21
 
24
- One command with no prior install. It writes twelve files, runs
25
- `npm install`, puts `agent-sdk` on your PATH, and offers a browser
26
- sign-in when the machine has no Cursor credential. Model turns need
27
- that credential, so sign in now rather than mid-flow:
22
+ Pick the tracker you use. The agent is the same. The overlay is the
23
+ connection.
28
24
 
29
- ```bash
30
- agent-sdk login
31
- ```
25
+ `init` writes the project, installs dependencies, and puts `agent-sdk`
26
+ on PATH.
32
27
 
33
- The scaffold is a plain directory. Commit it to your own repo.
28
+ ## Log in
34
29
 
35
- ## Get the first verdict
30
+ Model turns and Cursor MCP use your Cursor account.
36
31
 
37
32
  ```bash
38
33
  cd triage-agent
39
- agent-sdk dev
34
+ agent-sdk login
40
35
  ```
41
36
 
42
- Open the playground URL it prints and type a real report:
37
+ The command opens a browser if you have no stored credential.
38
+
39
+ ## Connect the tracker
43
40
 
44
- > Export to CSV has returned 500s since this morning. Request id 41ac.
45
- > Retrying does not help.
41
+ In the Cursor dashboard, open MCP and connect Linear or Jira.
46
42
 
47
- Watch the turn. The agent reads the `triage-rubric` skill, calls
48
- `record_triage` with a structured verdict, and replies in six lines or
49
- fewer. The tool call renders inline, so you see the exact kind,
50
- severity, and next step it recorded.
43
+ The connection stays on the host. The model calls `record_triage`.
44
+ Linear and Jira tools stay off the model's tool list.
51
45
 
52
- Integrations use the same door your playground does. The `intake`
53
- channel accepts reports over HTTP:
46
+ ## First verdict
54
47
 
55
48
  ```bash
56
- curl -X POST http://127.0.0.1:3000/triage-agent/v1/channels/intake/report \
57
- -H 'content-type: application/json' \
58
- -d '{"message":"How do I rotate staging API keys?","reporter":"dana"}'
49
+ agent-sdk dev
59
50
  ```
60
51
 
61
- The composer also autocompletes `/report`, which drives this route.
62
-
63
- ## Make the rubric yours
64
-
65
- `agent/skills/triage-rubric.md` holds the judgment: what counts as a
66
- bug, the tie-breakers, and the severity ladder. Edit it first. Your
67
- team's definition of `urgent` belongs there, in plain markdown, and the
68
- next turn uses it without a restart step.
52
+ Open the playground. Run `/queue/issue` and paste an existing issue.
69
53
 
70
- `agent/instructions.md` owns the flow around the rubric: when to ask a
71
- clarifying question, the one-call `record_triage` rule, and the reply
72
- shape.
73
-
74
- ## Prove it with evals
75
-
76
- The template ships three smoke cases, one per kind. Run them after
77
- every rubric edit:
54
+ Local multi-agent mode mounts this project as `triage-agent`. You can
55
+ also POST:
78
56
 
79
57
  ```bash
80
- agent-sdk eval
58
+ curl -X POST http://127.0.0.1:3000/triage-agent/v1/channels/queue/issue \
59
+ -H 'content-type: application/json' \
60
+ -d '{
61
+ "identifier": "ENG-142",
62
+ "title": "CSV export returns 500",
63
+ "description": "Export has returned 500 since this morning."
64
+ }'
81
65
  ```
82
66
 
83
- Each case sends a realistic report and asserts a verdict was recorded
84
- with the expected kind. When you tighten the rubric, add a case for the
85
- report style you tightened it for. The eval suite is the regression
86
- net for prompt changes, so grow it with the rubric.
87
-
88
- ## File verdicts into your tracker
89
-
90
- `agent/tools/record_triage.ts` defines the verdict shape with a zod
91
- schema and returns it. Point its `execute` at Linear, GitHub Issues, or
92
- a webhook to file real tickets. Test the tool without a model turn:
67
+ `POST /v1/channels/queue/issue` accepts
68
+ `{ identifier, title?, description? }` from the playground.
93
69
 
94
- ```bash
95
- agent-sdk call record_triage --dir . --input '{
96
- "kind": "bug",
97
- "severity": "high",
98
- "title": "CSV export 500s",
99
- "summary": "Export returns 500 since this morning.",
100
- "nextStep": "Check export service logs."
101
- }'
102
- ```
70
+ Hosted Cursor aliases require `X-Agent-Alias-Token`, which Linear and
71
+ Jira cannot send. For create webhooks, self-host, set
72
+ `LINEAR_WEBHOOK_SECRET` or `JIRA_WEBHOOK_SECRET`, and POST the signed
73
+ body to `/v1/channels/webhook`. The channel ACKs immediately and
74
+ coalesces retries onto the same issue.
103
75
 
104
- The call validates against the schema and runs your `execute` body
105
- in-process, so tracker wiring gets a fast loop.
76
+ `record_triage` comments on the issue in the continuation key, not
77
+ whatever identifier the model copies out of the ticket body.
106
78
 
107
- ## Wire it to Slack
79
+ ## Rubric
108
80
 
109
- The `slack` channel idles until its tokens exist, so local work never
110
- blocks on Slack. When you want it live:
81
+ Edit `agent/skills/triage-rubric.md` for kinds, tie-breakers, and what
82
+ `urgent` means on your team. The next turn reads the file.
111
83
 
112
- ```bash
113
- agent-sdk slack setup
114
- ```
84
+ `agent/instructions.md` is the procedure around the rubric: missing
85
+ details, when to call `record_triage`, how to reply.
115
86
 
116
- The guided flow creates the Slack app and writes the token pair to
117
- `.env.local`. Restart `agent-sdk dev`, invite the bot to a channel, and
118
- @mention it with a report. The [Slack guide](../guides/slack.md) covers
119
- channel watching and approvals when you outgrow mentions.
87
+ ## Evals
120
88
 
121
- ## Deploy it
89
+ Three smoke cases, one per kind. Run them after you change the rubric.
122
90
 
123
91
  ```bash
124
- agent-sdk deploy
92
+ agent-sdk eval
125
93
  ```
126
94
 
127
- From your git checkout, `deploy` infers the repo, ref, and slug, builds
128
- the ref on Cursor-managed hosting, and prints the hosted URL plus a
129
- one-time alias token. Store the token. Production Slack tokens go in as
130
- deployment secrets:
95
+ Eval sessions skip tracker writes.
96
+
97
+ ## Deploy
131
98
 
132
99
  ```bash
133
- agent-sdk secrets set SLACK_BOT_TOKEN SLACK_APP_TOKEN --slug triage-agent
134
100
  agent-sdk deploy
135
101
  ```
136
102
 
137
- Check on it with `agent-sdk deployments`. The
138
- [deployment guide](../deployment.md) covers self-hosting, storage, and
139
- auth when you need them.
140
-
141
- ## Where to go next
142
-
143
- - Add a GitHub intake with the [GitHub guide](../guides/github.md), so
144
- issue comments triage themselves.
145
- - Persist verdicts across restarts with [storage](../storage.md).
146
- - Measure rubric changes on live traffic with
147
- [A/B metrics](../ab.md).
103
+ Run it from the agent's git checkout. It infers repository, ref, path,
104
+ and slug, builds on Cursor-managed hosting, and prints the URL.
105
+ `agent-sdk deployments` shows status.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -11,9 +11,10 @@
11
11
  import type { SlackManifestOptions } from "./types.js";
12
12
 
13
13
  /**
14
- * Where Slack points users who open a Cursor-provisioned app's settings
15
- * page. Manifest builders know neither the team slug nor the deployment
16
- * id, so this is the dashboard tab root rather than a per-agent deep link.
14
+ * Dashboard tab root: Slack's app-settings banner, and the only OAuth
15
+ * redirect URI we register. AAA request-approval (`oauth_authorize_url`)
16
+ * 400s with redirect_uri mismatch unless a URI is registered. This is
17
+ * not an install callback. `apps.managedInstall` still installs.
17
18
  */
18
19
  export const SLACK_EXTERNAL_APP_MANAGEMENT_URL =
19
20
  "https://cursor.com/dashboard/deployed-agents";
@@ -42,6 +43,7 @@ export interface SlackAppManifest {
42
43
  scopes: {
43
44
  bot: string[];
44
45
  };
46
+ redirect_urls?: string[];
45
47
  };
46
48
  settings: {
47
49
  event_subscriptions: {
@@ -174,6 +176,10 @@ export function buildSlackManifest(
174
176
  scopes: {
175
177
  bot: botScopes,
176
178
  },
179
+ redirect_urls:
180
+ options.managedByCursor === false
181
+ ? undefined
182
+ : [SLACK_EXTERNAL_APP_MANAGEMENT_URL],
177
183
  },
178
184
  settings: {
179
185
  event_subscriptions: {
@@ -292,6 +292,8 @@ export async function cmdInit(options: AxCliOptions): Promise<number> {
292
292
  return 0;
293
293
  }
294
294
  let templateDir: string | undefined;
295
+ let overlayDir: string | undefined;
296
+ let templateName: string | undefined;
295
297
  if (options.template !== undefined) {
296
298
  const template = resolveTemplate(options.template);
297
299
  if (!template.ok) {
@@ -299,6 +301,8 @@ export async function cmdInit(options: AxCliOptions): Promise<number> {
299
301
  return 1;
300
302
  }
301
303
  templateDir = template.dir;
304
+ overlayDir = template.overlayDir;
305
+ templateName = template.name;
302
306
  }
303
307
  const cwd = options.cwd ?? process.cwd();
304
308
  const dir = resolve(cwd, target);
@@ -307,7 +311,12 @@ export async function cmdInit(options: AxCliOptions): Promise<number> {
307
311
  process.stderr.write(`${targetCheck.message}\n`);
308
312
  return 1;
309
313
  }
310
- const result = await initProject({ dir, templateDir });
314
+ const result = await initProject({
315
+ dir,
316
+ templateDir,
317
+ overlayDir,
318
+ templateName,
319
+ });
311
320
  let signedIn = (await resolveApiKey()) !== undefined;
312
321
 
313
322
  // Machine-readable path: install deps (piped), never block on browser login.
@@ -223,8 +223,8 @@ export async function resolveDeployAuth(
223
223
  `${dim(
224
224
  `Using team ${teamId}${
225
225
  identity.teamName === undefined ? "" : ` (${identity.teamName})`
226
- } — the account's team; pass --team to override.`
227
- )}\n`
226
+ }`
227
+ )}\n${dim(" the account's team — pass --team to override.")}\n`
228
228
  );
229
229
  }
230
230
 
@@ -34,6 +34,7 @@ import {
34
34
  type SlackWizardMintResult,
35
35
  type SlackWizardPollResult,
36
36
  } from "./slack-provision-client.js";
37
+ import { stderrPalette, type TerminalPalette } from "./terminal-style.js";
37
38
 
38
39
  export interface SlackCliOptions {
39
40
  dir: string;
@@ -71,6 +72,7 @@ export interface SlackCliOptions {
71
72
  doctorImpl?: typeof runSlackDoctor;
72
73
  out?: (text: string) => void;
73
74
  err?: (text: string) => void;
75
+ palette?: TerminalPalette;
74
76
  }
75
77
 
76
78
  const SLACK_HELP = `${CLI} slack — Slack channel pack helpers (Socket Mode)
@@ -198,6 +200,7 @@ interface SlackProvisionContext {
198
200
  json: boolean;
199
201
  out: (text: string) => void;
200
202
  err: (text: string) => void;
203
+ dim: (s: string) => string;
201
204
  openBrowserImpl: (url: string) => void;
202
205
  pollIntervalMs: number;
203
206
  pollTimeoutMs: number;
@@ -373,6 +376,7 @@ async function prepareProvision(
373
376
  json: options.json === true,
374
377
  out,
375
378
  err,
379
+ dim: (options.palette ?? stderrPalette()).dim,
376
380
  openBrowserImpl: options.openBrowserImpl ?? openBrowser,
377
381
  pollIntervalMs:
378
382
  options.connectPollIntervalMs ?? DEFAULT_WIZARD_POLL_INTERVAL_MS,
@@ -381,6 +385,40 @@ async function prepareProvision(
381
385
  };
382
386
  }
383
387
 
388
+ function writePendingAdminWait(
389
+ ctx: SlackProvisionContext,
390
+ poll: SlackWizardPollResult
391
+ ): void {
392
+ const appSuffix = poll.appId === undefined ? "" : ` (${poll.appId})`;
393
+ const requestUrl =
394
+ poll.oauthAuthorizeUrl !== undefined && poll.oauthAuthorizeUrl.length > 0
395
+ ? poll.oauthAuthorizeUrl
396
+ : undefined;
397
+ ctx.err(`Waiting for a Slack admin to approve the app${appSuffix}.\n\n`);
398
+ if (requestUrl !== undefined) {
399
+ ctx.err(
400
+ ctx.dim(
401
+ ` Slack will notify workspace owners once the request is in their queue.\n` +
402
+ ` Open this page to file it or send a reminder:\n\n`
403
+ )
404
+ );
405
+ ctx.err(` ${requestUrl}\n\n`);
406
+ ctx.openBrowserImpl(requestUrl);
407
+ } else {
408
+ ctx.err(
409
+ ctx.dim(
410
+ ` Slack will notify workspace owners once the request is in their queue.\n\n`
411
+ )
412
+ );
413
+ }
414
+ ctx.err(
415
+ ctx.dim(
416
+ ` Keep this command running.\n` +
417
+ ` After an admin approves, click Retry in the dashboard.\n`
418
+ )
419
+ );
420
+ }
421
+
384
422
  export async function cmdSlackCreate(
385
423
  options: SlackCliOptions
386
424
  ): Promise<number> {
@@ -412,7 +450,9 @@ export async function cmdSlackCreate(
412
450
  return 1;
413
451
  }
414
452
  if (wrote.length > 0 && !ctx.json) {
415
- ctx.err(`Scaffolded the Slack channel: ${wrote.join(", ")}\n`);
453
+ ctx.err(
454
+ `${ctx.dim(`Scaffolded the Slack channel: ${wrote.join(", ")}`)}\n`
455
+ );
416
456
  }
417
457
 
418
458
  const source = await inferDeployGitSource(target.dir);
@@ -436,10 +476,13 @@ export async function cmdSlackCreate(
436
476
  return renderSlackApiError(error, ctx.err);
437
477
  }
438
478
 
479
+ const waitMinutes = Math.round(ctx.pollTimeoutMs / 60_000);
480
+ ctx.err(`Finish Slack setup in the browser:\n\n ${minted.url}\n\n`);
439
481
  ctx.err(
440
- `Finish Slack setup in the browser:\n\n ${minted.url}\n\n` +
441
- `Sign in as the same Cursor account that ran this command. ` +
442
- `The CLI waits here (up to ${Math.round(ctx.pollTimeoutMs / 60_000)} minutes).\n`
482
+ ctx.dim(
483
+ ` Sign in as the same Cursor account that ran this command.\n` +
484
+ ` The CLI waits here (up to ${waitMinutes} minutes).\n`
485
+ )
443
486
  );
444
487
  ctx.openBrowserImpl(minted.url);
445
488
 
@@ -465,23 +508,16 @@ export async function cmdSlackCreate(
465
508
  }
466
509
  if (poll.status === "pending_admin_approval" && !sawPendingAdmin) {
467
510
  sawPendingAdmin = true;
468
- const appSuffix = poll.appId === undefined ? "" : ` (${poll.appId})`;
469
- ctx.err(
470
- `Waiting for a Slack admin to approve the app${appSuffix}. Keep this command running.\n` +
471
- `Managed install does not file the request. Open Slack to submit it; that same page lets you send a reminder to workspace owners.\n`
472
- );
473
- if (
474
- poll.oauthAuthorizeUrl !== undefined &&
475
- poll.oauthAuthorizeUrl.length > 0
476
- ) {
477
- ctx.err(` ${poll.oauthAuthorizeUrl}\n`);
478
- }
479
- ctx.err("After an admin approves, click Retry in the dashboard.\n");
511
+ writePendingAdminWait(ctx, poll);
480
512
  }
481
513
  if (Date.now() >= deadline) {
514
+ ctx.err(`Timed out waiting for Slack setup.\n\n`);
482
515
  ctx.err(
483
- `Timed out waiting for Slack setup. Finish it in the browser, then re-run \`${CLI} slack create\`.\n ${minted.url}\n`
516
+ ctx.dim(
517
+ ` Finish it in the browser, then re-run \`${CLI} slack create\`.\n\n`
518
+ )
484
519
  );
520
+ ctx.err(` ${minted.url}\n`);
485
521
  return 1;
486
522
  }
487
523
  await sleep(ctx.pollIntervalMs);
@@ -518,7 +554,10 @@ export async function cmdSlackCreate(
518
554
  }
519
555
  if (poll.localEnv.appToken === undefined) {
520
556
  ctx.err(
521
- `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`
557
+ ctx.dim(
558
+ `warning: Slack did not return an app-level token, so ${target.envKeys.app} was not written.\n` +
559
+ ` Re-run \`${CLI} slack create\` to recover it.\n`
560
+ )
522
561
  );
523
562
  }
524
563
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync, readdirSync, readFileSync } from "node:fs";
6
+ import { existsSync, readFileSync } from "node:fs";
7
7
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
8
8
  import { basename, dirname, join, relative, resolve } from "node:path";
9
9
  import type { ModelSetting } from "../types.js";
@@ -27,6 +27,8 @@ export interface InitProjectOptions {
27
27
  /** Model id override written into agent.ts. */
28
28
  model?: string;
29
29
  templateDir?: string;
30
+ overlayDir?: string;
31
+ templateName?: string;
30
32
  }
31
33
 
32
34
  export type InitFileAction = "create" | "exist";
@@ -46,23 +48,21 @@ export interface InitProjectResult {
46
48
  skipped: string[];
47
49
  }
48
50
 
51
+ const TRIAGE_OVERLAYS: Readonly<Record<string, string>> = {
52
+ "triage-linear": "linear",
53
+ "triage-jira": "jira",
54
+ };
55
+
56
+ function canonicalTemplateName(name: string): string {
57
+ return name === "triage" ? "triage-linear" : name;
58
+ }
59
+
49
60
  export function availableTemplates(): string[] {
50
- const templatesDir = join(packageRootDir(), "templates");
51
- try {
52
- return readdirSync(templatesDir, { withFileTypes: true })
53
- .filter((entry) => entry.isDirectory())
54
- .map((entry) => entry.name)
55
- .sort();
56
- } catch (error) {
57
- if (hasErrorCode(error, "ENOENT")) {
58
- return [];
59
- }
60
- throw error;
61
- }
61
+ return Object.keys(TRIAGE_OVERLAYS).sort();
62
62
  }
63
63
 
64
64
  export type TemplateResolution =
65
- | { ok: true; name: string; dir: string }
65
+ | { ok: true; name: string; dir: string; overlayDir?: string }
66
66
  | { ok: false; message: string };
67
67
 
68
68
  export function resolveTemplate(name: string): TemplateResolution {
@@ -72,21 +72,20 @@ export function resolveTemplate(name: string): TemplateResolution {
72
72
  message: "template name must match /^[a-z][a-z0-9-]*$/",
73
73
  };
74
74
  }
75
- const available = availableTemplates();
76
- if (!available.includes(name)) {
77
- const detail =
78
- available.length === 0
79
- ? "no templates ship with this build"
80
- : `available: ${available.join(", ")}`;
75
+ const resolved = canonicalTemplateName(name);
76
+ const overlay = TRIAGE_OVERLAYS[resolved];
77
+ if (overlay !== undefined) {
78
+ const dir = join(packageRootDir(), "templates", "triage");
81
79
  return {
82
- ok: false,
83
- message: `unknown template "${name}" (${detail})`,
80
+ ok: true,
81
+ name: resolved,
82
+ dir,
83
+ overlayDir: join(dir, "overlays", overlay),
84
84
  };
85
85
  }
86
86
  return {
87
- ok: true,
88
- name,
89
- dir: join(packageRootDir(), "templates", name),
87
+ ok: false,
88
+ message: `unknown template "${name}" (available: ${availableTemplates().join(", ")})`,
90
89
  };
91
90
  }
92
91
 
@@ -121,6 +120,13 @@ export function checkInitTarget(
121
120
  if (marker === template) {
122
121
  return { ok: true };
123
122
  }
123
+ if (
124
+ marker !== undefined &&
125
+ template !== undefined &&
126
+ canonicalTemplateName(marker) === canonicalTemplateName(template)
127
+ ) {
128
+ return { ok: true };
129
+ }
124
130
  if (template === undefined) {
125
131
  return {
126
132
  ok: false,
@@ -194,28 +200,31 @@ export async function initProject(
194
200
 
195
201
  if (options.templateDir !== undefined) {
196
202
  const templateDir = options.templateDir;
197
- const entries = await readdir(templateDir, {
198
- recursive: true,
199
- withFileTypes: true,
200
- });
201
- const relativePaths = entries
202
- .filter((entry) => entry.isFile())
203
- .map((entry) => relative(templateDir, join(entry.parentPath, entry.name)))
204
- .sort();
205
- for (const rel of relativePaths) {
206
- const source = join(templateDir, rel);
207
- if (rel === "package.json") {
208
- const packageJson: {
209
- name: string;
210
- dependencies: Record<string, string>;
211
- [key: string]: unknown;
212
- } = JSON.parse(await readFile(source, "utf8"));
213
- packageJson.name = projectName;
214
- packageJson.dependencies[PACKAGE_NAME] = `^${packageVersion()}`;
215
- packageJson.cursorAgentTemplate = basename(templateDir);
216
- await write(rel, `${JSON.stringify(packageJson, null, 2)}\n`);
217
- } else {
218
- await write(rel, await readFile(source));
203
+ const overlayDir = options.overlayDir;
204
+ const overlayPaths = overlayDir ? await listTemplateFiles(overlayDir) : [];
205
+ const overlaySet = new Set(overlayPaths);
206
+ const corePaths = (await listTemplateFiles(templateDir)).filter(
207
+ (rel) => !overlaySet.has(rel)
208
+ );
209
+ const templateName = options.templateName ?? basename(templateDir);
210
+ for (const rel of corePaths) {
211
+ await writeTemplateFile({
212
+ write,
213
+ sourceDir: templateDir,
214
+ rel,
215
+ projectName,
216
+ templateName,
217
+ });
218
+ }
219
+ if (overlayDir !== undefined) {
220
+ for (const rel of overlayPaths) {
221
+ await writeTemplateFile({
222
+ write,
223
+ sourceDir: overlayDir,
224
+ rel,
225
+ projectName,
226
+ templateName,
227
+ });
219
228
  }
220
229
  }
221
230
  return {
@@ -346,6 +355,41 @@ export default memoryHook();
346
355
  };
347
356
  }
348
357
 
358
+ async function listTemplateFiles(root: string): Promise<string[]> {
359
+ const entries = await readdir(root, {
360
+ recursive: true,
361
+ withFileTypes: true,
362
+ });
363
+ return entries
364
+ .filter((entry) => entry.isFile())
365
+ .map((entry) => relative(root, join(entry.parentPath, entry.name)))
366
+ .filter((rel) => rel.split(/[\\/]/)[0] !== "overlays")
367
+ .sort();
368
+ }
369
+
370
+ async function writeTemplateFile(input: {
371
+ write: (rel: string, contents: string | Uint8Array) => Promise<void>;
372
+ sourceDir: string;
373
+ rel: string;
374
+ projectName: string;
375
+ templateName: string;
376
+ }): Promise<void> {
377
+ const source = join(input.sourceDir, input.rel);
378
+ if (input.rel === "package.json") {
379
+ const packageJson: {
380
+ name: string;
381
+ dependencies: Record<string, string>;
382
+ [key: string]: unknown;
383
+ } = JSON.parse(await readFile(source, "utf8"));
384
+ packageJson.name = input.projectName;
385
+ packageJson.dependencies[PACKAGE_NAME] = `^${packageVersion()}`;
386
+ packageJson.cursorAgentTemplate = input.templateName;
387
+ await input.write(input.rel, `${JSON.stringify(packageJson, null, 2)}\n`);
388
+ return;
389
+ }
390
+ await input.write(input.rel, await readFile(source));
391
+ }
392
+
349
393
  export function isEexist(error: unknown): boolean {
350
394
  return hasErrorCode(error, "EEXIST");
351
395
  }
@@ -740,7 +784,8 @@ export function printInitGuide(): void {
740
784
 
741
785
  Scaffold a project, then open the playground:
742
786
  npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
743
- npx ${PACKAGE_NAME} init ./my-triage --template triage # start from a template (agent-sdk templates: triage)
787
+ npx ${PACKAGE_NAME} init ./my-triage --template triage-linear # comment on Linear issues
788
+ npx ${PACKAGE_NAME} init ./my-triage --template triage-jira # comment on Jira issues
744
789
  cd ./my-agent && agent-sdk dev # or: npx ${PACKAGE_NAME} dev
745
790
 
746
791
  Recommended loop once the project exists (from the project directory):