@orderful/droid 0.41.0 → 0.42.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.
- package/.claude-plugin/plugin.json +2 -0
- package/CHANGELOG.md +25 -0
- package/dist/bin/droid.js +132 -4
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/tui/components/IntegrationsDetails.d.ts.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/integrations/github/index.d.ts +6 -0
- package/dist/integrations/github/index.d.ts.map +1 -0
- package/dist/integrations/github/index.ts +17 -0
- package/dist/integrations/github/references/setup.md +61 -0
- package/dist/integrations/granola/references/setup.md +54 -0
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/meeting/.claude-plugin/plugin.json +22 -0
- package/dist/tools/meeting/TOOL.yaml +15 -0
- package/dist/tools/meeting/commands/meeting.md +35 -0
- package/dist/tools/meeting/skills/meeting/SKILL.md +113 -0
- package/dist/tools/meeting/skills/meeting/references/export-workflow.md +87 -0
- package/package.json +1 -1
- package/src/commands/integrations.ts +30 -0
- package/src/commands/tui/components/IntegrationsDetails.tsx +114 -0
- package/src/commands/tui.tsx +26 -4
- package/src/integrations/github/index.ts +17 -0
- package/src/integrations/github/references/setup.md +61 -0
- package/src/integrations/granola/references/setup.md +54 -0
- package/src/lib/types.ts +10 -0
- package/src/tools/meeting/.claude-plugin/plugin.json +22 -0
- package/src/tools/meeting/TOOL.yaml +15 -0
- package/src/tools/meeting/commands/meeting.md +35 -0
- package/src/tools/meeting/skills/meeting/SKILL.md +113 -0
- package/src/tools/meeting/skills/meeting/references/export-workflow.md +87 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Export Workflow
|
|
2
|
+
|
|
3
|
+
Full procedure for `/meeting export {title}`.
|
|
4
|
+
|
|
5
|
+
## E1. Resolve Meeting
|
|
6
|
+
|
|
7
|
+
1. Use `ToolSearch` to load `mcp__granola__list_meetings`
|
|
8
|
+
2. Call with `time_range: "last_30_days"` to get recent meetings
|
|
9
|
+
3. Fuzzy-match `{title}` against meeting titles
|
|
10
|
+
4. If multiple matches, present list and ask user to pick
|
|
11
|
+
5. If no match, show recent meetings and ask user to clarify
|
|
12
|
+
|
|
13
|
+
## E2. Get Meeting Data
|
|
14
|
+
|
|
15
|
+
1. Use `ToolSearch` to load `mcp__granola__get_meetings`
|
|
16
|
+
2. Call with the resolved meeting ID
|
|
17
|
+
3. Extract: title, date, participants, summary
|
|
18
|
+
|
|
19
|
+
## E3. Resolve Destination
|
|
20
|
+
|
|
21
|
+
If codex is in current context (user has loaded a codex repo this session):
|
|
22
|
+
- Export to `{codex_repo}/meetings/{date}-{slugified-title}.md`
|
|
23
|
+
- Confirm with user: "Export to codex at meetings/{filename}?"
|
|
24
|
+
|
|
25
|
+
If no codex in context:
|
|
26
|
+
- Ask: "Where should I export this meeting?"
|
|
27
|
+
- Codex — "Which codex repo?" (list available via `droid config --get repos`)
|
|
28
|
+
- Brain — Export to brain vault as a meeting doc
|
|
29
|
+
- Clipboard — Copy formatted markdown to clipboard
|
|
30
|
+
- Terminal — Just print it here
|
|
31
|
+
|
|
32
|
+
## E4. Format Document
|
|
33
|
+
|
|
34
|
+
Generate markdown with this exact frontmatter structure:
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
title: "{meeting title}"
|
|
38
|
+
type: meeting
|
|
39
|
+
source: granola
|
|
40
|
+
source_url: "https://notes.granola.ai/d/{meeting_id}"
|
|
41
|
+
date: {YYYY-MM-DD}
|
|
42
|
+
participants: [{comma-separated names}]
|
|
43
|
+
exported: {YYYY-MM-DD}
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# {meeting title}
|
|
47
|
+
|
|
48
|
+
**Date:** {full date and time}
|
|
49
|
+
**Participants:** {names with roles/orgs if available}
|
|
50
|
+
|
|
51
|
+
## Summary
|
|
52
|
+
|
|
53
|
+
{Granola's structured summary from get_meetings}
|
|
54
|
+
|
|
55
|
+
## Key Decisions
|
|
56
|
+
|
|
57
|
+
{Extract decisions from the summary, or note "No explicit decisions captured"}
|
|
58
|
+
|
|
59
|
+
## Action Items
|
|
60
|
+
|
|
61
|
+
{Extract action items from the summary, or note "No action items captured"}
|
|
62
|
+
|
|
63
|
+
## E5. Slugification Rules
|
|
64
|
+
|
|
65
|
+
Convert title to filename slug:
|
|
66
|
+
- Lowercase
|
|
67
|
+
- Replace spaces with hyphens
|
|
68
|
+
- Remove special characters except hyphens
|
|
69
|
+
- Strip leading/trailing hyphens
|
|
70
|
+
- Collapse multiple hyphens
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
- "[Tech Design Review] Automated Partner Testing" → `automated-partner-testing`
|
|
74
|
+
- "Tyler / Bosun - Partner Testing" → `tyler-bosun-partner-testing`
|
|
75
|
+
- "🌅 Horizon Daily" → `horizon-daily`
|
|
76
|
+
|
|
77
|
+
Final filename: `{YYYY-MM-DD}-{slug}.md`
|
|
78
|
+
|
|
79
|
+
## E6. Write File
|
|
80
|
+
|
|
81
|
+
Write the formatted document to the resolved destination path.
|
|
82
|
+
|
|
83
|
+
## E7. Confirm
|
|
84
|
+
|
|
85
|
+
Tell the user:
|
|
86
|
+
- "Exported to: {path}"
|
|
87
|
+
- If codex: suggest committing the new file
|
package/package.json
CHANGED
|
@@ -399,6 +399,36 @@ export async function integrationsStatusCommand(): Promise<void> {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
console.log('');
|
|
402
|
+
|
|
403
|
+
// GitHub
|
|
404
|
+
console.log(chalk.bold(' GitHub'));
|
|
405
|
+
|
|
406
|
+
const githubConfigured = getConfigValue('integrations.github.configured');
|
|
407
|
+
|
|
408
|
+
if (githubConfigured) {
|
|
409
|
+
console.log(chalk.green(' CLI: Installed'));
|
|
410
|
+
console.log(chalk.green(' Status: configured'));
|
|
411
|
+
} else {
|
|
412
|
+
console.log(chalk.yellow(' CLI: Not verified'));
|
|
413
|
+
console.log(chalk.gray(' Run: gh auth login'));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
console.log('');
|
|
417
|
+
|
|
418
|
+
// Granola
|
|
419
|
+
console.log(chalk.bold(' Granola'));
|
|
420
|
+
|
|
421
|
+
const granolaConfigured = getConfigValue('integrations.granola.configured');
|
|
422
|
+
|
|
423
|
+
if (granolaConfigured) {
|
|
424
|
+
console.log(chalk.green(' MCP: Connected'));
|
|
425
|
+
console.log(chalk.green(' Status: configured'));
|
|
426
|
+
} else {
|
|
427
|
+
console.log(chalk.yellow(' MCP: Not yet verified'));
|
|
428
|
+
console.log(chalk.gray(' Use /mcp to add the Granola server, then verify with a meeting query'));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log('');
|
|
402
432
|
}
|
|
403
433
|
|
|
404
434
|
/**
|
|
@@ -148,6 +148,112 @@ function AtlassianDetails({ isFocused, selectedAction, connected }: { isFocused:
|
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
function GithubDetails({ isFocused, selectedAction, connected }: { isFocused: boolean; selectedAction: number; connected: boolean }) {
|
|
152
|
+
return (
|
|
153
|
+
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
154
|
+
<Text color={colors.text} bold>GitHub CLI</Text>
|
|
155
|
+
|
|
156
|
+
<Box flexDirection="column" marginTop={1}>
|
|
157
|
+
<Text color={colors.textDim} bold>Status</Text>
|
|
158
|
+
<Text>
|
|
159
|
+
<Text color={colors.textDim}> CLI </Text>
|
|
160
|
+
{connected
|
|
161
|
+
? <Text color={colors.success}>Installed ✓</Text>
|
|
162
|
+
: <Text color="#fbbf24">Not detected</Text>}
|
|
163
|
+
</Text>
|
|
164
|
+
<Text>
|
|
165
|
+
<Text color={colors.textDim}> Auth </Text>
|
|
166
|
+
{connected
|
|
167
|
+
? <Text color={colors.success}>Authenticated ✓</Text>
|
|
168
|
+
: <Text color="#fbbf24">Not yet verified</Text>}
|
|
169
|
+
</Text>
|
|
170
|
+
</Box>
|
|
171
|
+
|
|
172
|
+
<Box flexDirection="column" marginTop={1}>
|
|
173
|
+
<Text color={colors.textDim}>
|
|
174
|
+
{connected
|
|
175
|
+
? 'Used by codex, tech-design, code-review, share'
|
|
176
|
+
: 'Run `gh auth login` to connect'}
|
|
177
|
+
</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
|
|
180
|
+
{isFocused && (
|
|
181
|
+
<Box marginTop={2} flexDirection="row" gap={2}>
|
|
182
|
+
<Text
|
|
183
|
+
backgroundColor={selectedAction === 0 ? colors.primary : undefined}
|
|
184
|
+
color={selectedAction === 0 ? '#ffffff' : colors.textDim}
|
|
185
|
+
bold={selectedAction === 0}
|
|
186
|
+
>
|
|
187
|
+
{' '}Setup Guide{' '}
|
|
188
|
+
</Text>
|
|
189
|
+
</Box>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{isFocused && (
|
|
193
|
+
<Box marginTop={1}>
|
|
194
|
+
<Text color={colors.textDim}>enter confirm {'·'} esc back</Text>
|
|
195
|
+
</Box>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{!isFocused && (
|
|
199
|
+
<Box marginTop={2}>
|
|
200
|
+
<Text color={colors.textDim}>press enter for options</Text>
|
|
201
|
+
</Box>
|
|
202
|
+
)}
|
|
203
|
+
</Box>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function GranolaDetails({ isFocused, selectedAction, connected }: { isFocused: boolean; selectedAction: number; connected: boolean }) {
|
|
208
|
+
return (
|
|
209
|
+
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
210
|
+
<Text color={colors.text} bold>Granola</Text>
|
|
211
|
+
|
|
212
|
+
<Box flexDirection="column" marginTop={1}>
|
|
213
|
+
<Text color={colors.textDim} bold>MCP Server</Text>
|
|
214
|
+
<Text>
|
|
215
|
+
<Text color={colors.textDim}> Status </Text>
|
|
216
|
+
{connected
|
|
217
|
+
? <Text color={colors.success}>Connected ✓</Text>
|
|
218
|
+
: <Text color="#fbbf24">Not yet verified</Text>}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
|
|
222
|
+
<Box flexDirection="column" marginTop={1}>
|
|
223
|
+
<Text color={colors.textDim}>
|
|
224
|
+
{connected
|
|
225
|
+
? 'Meeting notes and transcripts available via MCP'
|
|
226
|
+
: 'Use /mcp to add the Granola server'}
|
|
227
|
+
</Text>
|
|
228
|
+
</Box>
|
|
229
|
+
|
|
230
|
+
{isFocused && (
|
|
231
|
+
<Box marginTop={2} flexDirection="row" gap={2}>
|
|
232
|
+
<Text
|
|
233
|
+
backgroundColor={selectedAction === 0 ? colors.primary : undefined}
|
|
234
|
+
color={selectedAction === 0 ? '#ffffff' : colors.textDim}
|
|
235
|
+
bold={selectedAction === 0}
|
|
236
|
+
>
|
|
237
|
+
{' '}Setup Guide{' '}
|
|
238
|
+
</Text>
|
|
239
|
+
</Box>
|
|
240
|
+
)}
|
|
241
|
+
|
|
242
|
+
{isFocused && (
|
|
243
|
+
<Box marginTop={1}>
|
|
244
|
+
<Text color={colors.textDim}>enter confirm {'·'} esc back</Text>
|
|
245
|
+
</Box>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{!isFocused && (
|
|
249
|
+
<Box marginTop={2}>
|
|
250
|
+
<Text color={colors.textDim}>press enter for options</Text>
|
|
251
|
+
</Box>
|
|
252
|
+
)}
|
|
253
|
+
</Box>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
151
257
|
export function IntegrationsDetails({
|
|
152
258
|
isFocused,
|
|
153
259
|
selectedAction,
|
|
@@ -157,6 +263,14 @@ export function IntegrationsDetails({
|
|
|
157
263
|
return <AtlassianDetails isFocused={isFocused} selectedAction={selectedAction} connected={integration.connected} />;
|
|
158
264
|
}
|
|
159
265
|
|
|
266
|
+
if (integration.id === 'github') {
|
|
267
|
+
return <GithubDetails isFocused={isFocused} selectedAction={selectedAction} connected={integration.connected} />;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (integration.id === 'granola') {
|
|
271
|
+
return <GranolaDetails isFocused={isFocused} selectedAction={selectedAction} connected={integration.connected} />;
|
|
272
|
+
}
|
|
273
|
+
|
|
160
274
|
// Default: Slack
|
|
161
275
|
return <SlackDetails isFocused={isFocused} selectedAction={selectedAction} />;
|
|
162
276
|
}
|
package/src/commands/tui.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isToolInstalled,
|
|
16
16
|
getToolUpdateStatus,
|
|
17
17
|
} from '../lib/tools';
|
|
18
|
-
import { configExists, loadConfig, getAutoUpdateConfig, getConfigValue } from '../lib/config';
|
|
18
|
+
import { configExists, loadConfig, getAutoUpdateConfig, getConfigValue, setConfigValue } from '../lib/config';
|
|
19
19
|
import { type ConfigOption, type ToolManifest } from '../lib/types';
|
|
20
20
|
import { detectAllPlatforms } from '../lib/platforms';
|
|
21
21
|
import { getVersion } from '../lib/version';
|
|
@@ -38,12 +38,20 @@ import { ReposManagementScreen } from './tui/views/ReposManagementScreen';
|
|
|
38
38
|
import { ReposViewerScreen } from './tui/views/ReposViewerScreen';
|
|
39
39
|
import { useAppUpdate } from './tui/hooks/useAppUpdate';
|
|
40
40
|
import { useToolUpdates } from './tui/hooks/useToolUpdates';
|
|
41
|
+
import { checkGhAuth } from '../integrations/github';
|
|
41
42
|
|
|
42
43
|
// Module-level variable to store exit message (printed after leaving alternate screen)
|
|
43
44
|
let exitMessage: string | null = null;
|
|
44
45
|
// Module-level variable to store a CLI command to run after TUI exits
|
|
45
46
|
let exitCommand: string[] | null = null;
|
|
46
47
|
|
|
48
|
+
const INTEGRATION_GUIDE_TITLES: Record<string, string> = {
|
|
49
|
+
slack: 'Slack Integration Setup',
|
|
50
|
+
atlassian: 'Atlassian Integration Setup',
|
|
51
|
+
github: 'GitHub CLI Setup',
|
|
52
|
+
granola: 'Granola Integration Setup',
|
|
53
|
+
};
|
|
54
|
+
|
|
47
55
|
// Resolve path to bundled integrations (dist/integrations/ at runtime)
|
|
48
56
|
const __tui_dirname = dirname(fileURLToPath(import.meta.url));
|
|
49
57
|
const INTEGRATIONS_DIR = join(__tui_dirname, '../integrations');
|
|
@@ -181,8 +189,20 @@ function App() {
|
|
|
181
189
|
const integrations = [
|
|
182
190
|
{ id: 'slack', name: 'Slack', connected: !!process.env.SLACK_USER_TOKEN },
|
|
183
191
|
{ id: 'atlassian', name: 'Atlassian', connected: !!getConfigValue('integrations.atlassian.configured') },
|
|
192
|
+
{ id: 'github', name: 'GitHub', connected: !!getConfigValue('integrations.github.configured') },
|
|
193
|
+
{ id: 'granola', name: 'Granola', connected: !!getConfigValue('integrations.granola.configured') },
|
|
184
194
|
];
|
|
185
195
|
|
|
196
|
+
// Detect GitHub CLI auth on first mount (async, caches to config)
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (!getConfigValue('integrations.github.configured')) {
|
|
199
|
+
const isAuthed = checkGhAuth();
|
|
200
|
+
if (isAuthed) {
|
|
201
|
+
setConfigValue('integrations.github.configured', true);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}, []);
|
|
205
|
+
|
|
186
206
|
useInput(
|
|
187
207
|
(input, key) => {
|
|
188
208
|
if (message) setMessage(null);
|
|
@@ -257,6 +277,10 @@ function App() {
|
|
|
257
277
|
];
|
|
258
278
|
} else if (currentIntegration?.id === 'atlassian') {
|
|
259
279
|
intActions = [{ id: 'guide' }];
|
|
280
|
+
} else if (currentIntegration?.id === 'github') {
|
|
281
|
+
intActions = [{ id: 'guide' }];
|
|
282
|
+
} else if (currentIntegration?.id === 'granola') {
|
|
283
|
+
intActions = [{ id: 'guide' }];
|
|
260
284
|
}
|
|
261
285
|
|
|
262
286
|
const maxIntAction = intActions.length - 1;
|
|
@@ -273,9 +297,7 @@ function App() {
|
|
|
273
297
|
exit();
|
|
274
298
|
} else if (actionId === 'guide') {
|
|
275
299
|
const integrationId = currentIntegration?.id ?? 'slack';
|
|
276
|
-
const guideTitle = currentIntegration?.
|
|
277
|
-
? 'Atlassian Integration Setup'
|
|
278
|
-
: 'Slack Integration Setup';
|
|
300
|
+
const guideTitle = INTEGRATION_GUIDE_TITLES[integrationId] ?? `${currentIntegration?.name} Setup`;
|
|
279
301
|
const content = loadIntegrationReference(integrationId, 'setup.md');
|
|
280
302
|
if (content) {
|
|
281
303
|
setPreviousView('detail');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if the GitHub CLI (gh) is installed and authenticated.
|
|
5
|
+
* Returns true if `gh auth status` exits with code 0.
|
|
6
|
+
*/
|
|
7
|
+
export function checkGhAuth(): boolean {
|
|
8
|
+
try {
|
|
9
|
+
const result = spawnSync('gh', ['auth', 'status'], {
|
|
10
|
+
stdio: 'ignore',
|
|
11
|
+
timeout: 5000,
|
|
12
|
+
});
|
|
13
|
+
return result.status === 0;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# GitHub CLI Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The GitHub CLI (`gh`) integration connects droid to GitHub for PR creation, issue management, and repository operations. Several skills depend on it: **codex**, **tech-design**, **code-review**, and **share**.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Install the GitHub CLI
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
brew install gh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Authenticate
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gh auth login
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Follow the interactive prompts to authenticate with your GitHub account. Choose HTTPS as the preferred protocol.
|
|
22
|
+
|
|
23
|
+
### 3. Verify
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gh auth status
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You should see your GitHub username and the scopes available. The TUI Integrations tab will show `GitHub ✓` once detection runs.
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
Unlike Slack (which uses environment variables and OAuth) or Atlassian (which uses MCP), GitHub uses a locally installed CLI binary:
|
|
34
|
+
|
|
35
|
+
| Aspect | Slack | Atlassian | GitHub |
|
|
36
|
+
|--------|-------|-----------|--------|
|
|
37
|
+
| Auth method | OAuth + env vars | MCP server (managed by Claude Code) | `gh auth login` (local CLI) |
|
|
38
|
+
| Setup | `droid integrations setup slack` | `/mcp` in Claude Code | `brew install gh && gh auth login` |
|
|
39
|
+
| API access | `@slack/web-api` SDK | MCP tool calls (`mcp__claude_ai_Atlassian__*`) | `gh` CLI via Bash |
|
|
40
|
+
| Config flag | `integrations.slack.configured` | `integrations.atlassian.configured` | `integrations.github.configured` |
|
|
41
|
+
|
|
42
|
+
The `configured` flag is set automatically when the TUI detects a working `gh auth status` — no manual configuration needed.
|
|
43
|
+
|
|
44
|
+
## Troubleshooting
|
|
45
|
+
|
|
46
|
+
| Issue | Resolution |
|
|
47
|
+
|-------|------------|
|
|
48
|
+
| `gh: command not found` | Install with `brew install gh` |
|
|
49
|
+
| Not authenticated | Run `gh auth login` and follow prompts |
|
|
50
|
+
| Wrong account | Run `gh auth logout` then `gh auth login` |
|
|
51
|
+
| Scopes missing | Run `gh auth refresh -s <scope>` to add scopes |
|
|
52
|
+
| "Not configured" in TUI | Relaunch the TUI — detection runs on startup |
|
|
53
|
+
|
|
54
|
+
## Dependent Skills
|
|
55
|
+
|
|
56
|
+
These skills use `gh` and will fail without it:
|
|
57
|
+
|
|
58
|
+
- **codex** — Fetches PR metadata and diffs for context
|
|
59
|
+
- **tech-design** — Creates PRs for tech design documents
|
|
60
|
+
- **code-review** — Reads PR details, checks, and comments
|
|
61
|
+
- **share** — Creates PRs and interacts with GitHub APIs
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Granola Integration Setup
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Granola integration connects droid to your meeting notes, summaries, and transcripts through the Granola MCP server in Claude Code.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Install Granola
|
|
10
|
+
|
|
11
|
+
Install and sign in to the Granola app first if you have not already.
|
|
12
|
+
|
|
13
|
+
### 2. Add the Granola MCP Server
|
|
14
|
+
|
|
15
|
+
In Claude Code, run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
/mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Select **Granola** and complete the auth flow.
|
|
22
|
+
|
|
23
|
+
### 3. Verify Connection
|
|
24
|
+
|
|
25
|
+
Ask Claude to list recent meetings. If successful, droid can use Granola MCP tools and the Integrations tab should show `Granola ✓`.
|
|
26
|
+
|
|
27
|
+
## Available MCP Tools
|
|
28
|
+
|
|
29
|
+
Depending on your Granola MCP version, tools may include:
|
|
30
|
+
|
|
31
|
+
- `list_meetings`
|
|
32
|
+
- `get_meetings`
|
|
33
|
+
- `get_meeting_transcript`
|
|
34
|
+
- `query_granola_meetings`
|
|
35
|
+
|
|
36
|
+
## How It Works
|
|
37
|
+
|
|
38
|
+
Granola follows the same integration model as Atlassian: MCP is managed by Claude Code, not by a local CLI.
|
|
39
|
+
|
|
40
|
+
| Aspect | Granola |
|
|
41
|
+
|--------|---------|
|
|
42
|
+
| Auth method | MCP server via Claude Code |
|
|
43
|
+
| Setup command | `/mcp` |
|
|
44
|
+
| Config flag | `integrations.granola.configured` |
|
|
45
|
+
|
|
46
|
+
No `droid integrations setup granola` command is required.
|
|
47
|
+
|
|
48
|
+
## Troubleshooting
|
|
49
|
+
|
|
50
|
+
| Issue | Resolution |
|
|
51
|
+
|-------|------------|
|
|
52
|
+
| Granola not shown in `/mcp` | Update Claude Code and try again |
|
|
53
|
+
| Not connected in TUI | Re-run `/mcp` and verify with a meeting query |
|
|
54
|
+
| No meetings returned | Confirm the Granola app has synced meetings |
|
package/src/lib/types.ts
CHANGED
|
@@ -67,9 +67,19 @@ export interface IntegrationAtlassianConfig {
|
|
|
67
67
|
configured?: boolean;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
export interface IntegrationGithubConfig {
|
|
71
|
+
configured?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface IntegrationGranolaConfig {
|
|
75
|
+
configured?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
export interface IntegrationsConfig {
|
|
71
79
|
slack?: IntegrationSlackConfig;
|
|
72
80
|
atlassian?: IntegrationAtlassianConfig;
|
|
81
|
+
github?: IntegrationGithubConfig;
|
|
82
|
+
granola?: IntegrationGranolaConfig;
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
export interface DroidConfig {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-meeting",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Work with meeting notes, summaries, and transcripts. List recent meetings, search content, generate context-aware summaries, export to codex. Backed by Granola MCP.",
|
|
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
|
+
"meeting"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/meeting/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/meeting.md"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: meeting
|
|
2
|
+
description: "Work with meeting notes, summaries, and transcripts. List recent meetings, search content, generate context-aware summaries, export to codex. Backed by Granola MCP."
|
|
3
|
+
version: 0.1.1
|
|
4
|
+
status: beta
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: meeting
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: meeting
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents: []
|
|
14
|
+
|
|
15
|
+
dependencies: []
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meeting
|
|
3
|
+
description: "Work with meeting notes, summaries, and transcripts"
|
|
4
|
+
argument-hint: "[search {query} | summary {title} | summarize {title} | export {title} | decisions | last week]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /meeting
|
|
8
|
+
|
|
9
|
+
**User invoked:** `/meeting $ARGUMENTS`
|
|
10
|
+
|
|
11
|
+
**Your task:** Invoke the **meeting skill** with these arguments.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
- `/meeting` → List recent meetings (this week)
|
|
16
|
+
- `/meeting last week` → List meetings from last week
|
|
17
|
+
- `/meeting search auth decisions` → Search meetings for auth-related decisions
|
|
18
|
+
- `/meeting summary standup` → Quick Granola summary of the standup
|
|
19
|
+
- `/meeting summarize tech design review` → Context-aware summary using transcript
|
|
20
|
+
- `/meeting export partner testing review` → Export meeting to codex
|
|
21
|
+
- `/meeting decisions` → Pull decisions from recent meetings
|
|
22
|
+
|
|
23
|
+
## Quick Reference
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/meeting # List this week's meetings
|
|
27
|
+
/meeting last week # List last week's meetings
|
|
28
|
+
/meeting search {query} # Natural language search
|
|
29
|
+
/meeting summary {title} # Quick summary (Granola)
|
|
30
|
+
/meeting summarize {title} # Deep summary (transcript + context)
|
|
31
|
+
/meeting export {title} # Export to codex
|
|
32
|
+
/meeting decisions # Decisions from recent meetings
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
See the **meeting skill** for complete documentation.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meeting
|
|
3
|
+
description: "Work with meeting notes, summaries, and transcripts. Use when user asks about meetings, wants to review notes, search decisions, or export to codex. User prompts like 'what did we discuss today', 'summarise the tech design review', 'export my meeting with Thea to codex'."
|
|
4
|
+
argument-hint: "[search {query} [--all] | summary {title} | summarize {title} | export {title} | decisions | last week]"
|
|
5
|
+
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash(droid:*)]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Meeting Skill
|
|
9
|
+
|
|
10
|
+
Work with meeting notes, summaries, and transcripts via Granola MCP.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- User asks about what was discussed in a meeting
|
|
15
|
+
- User wants a summary of a specific meeting
|
|
16
|
+
- User wants to search across meeting content for decisions/action items
|
|
17
|
+
- User asks to export meeting notes to codex
|
|
18
|
+
- Natural language like "what did we decide in the partner testing review?", "summarise my meeting with Thea", "what were the action items from today?"
|
|
19
|
+
|
|
20
|
+
## When NOT to Use
|
|
21
|
+
|
|
22
|
+
- Calendar scheduling or upcoming events (this is about past meeting content)
|
|
23
|
+
- User is explicitly asking for Granola app features (this skill does not control Granola)
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
Check if Granola MCP is available. Run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
droid config --get integrations.granola.configured
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If not configured, tell user:
|
|
34
|
+
> "Granola MCP is not connected. Run `/mcp` in Claude Code to add the Granola integration, then try again."
|
|
35
|
+
|
|
36
|
+
If connected, proceed.
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
| Command | Action |
|
|
41
|
+
|---------|--------|
|
|
42
|
+
| `/meeting` | List recent meetings (this week) |
|
|
43
|
+
| `/meeting last week` | List meetings from last week |
|
|
44
|
+
| `/meeting search {query}` | Search meetings (latest match by default; use `--all` for all matches) |
|
|
45
|
+
| `/meeting summary {title}` | Quick summary from Granola (fast, no context cost) |
|
|
46
|
+
| `/meeting summarize {title}` | Context-aware summary using transcript + loaded project/codex context |
|
|
47
|
+
| `/meeting export {title}` | Export meeting to codex |
|
|
48
|
+
| `/meeting decisions` | Pull decisions from recent meetings |
|
|
49
|
+
|
|
50
|
+
Natural language is the primary interface. Users should not need these commands. Recognise meeting intent and route accordingly:
|
|
51
|
+
- "what did Calvin say about Mosaic?" → search
|
|
52
|
+
- "summarise the partner testing review" → summary or summarize
|
|
53
|
+
- "export today's standup to codex" → export
|
|
54
|
+
- "what decisions were made this week?" → decisions
|
|
55
|
+
|
|
56
|
+
## Procedures
|
|
57
|
+
|
|
58
|
+
### List meetings (`/meeting`, `/meeting last week`)
|
|
59
|
+
|
|
60
|
+
1. Use `ToolSearch` to load `mcp__granola__list_meetings`
|
|
61
|
+
2. Call with `time_range`: `"this_week"` (default) or `"last_week"`
|
|
62
|
+
3. Present results as a table: title, date, participants
|
|
63
|
+
|
|
64
|
+
### Search (`/meeting search {query}` / `/meeting search {query} --all`)
|
|
65
|
+
|
|
66
|
+
**Default behaviour: latest match.** Recurring meetings (dailies, weeklies) return multiple hits. Searching all of them is rarely what the user wants — they usually mean the most recent one.
|
|
67
|
+
|
|
68
|
+
1. Use `ToolSearch` to load `mcp__granola__list_meetings` and `mcp__granola__query_granola_meetings`
|
|
69
|
+
2. Call `list_meetings` with `time_range: "this_week"` (or `"last_week"` / `"last_30_days"` if no matches)
|
|
70
|
+
3. Fuzzy-match the query against meeting titles in the list
|
|
71
|
+
4. **If multiple meetings match the query title:**
|
|
72
|
+
- **Without `--all`:** Scope the Granola query to only the **most recent** matching meeting ID (pass `document_ids`). Mention: "Showing results from {date}'s {title}. Use `--all` to search across all {N} matches."
|
|
73
|
+
- **With `--all`:** Query across all matching meeting IDs (pass all IDs in `document_ids`). Mention: "Searching across {N} {title} meetings."
|
|
74
|
+
5. **If only one meeting matches:** Query that single meeting (pass `document_ids`)
|
|
75
|
+
6. **If no title matches but query looks like a topic:** Fall back to a broad Granola query without `document_ids` (original behaviour)
|
|
76
|
+
7. Preserve citation links in the response
|
|
77
|
+
8. Present the response to the user
|
|
78
|
+
|
|
79
|
+
### Quick summary (`/meeting summary {title}`)
|
|
80
|
+
|
|
81
|
+
1. First, list recent meetings to find the meeting ID matching `{title}` (fuzzy match)
|
|
82
|
+
2. Use `ToolSearch` to load `mcp__granola__get_meetings`
|
|
83
|
+
3. Call with the meeting ID
|
|
84
|
+
4. Present the structured summary to the user
|
|
85
|
+
|
|
86
|
+
### Context-aware summary (`/meeting summarize {title}`)
|
|
87
|
+
|
|
88
|
+
1. First, list recent meetings to find the meeting ID matching `{title}` (fuzzy match)
|
|
89
|
+
2. Warn user: "This will load the full transcript into context. The meeting was ~{duration}. Proceed, or use `/meeting summary` for a quicker Granola summary?"
|
|
90
|
+
3. If user confirms, use `ToolSearch` to load `mcp__granola__get_meeting_transcript`
|
|
91
|
+
4. Call with the meeting ID
|
|
92
|
+
5. Generate a summary that incorporates any loaded project context, codex knowledge, or brain docs
|
|
93
|
+
6. Alternative: use the Task tool with a subagent to process the transcript in a separate context window, then return a summary
|
|
94
|
+
|
|
95
|
+
### Decisions (`/meeting decisions`)
|
|
96
|
+
|
|
97
|
+
1. Use `ToolSearch` to load `mcp__granola__query_granola_meetings`
|
|
98
|
+
2. Call with query: "What decisions were made and what are the action items from recent meetings?"
|
|
99
|
+
3. Preserve citation links in the response
|
|
100
|
+
|
|
101
|
+
### Export (`/meeting export {title}`)
|
|
102
|
+
|
|
103
|
+
See `references/export-workflow.md` for the full procedure.
|
|
104
|
+
|
|
105
|
+
## Error Handling
|
|
106
|
+
|
|
107
|
+
| Error | Action |
|
|
108
|
+
|-------|--------|
|
|
109
|
+
| Granola MCP not available | Suggest `/mcp` to connect Granola |
|
|
110
|
+
| No meetings found for time range | Suggest a different time range |
|
|
111
|
+
| Meeting title not found | Show recent meetings, ask user to pick |
|
|
112
|
+
| Transcript too large for context | Offer subagent approach or fall back to Granola summary |
|
|
113
|
+
| Codex not in current context (export) | Prompt for destination (see export workflow) |
|