@orderful/droid 0.38.0 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.claude-plugin/plugin.json +2 -0
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +17 -0
  4. package/dist/bin/droid.js +136 -3
  5. package/dist/commands/auth.d.ts +3 -0
  6. package/dist/commands/auth.d.ts.map +1 -0
  7. package/dist/lib/secrets.d.ts +7 -0
  8. package/dist/lib/secrets.d.ts.map +1 -0
  9. package/dist/tools/status-update/.claude-plugin/plugin.json +22 -0
  10. package/dist/tools/status-update/TOOL.yaml +21 -0
  11. package/dist/tools/status-update/commands/status-update.md +27 -0
  12. package/dist/tools/status-update/skills/status-update/SKILL.md +253 -0
  13. package/dist/tools/status-update/skills/status-update/references/formatting.md +203 -0
  14. package/dist/tools/tech-design/skills/tech-design/SKILL.md +47 -10
  15. package/dist/tools/tech-design/skills/tech-design/references/draft.md +8 -0
  16. package/dist/tools/tech-design/skills/tech-design/references/publish.md +164 -48
  17. package/dist/tools/tech-design/skills/tech-design/references/summary-template.md +94 -0
  18. package/package.json +1 -1
  19. package/src/bin/droid.ts +19 -0
  20. package/src/commands/auth.ts +150 -0
  21. package/src/lib/secrets.ts +12 -0
  22. package/src/tools/status-update/.claude-plugin/plugin.json +22 -0
  23. package/src/tools/status-update/TOOL.yaml +21 -0
  24. package/src/tools/status-update/commands/status-update.md +27 -0
  25. package/src/tools/status-update/skills/status-update/SKILL.md +253 -0
  26. package/src/tools/status-update/skills/status-update/references/formatting.md +203 -0
  27. package/src/tools/tech-design/skills/tech-design/SKILL.md +47 -10
  28. package/src/tools/tech-design/skills/tech-design/references/draft.md +8 -0
  29. package/src/tools/tech-design/skills/tech-design/references/publish.md +164 -48
  30. package/src/tools/tech-design/skills/tech-design/references/summary-template.md +94 -0
@@ -0,0 +1,94 @@
1
+ # {Feature Name} - Summary
2
+
3
+ > **Quick Overview:** {2-3 sentences: what we're building, why it matters, expected impact}
4
+
5
+ **Related Documents:**
6
+ - [Full Tech Design](./TECH-DESIGN.md) - Complete technical design with all decisions
7
+ - [PRD](./PRD.md) - Product requirements
8
+ - [Thought Doc](./artifacts/thought-doc.md) - Full exploration and working notes
9
+
10
+ ---
11
+
12
+ ## The Problem
13
+
14
+ {2-3 paragraphs: current pain, impact if not solved, who's affected}
15
+
16
+ ---
17
+
18
+ ## The Solution
19
+
20
+ ### Core Concept
21
+
22
+ {1-2 paragraphs: high-level approach, how it works}
23
+
24
+ **Flow:**
25
+ ```
26
+ {Simple diagram or step-by-step flow}
27
+ ```
28
+
29
+ ### Key Components
30
+
31
+ {3-5 bullet points with brief explanations of main pieces}
32
+
33
+ ---
34
+
35
+ ## Scope
36
+
37
+ ### In Scope (MVP)
38
+ ✅ {Feature 1}
39
+ ✅ {Feature 2}
40
+ ✅ {Feature 3}
41
+
42
+ ### Out of Scope
43
+ ❌ {Deferred feature 1}
44
+ ❌ {Deferred feature 2}
45
+
46
+ ---
47
+
48
+ ## Key Design Decisions
49
+
50
+ ### 1. {Decision Topic}
51
+ {What we chose}
52
+
53
+ **Why:** {Brief rationale}
54
+
55
+ ### 2. {Decision Topic}
56
+ {What we chose}
57
+
58
+ **Why:** {Brief rationale}
59
+
60
+ ---
61
+
62
+ ## Implementation Timeline
63
+
64
+ {Brief phase overview with status}
65
+
66
+ See [Full Tech Design](./TECH-DESIGN.md) for complete details.
67
+
68
+ ---
69
+
70
+ ## Success Metrics
71
+
72
+ **MVP Success Criteria:**
73
+ - {Metric 1}
74
+ - {Metric 2}
75
+ - {Metric 3}
76
+
77
+ **Long-Term Impact:**
78
+ - {Goal 1}
79
+ - {Goal 2}
80
+
81
+ ---
82
+
83
+ ## Risks & Mitigations
84
+
85
+ | Risk | Mitigation |
86
+ |------|------------|
87
+ | {Risk} | {How we handle it} |
88
+
89
+ ---
90
+
91
+ ## For More Details
92
+
93
+ - **Full technical design:** [TECH-DESIGN.md](./TECH-DESIGN.md)
94
+ - **{Additional artifact}:** [artifacts/{file}.md](./artifacts/{file}.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.38.0",
3
+ "version": "0.39.1",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin/droid.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  reposRemoveCommand,
17
17
  reposGetCommand,
18
18
  } from '../commands/repos';
19
+ import { authSlackCommand, authStatusCommand } from '../commands/auth';
19
20
  import { getVersion } from '../lib/version';
20
21
  import { loadConfig, saveConfig, configExists } from '../lib/config';
21
22
  import { syncNewPlatforms } from '../lib/platforms';
@@ -109,6 +110,24 @@ program
109
110
  .allowUnknownOption()
110
111
  .action(execCommand);
111
112
 
113
+ // Auth command with subcommands
114
+ const auth = program
115
+ .command('auth')
116
+ .description('Set up authentication for external services');
117
+
118
+ auth
119
+ .command('slack')
120
+ .description('Set up Slack authentication for status updates')
121
+ .action(authSlackCommand);
122
+
123
+ auth
124
+ .command('status')
125
+ .description('Show authentication status')
126
+ .action(authStatusCommand);
127
+
128
+ // Default: show auth status if no subcommand
129
+ auth.action(authStatusCommand);
130
+
112
131
  // Sync new platforms before running any command
113
132
  // This ensures that newly installed platforms (e.g., Codex) get all existing tools
114
133
  if (configExists()) {
@@ -0,0 +1,150 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { execSync } from 'child_process';
4
+ import { hasSlackToken } from '../lib/secrets';
5
+
6
+ // Slack OAuth configuration
7
+ const SLACK_SCOPES = 'chat:write,canvases:write';
8
+ const REDIRECT_URI = 'https://localhost:9876/callback';
9
+
10
+ /**
11
+ * Get the user's shell config file path
12
+ */
13
+ function getShellConfig(): string {
14
+ const shell = process.env.SHELL || '/bin/zsh';
15
+ if (shell.includes('zsh')) return '~/.zshrc';
16
+ if (shell.includes('bash')) return '~/.bashrc';
17
+ if (shell.includes('fish')) return '~/.config/fish/config.fish';
18
+ return '~/.profile';
19
+ }
20
+
21
+ export async function authSlackCommand(): Promise<void> {
22
+ console.log(chalk.bold('\n🔐 Slack Authentication Setup\n'));
23
+
24
+ // Check current status
25
+ if (hasSlackToken()) {
26
+ console.log(chalk.green('✓ SLACK_USER_TOKEN is already set in your environment\n'));
27
+ const { reauth } = await inquirer.prompt<{ reauth: boolean }>([
28
+ {
29
+ type: 'confirm',
30
+ name: 'reauth',
31
+ message: 'Re-authenticate anyway?',
32
+ default: false,
33
+ },
34
+ ]);
35
+
36
+ if (!reauth) {
37
+ return;
38
+ }
39
+ console.log('');
40
+ }
41
+
42
+ // Check for client credentials
43
+ const clientId = process.env.SLACK_CLIENT_ID;
44
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
45
+
46
+ if (!clientId || !clientSecret) {
47
+ const shellConfig = getShellConfig();
48
+ console.log(chalk.yellow('Missing Slack app credentials.\n'));
49
+ console.log(chalk.gray('Get Client ID and Client Secret from 1Password ("Droid Slack App")'));
50
+ console.log(chalk.gray(`and add to your ${shellConfig}:\n`));
51
+ console.log(chalk.white(' export SLACK_CLIENT_ID="your-client-id"'));
52
+ console.log(chalk.white(' export SLACK_CLIENT_SECRET="your-client-secret"\n'));
53
+ console.log(chalk.gray('Then reload and run this again:\n'));
54
+ console.log(chalk.white(` source ${shellConfig} && droid auth slack\n`));
55
+ return;
56
+ }
57
+
58
+ // Step 1: Direct user to authorize
59
+ const authorizeUrl = `https://slack.com/oauth/v2/authorize?client_id=${clientId}&user_scope=${SLACK_SCOPES}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;
60
+
61
+ console.log(chalk.yellow('Step 1: Authorize in Slack\n'));
62
+ console.log(chalk.gray(' Go to the "Droid Setup" canvas in #rnd-updates and click "Install Droid Slack App"'));
63
+ console.log(chalk.gray(' Or open this URL:\n'));
64
+ console.log(chalk.cyan(` ${authorizeUrl}\n`));
65
+ console.log(chalk.gray(' After clicking Allow, you\'ll be redirected to a URL that won\'t load.'));
66
+ console.log(chalk.gray(' Copy the "code" parameter from the URL bar.\n'));
67
+
68
+ // Step 2: Get the code
69
+ const { code } = await inquirer.prompt<{ code: string }>([
70
+ {
71
+ type: 'input',
72
+ name: 'code',
73
+ message: 'Paste the code:',
74
+ validate: (input: string) => input.length > 0 || 'Code is required',
75
+ },
76
+ ]);
77
+
78
+ // Step 3: Exchange code for token
79
+ console.log(chalk.yellow('\nStep 2: Exchanging code for token...\n'));
80
+
81
+ try {
82
+ const response = execSync(
83
+ `curl -s -X POST https://slack.com/api/oauth.v2.access \
84
+ -d "client_id=${clientId}" \
85
+ -d "client_secret=${clientSecret}" \
86
+ -d "code=${code}" \
87
+ -d "redirect_uri=${REDIRECT_URI}"`,
88
+ { encoding: 'utf-8' }
89
+ );
90
+
91
+ const result = JSON.parse(response);
92
+
93
+ if (!result.ok) {
94
+ console.log(chalk.red(`✗ Slack API error: ${result.error}`));
95
+ if (result.error === 'invalid_code') {
96
+ console.log(chalk.gray(' The code may have expired. Try again with a fresh code.'));
97
+ }
98
+ return;
99
+ }
100
+
101
+ const token = result.authed_user?.access_token;
102
+ if (!token) {
103
+ console.log(chalk.red('✗ No user token in response'));
104
+ console.log(chalk.gray(' Response:'), JSON.stringify(result, null, 2));
105
+ return;
106
+ }
107
+
108
+ // Success!
109
+ const shellConfig = getShellConfig();
110
+ console.log(chalk.green('✓ Got token!\n'));
111
+ console.log(chalk.yellow('Step 3: Save the token\n'));
112
+ console.log(chalk.gray(` Add to your ${shellConfig}:\n`));
113
+ console.log(chalk.white(` export SLACK_USER_TOKEN="${token}"\n`));
114
+ console.log(chalk.gray(' Then reload your shell:\n'));
115
+ console.log(chalk.white(` source ${shellConfig}\n`));
116
+
117
+ } catch (error) {
118
+ console.log(chalk.red('✗ Failed to exchange code for token'));
119
+ if (error instanceof Error) {
120
+ console.log(chalk.gray(` ${error.message}`));
121
+ }
122
+ }
123
+ }
124
+
125
+ export async function authStatusCommand(): Promise<void> {
126
+ console.log(chalk.bold('\n🔐 Auth Status\n'));
127
+
128
+ const clientId = process.env.SLACK_CLIENT_ID;
129
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
130
+ const hasCredentials = clientId && clientSecret;
131
+
132
+ if (hasCredentials) {
133
+ console.log(chalk.green('✓ Slack app credentials configured'));
134
+ } else {
135
+ console.log(chalk.yellow('✗ Slack app credentials missing'));
136
+ console.log(chalk.gray(' Run: droid auth slack'));
137
+ }
138
+
139
+ if (hasSlackToken()) {
140
+ const token = process.env.SLACK_USER_TOKEN!;
141
+ const masked = token.slice(0, 10) + '...' + token.slice(-4);
142
+ console.log(chalk.green('✓ Slack user token configured'));
143
+ console.log(chalk.gray(` Token: ${masked}`));
144
+ } else {
145
+ console.log(chalk.yellow('✗ Slack user token missing'));
146
+ if (hasCredentials) {
147
+ console.log(chalk.gray(' Run: droid auth slack'));
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Secret/token helpers - reads from environment variables only
3
+ * Users manage their own tokens in shell config (e.g., ~/.zshrc)
4
+ */
5
+
6
+ export function getSlackToken(): string | undefined {
7
+ return process.env.SLACK_USER_TOKEN;
8
+ }
9
+
10
+ export function hasSlackToken(): boolean {
11
+ return !!process.env.SLACK_USER_TOKEN;
12
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "droid-status-update",
3
+ "version": "0.1.0",
4
+ "description": "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal.",
5
+ "author": {
6
+ "name": "Orderful",
7
+ "url": "https://github.com/orderful"
8
+ },
9
+ "repository": "https://github.com/orderful/droid",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "droid",
13
+ "ai",
14
+ "status-update"
15
+ ],
16
+ "skills": [
17
+ "./skills/status-update/SKILL.md"
18
+ ],
19
+ "commands": [
20
+ "./commands/status-update.md"
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ name: status-update
2
+ description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
3
+ version: 0.1.0
4
+ status: beta
5
+
6
+ includes:
7
+ skills:
8
+ - name: status-update
9
+ required: true
10
+ commands:
11
+ - name: status-update
12
+ is_alias: false
13
+ agents: []
14
+
15
+ dependencies: []
16
+
17
+ config_schema:
18
+ default_crosspost_channel:
19
+ type: string
20
+ description: Default channel for cross-posting updates (e.g., rnd-updates)
21
+ required: false
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: status-update
3
+ description: "Generate and post project status updates. Pulls from codex, Jira, and manual input. Posts to Slack or prints to terminal."
4
+ argument-hint: "[{project}]"
5
+ ---
6
+
7
+ # /status-update
8
+
9
+ **User invoked:** `/status-update $ARGUMENTS`
10
+
11
+ **Your task:** Invoke the **status-update skill** with these arguments.
12
+
13
+ ## Examples
14
+
15
+ - `/status-update` → Prompt for project or use active project
16
+ - `/status-update partnership-automation` → Generate update for specific project
17
+ - `/status-update --no-slack` → Generate update but print to terminal instead of posting
18
+
19
+ ## Quick Reference
20
+
21
+ ```
22
+ /status-update # Interactive - prompts for everything
23
+ /status-update {project} # Use codex project for metadata
24
+ /status-update --no-slack # Output to terminal instead of Slack
25
+ ```
26
+
27
+ See the **status-update skill** for complete documentation.
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: status-update
3
+ description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
4
+ argument-hint: "[{project}]"
5
+ allowed-tools:
6
+ [
7
+ Read,
8
+ Glob,
9
+ Grep,
10
+ Bash(git:*),
11
+ Bash(curl:*),
12
+ Bash(droid:*),
13
+ ]
14
+ ---
15
+
16
+ # Status Update Skill
17
+
18
+ Generate formatted project status updates from multiple sources and post to Slack (or print to terminal).
19
+
20
+ ## Data Sources (All Optional)
21
+
22
+ | Source | When available | What it provides |
23
+ |--------|----------------|------------------|
24
+ | **Codex project** | If project name matches a codex project | Metadata (slack, jira, links) + git history |
25
+ | **Jira MCP** | If jira.epic configured + MCP connected | Ticket status for in-flight/up-next drafts |
26
+ | **Manual input** | Always | User-provided status, phase, bullets |
27
+
28
+ ## Output Destinations
29
+
30
+ | Destination | When available | Fallback |
31
+ |-------------|----------------|----------|
32
+ | **Slack** | If slack.channel configured + SLACK_USER_TOKEN set | Print to terminal |
33
+
34
+ ## Procedure
35
+
36
+ ### 1. Resolve Project Context
37
+
38
+ **If project name provided:**
39
+
40
+ 1. Check if codex is configured:
41
+ ```bash
42
+ droid config --get tools.codex
43
+ ```
44
+
45
+ 2. If codex configured, look for matching project:
46
+ ```bash
47
+ ls {codex_repo}/projects/ | grep -i {project}
48
+ ```
49
+
50
+ 3. If found, read CONTEXT.md frontmatter:
51
+ - `title` - Project name
52
+ - `slack.channel` - Primary Slack channel
53
+ - `slack.crosspost_channel` - Optional crosspost channel
54
+ - `slack.canvas_id` - Optional canvas for update log
55
+ - `jira.epic` - Optional Jira epic key
56
+ - `links.prd`, `links.tech_design`, `links.figma` - Optional doc links
57
+
58
+ **If no project name provided:**
59
+
60
+ 1. Check for active project in conversation context
61
+ 2. If none, ask: "Which project is this update for?"
62
+ 3. If user provides a name, attempt to match to codex project
63
+ 4. If no match, continue without codex metadata (prompt for details manually)
64
+
65
+ **If no codex match:**
66
+
67
+ Prompt user for:
68
+ - Project name (for display)
69
+ - Slack channel (optional - skip to print to terminal)
70
+ - Jira epic key (optional)
71
+
72
+ ### 2. Generate Draft Content
73
+
74
+ Attempt to generate draft bullets from available sources.
75
+
76
+ #### Source A: Codex Git History (if codex project found)
77
+
78
+ ```bash
79
+ git -C {codex_repo} log --oneline --since="7 days ago" -- projects/{project}/
80
+ ```
81
+
82
+ Parse commits to understand recent activity:
83
+ - DECISIONS.md changes → decisions made
84
+ - New artifacts → meetings, documents produced
85
+ - CONTEXT.md changes → project evolution
86
+ - PRD.md/TECH-DESIGN.md changes → documentation progress
87
+
88
+ Generate human-readable draft bullets from the git history.
89
+
90
+ #### Source B: Jira MCP (if configured)
91
+
92
+ **Check MCP availability:**
93
+ ```
94
+ mcp__atlassian__getAccessibleAtlassianResources
95
+ ```
96
+
97
+ If available and `jira.epic` is set, run two JQL queries:
98
+
99
+ 1. **Recently completed + in progress:**
100
+ ```
101
+ parent = {jira.epic} AND (status changed to Done AFTER -7d OR statusCategory in ("In Progress"))
102
+ ```
103
+
104
+ 2. **Up next:**
105
+ ```
106
+ parent = {jira.epic} AND statusCategory = "To Do"
107
+ ```
108
+
109
+ Categorise tickets:
110
+
111
+ | Jira Status | Category |
112
+ |-------------|----------|
113
+ | Done (last 7 days) | In flight (completed) |
114
+ | In Progress, In Review, In QA | In flight (active) |
115
+ | To Do, Backlog | Up next |
116
+
117
+ #### Combine Sources
118
+
119
+ If multiple sources available:
120
+ - Deduplicate where codex change and Jira ticket describe the same work
121
+ - Prefer Jira summaries for ticket-tracked work
122
+ - Include codex-only changes (decisions, meetings) not in Jira
123
+
124
+ **Present draft to user** for review/editing before finalising.
125
+
126
+ ### 3. Gather Update Content
127
+
128
+ Ask user for (pre-fill from drafts where available):
129
+
130
+ 1. **Status** - One of:
131
+ - `on_track` (green) - Everything proceeding as planned
132
+ - `at_risk` (yellow) - Some concerns or blockers emerging
133
+ - `blocked` (red) - Cannot proceed without resolution
134
+
135
+ 2. **Current phase** - One of:
136
+ - `discovery` - Research and requirements gathering
137
+ - `post_discovery_feedback` - Incorporating discovery feedback
138
+ - `design` - Prototype and design work
139
+ - `post_design_feedback` - Incorporating design feedback
140
+ - `prd` - Writing PRD
141
+ - `tech_design` - Writing Tech Design
142
+ - `development` - Active implementation
143
+ - `qa` - QA & Testing
144
+ - `release` - Released to production
145
+
146
+ 3. **In flight this week** - Bullet points (from draft or manual)
147
+
148
+ 4. **Up next** - Bullet points (from draft or manual)
149
+
150
+ ### 4. Format Message
151
+
152
+ See `references/formatting.md` for detailed formatting rules.
153
+
154
+ **Structure:**
155
+ ```
156
+ *Status:* {status_emoji} *{status_text}*
157
+
158
+ *Where we're at*
159
+ {phase_checklist}
160
+
161
+ *In flight this week*
162
+ • {bullet 1}
163
+ • {bullet 2}
164
+
165
+ *Up next*
166
+ • {bullet 1}
167
+ • {bullet 2}
168
+
169
+ {links_footer if configured}
170
+
171
+ Posted with help from :droid:
172
+ ```
173
+
174
+ Add ` & :codex:` to footer if codex project was used.
175
+
176
+ ### 5. Output
177
+
178
+ #### If Slack configured (channel + SLACK_USER_TOKEN):
179
+
180
+ **Post to primary channel:**
181
+ ```bash
182
+ curl -s -X POST https://slack.com/api/chat.postMessage \
183
+ -H "Authorization: Bearer $SLACK_USER_TOKEN" \
184
+ -H "Content-Type: application/json; charset=utf-8" \
185
+ -d '{
186
+ "channel": "{slack.channel}",
187
+ "text": "{formatted_message}",
188
+ "unfurl_links": false
189
+ }'
190
+ ```
191
+
192
+ Check response for `"ok": true`. Save `ts` and `channel` from response.
193
+
194
+ **Crosspost (if crosspost_channel configured):**
195
+
196
+ Post summary version with link to full update:
197
+ ```
198
+ *{project_title}*
199
+
200
+ {status_line}
201
+ {current_phase_only}
202
+
203
+ {in_flight_section}
204
+
205
+ :link: <{message_url}|View full update>
206
+ ```
207
+
208
+ Message URL format: `https://orderful.slack.com/archives/{channel_id}/p{ts_without_dot}`
209
+
210
+ **Update canvas log (if canvas_id configured):**
211
+ ```bash
212
+ curl -s -X POST https://slack.com/api/canvases.edit \
213
+ -H "Authorization: Bearer $SLACK_USER_TOKEN" \
214
+ -H "Content-Type: application/json; charset=utf-8" \
215
+ -d '{
216
+ "canvas_id": "{slack.canvas_id}",
217
+ "changes": [{
218
+ "operation": "insert_at_start",
219
+ "document_content": {
220
+ "type": "markdown",
221
+ "markdown": "### {date}\n{status} · {current_phase}\n[View update]({message_url})\n\n---"
222
+ }
223
+ }]
224
+ }'
225
+ ```
226
+
227
+ **Confirm to user:**
228
+ - "Posted update to #{channel}"
229
+ - "Cross-posted to #{crosspost_channel}" (if applicable)
230
+ - "Added to canvas log" (if applicable)
231
+
232
+ #### If Slack NOT configured:
233
+
234
+ Print the formatted message to terminal.
235
+
236
+ Tell user: "Slack not configured. To post updates directly, add `slack.channel` to your project's CONTEXT.md and set `SLACK_USER_TOKEN` in your environment."
237
+
238
+ ### Error Handling
239
+
240
+ | Error | Action |
241
+ |-------|--------|
242
+ | No SLACK_USER_TOKEN | Print to terminal, suggest setup |
243
+ | Slack API error | Show error, offer to print instead |
244
+ | Jira MCP not available | Skip Jira, continue with other sources |
245
+ | No codex project found | Continue with manual input |
246
+
247
+ ## Configuration
248
+
249
+ No required configuration. Optional settings via `droid config`:
250
+
251
+ | Setting | Description |
252
+ |---------|-------------|
253
+ | `default_crosspost_channel` | Default channel for cross-posts if not in project metadata |