dear-claude 1.0.1 → 1.1.0
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/README.md +20 -54
- package/dist/index.js +14 -276
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>MCP server that triggers local Claude Code instances from external platforms.</strong><br>
|
|
9
|
-
Say "Dear Claude" in Linear, GitHub, Jira, GitLab, Notion,
|
|
9
|
+
Say "Dear Claude" in Linear, GitHub, Jira, GitLab, Notion, or Obsidian — and a Claude Code instance spins up to handle it.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -36,7 +36,6 @@ Dear Claude is an MCP (Model Context Protocol) server that watches your project
|
|
|
36
36
|
| GitLab | Yes | Yes | Yes | Yes | - | Yes |
|
|
37
37
|
| Notion | Yes | Yes | Yes | - | - | - |
|
|
38
38
|
| Obsidian | Yes | - | Yes | - | - | - |
|
|
39
|
-
| Gmail | Yes | - | Yes | - | - | - |
|
|
40
39
|
|
|
41
40
|
## Cross-Platform Orchestration
|
|
42
41
|
|
|
@@ -49,28 +48,30 @@ Instances from **any** platform get API access to **all** configured platforms.
|
|
|
49
48
|
|
|
50
49
|
## Quick Start
|
|
51
50
|
|
|
51
|
+
### Install in one line
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
claude mcp add dear-claude -- bunx dear-claude start --mcp
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it. Start Claude Code and Dear Claude is ready.
|
|
58
|
+
|
|
52
59
|
### Prerequisites
|
|
53
60
|
|
|
54
|
-
- [Bun](https://bun.sh) runtime
|
|
55
61
|
- [Claude Code](https://claude.ai/claude-code) CLI installed (`claude` command available)
|
|
62
|
+
- [Bun](https://bun.sh) runtime (for `bunx`)
|
|
56
63
|
- [Tailscale](https://tailscale.com/download) with Funnel enabled (for webhooks from external platforms)
|
|
57
64
|
|
|
58
|
-
###
|
|
65
|
+
### Manual setup (alternative)
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
bun install
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 2. Add to Claude Code as MCP Server
|
|
65
|
-
|
|
66
|
-
Add to `~/.claude.json` under `mcpServers`:
|
|
67
|
+
If you prefer manual configuration, add to `~/.claude.json` under `mcpServers`:
|
|
67
68
|
|
|
68
69
|
```json
|
|
69
70
|
{
|
|
70
71
|
"mcpServers": {
|
|
71
72
|
"dear-claude": {
|
|
72
|
-
"command": "
|
|
73
|
-
"args": ["
|
|
73
|
+
"command": "bunx",
|
|
74
|
+
"args": ["dear-claude", "start", "--mcp"],
|
|
74
75
|
"env": {
|
|
75
76
|
"DEAR_CLAUDE_PORT": "3334",
|
|
76
77
|
"GITHUB_CLIENT_ID": "...",
|
|
@@ -85,7 +86,7 @@ Add to `~/.claude.json` under `mcpServers`:
|
|
|
85
86
|
}
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
Then start Claude Code:
|
|
89
90
|
|
|
90
91
|
```bash
|
|
91
92
|
claude
|
|
@@ -312,33 +313,6 @@ Claude's response appears as a callout block appended to the same note. The fron
|
|
|
312
313
|
|
|
313
314
|
---
|
|
314
315
|
|
|
315
|
-
### Gmail
|
|
316
|
-
|
|
317
|
-
1. **Create OAuth credentials** in [Google Cloud Console](https://console.cloud.google.com/apis/credentials):
|
|
318
|
-
- Create a project (or use existing)
|
|
319
|
-
- Enable the **Gmail API**
|
|
320
|
-
- Create **OAuth 2.0 Client ID** (Web application type)
|
|
321
|
-
- Add authorized redirect URI: `https://<your-hostname>.ts.net/dc/oauth/callback/gmail`
|
|
322
|
-
2. Set env vars:
|
|
323
|
-
```bash
|
|
324
|
-
GOOGLE_CLIENT_ID=123456789-abc.apps.googleusercontent.com
|
|
325
|
-
GOOGLE_CLIENT_SECRET=GOCSPX-...
|
|
326
|
-
```
|
|
327
|
-
3. **(Optional) Set up Pub/Sub** for real-time push notifications:
|
|
328
|
-
- Create a Pub/Sub topic in Google Cloud Console
|
|
329
|
-
- Set: `GOOGLE_PUBSUB_TOPIC=projects/your-project/topics/your-topic`
|
|
330
|
-
4. Complete OAuth: visit `https://<your-hostname>.ts.net/dc/setup/gmail`
|
|
331
|
-
|
|
332
|
-
| Environment Variable | Description |
|
|
333
|
-
|---------------------|-------------|
|
|
334
|
-
| `GOOGLE_CLIENT_ID` | OAuth client ID |
|
|
335
|
-
| `GOOGLE_CLIENT_SECRET` | OAuth client secret |
|
|
336
|
-
| `GOOGLE_ACCESS_TOKEN` | Access token (skip OAuth) |
|
|
337
|
-
| `GOOGLE_REFRESH_TOKEN` | Refresh token |
|
|
338
|
-
| `GOOGLE_PUBSUB_TOPIC` | Pub/Sub topic for push notifications |
|
|
339
|
-
|
|
340
|
-
---
|
|
341
|
-
|
|
342
316
|
## Usage
|
|
343
317
|
|
|
344
318
|
### Trigger Format
|
|
@@ -350,7 +324,6 @@ Write **"Dear Claude"** (case-insensitive, with a space) anywhere in:
|
|
|
350
324
|
- GitLab issue/MR descriptions or comments
|
|
351
325
|
- Notion page comments
|
|
352
326
|
- Obsidian `.md` files
|
|
353
|
-
- Gmail emails
|
|
354
327
|
|
|
355
328
|
### Example
|
|
356
329
|
|
|
@@ -427,13 +400,6 @@ NOTION_ACCESS_TOKEN=
|
|
|
427
400
|
OBSIDIAN_VAULT_PATH=
|
|
428
401
|
OBSIDIAN_WATCH_DEBOUNCE_MS=2000
|
|
429
402
|
|
|
430
|
-
# Gmail/Google
|
|
431
|
-
GOOGLE_CLIENT_ID=
|
|
432
|
-
GOOGLE_CLIENT_SECRET=
|
|
433
|
-
GOOGLE_ACCESS_TOKEN=
|
|
434
|
-
GOOGLE_REFRESH_TOKEN=
|
|
435
|
-
GOOGLE_PUBSUB_TOPIC=
|
|
436
|
-
|
|
437
403
|
# Optional
|
|
438
404
|
GIPHY_API_KEY= # For fun GIF reactions in responses
|
|
439
405
|
```
|
|
@@ -505,11 +471,11 @@ The server also exposes REST endpoints on `localhost:3334`:
|
|
|
505
471
|
|
|
506
472
|
```
|
|
507
473
|
Webhooks / File Watcher
|
|
508
|
-
┌────────┐ ┌────────┐ ┌──────┐ ┌────────┐ ┌──────────┐
|
|
509
|
-
│ GitHub │ │ Linear │ │ Jira │ │ GitLab │ │ Obsidian │ │
|
|
510
|
-
└───┬────┘ └───┬────┘ └──┬───┘ └───┬────┘ └────┬─────┘
|
|
511
|
-
│ │ │ │ │
|
|
512
|
-
|
|
474
|
+
┌────────┐ ┌────────┐ ┌──────┐ ┌────────┐ ┌──────────┐ ┌────────┐
|
|
475
|
+
│ GitHub │ │ Linear │ │ Jira │ │ GitLab │ │ Obsidian │ │ Notion │
|
|
476
|
+
└───┬────┘ └───┬────┘ └──┬───┘ └───┬────┘ └────┬─────┘ └───┬────┘
|
|
477
|
+
│ │ │ │ │ │
|
|
478
|
+
└──────────┴────┬────┴─────────┴────────────┴────────────┘
|
|
513
479
|
│
|
|
514
480
|
▼
|
|
515
481
|
┌─────────────────────┐
|
package/dist/index.js
CHANGED
|
@@ -1960,18 +1960,6 @@ ${event.data.description || ""}`,
|
|
|
1960
1960
|
}
|
|
1961
1961
|
return null;
|
|
1962
1962
|
}
|
|
1963
|
-
static parseGmailEvent(message, threadId, isFirstInThread) {
|
|
1964
|
-
return {
|
|
1965
|
-
threadId,
|
|
1966
|
-
platform: "gmail",
|
|
1967
|
-
content: `${message.subject || ""}
|
|
1968
|
-
${message.body || ""}`,
|
|
1969
|
-
isDescription: isFirstInThread,
|
|
1970
|
-
messageId: message.id,
|
|
1971
|
-
authorId: message.from,
|
|
1972
|
-
timestamp: message.timestamp
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
1963
|
static parseGitHubEvent(event) {
|
|
1976
1964
|
if (event.action === "opened" && event.issue) {
|
|
1977
1965
|
return {
|
|
@@ -15246,230 +15234,6 @@ ${issue.description || ""}`,
|
|
|
15246
15234
|
}
|
|
15247
15235
|
var init_linear_adapter = () => {};
|
|
15248
15236
|
|
|
15249
|
-
// src/adapters/gmail-adapter.ts
|
|
15250
|
-
class GmailAdapter {
|
|
15251
|
-
platform = "gmail";
|
|
15252
|
-
config;
|
|
15253
|
-
apiUrl = "https://gmail.googleapis.com/gmail/v1";
|
|
15254
|
-
authUrl = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
15255
|
-
tokenUrl = "https://oauth2.googleapis.com/token";
|
|
15256
|
-
constructor(config) {
|
|
15257
|
-
this.config = config;
|
|
15258
|
-
}
|
|
15259
|
-
isConfigured() {
|
|
15260
|
-
return !!(this.config.accessToken || this.config.clientId && this.config.clientSecret);
|
|
15261
|
-
}
|
|
15262
|
-
async verifySignature(ctx, body) {
|
|
15263
|
-
const authHeader = ctx.req.header("authorization");
|
|
15264
|
-
if (!authHeader?.startsWith("Bearer ")) {
|
|
15265
|
-
console.warn("[GmailAdapter] Missing or invalid authorization header");
|
|
15266
|
-
}
|
|
15267
|
-
return true;
|
|
15268
|
-
}
|
|
15269
|
-
async parseWebhook(ctx, body) {
|
|
15270
|
-
const pubsubMessage = body;
|
|
15271
|
-
if (!pubsubMessage.message?.data) {
|
|
15272
|
-
console.warn("[GmailAdapter] Invalid Pub/Sub message format");
|
|
15273
|
-
return null;
|
|
15274
|
-
}
|
|
15275
|
-
const notificationData = Buffer.from(pubsubMessage.message.data, "base64").toString();
|
|
15276
|
-
const notification = JSON.parse(notificationData);
|
|
15277
|
-
console.log(`[GmailAdapter] Received notification for ${notification.emailAddress}, historyId: ${notification.historyId}`);
|
|
15278
|
-
return {
|
|
15279
|
-
platform: "gmail",
|
|
15280
|
-
threadId: notification.historyId,
|
|
15281
|
-
content: "",
|
|
15282
|
-
isDescription: true,
|
|
15283
|
-
raw: notification
|
|
15284
|
-
};
|
|
15285
|
-
}
|
|
15286
|
-
async processNotification(historyId, startHistoryId) {
|
|
15287
|
-
if (!this.config.accessToken) {
|
|
15288
|
-
throw new Error("Gmail access token not configured");
|
|
15289
|
-
}
|
|
15290
|
-
const events = [];
|
|
15291
|
-
const historyUrl = `${this.apiUrl}/users/me/history?startHistoryId=${startHistoryId || historyId}&historyTypes=messageAdded`;
|
|
15292
|
-
const historyResponse = await fetch(historyUrl, {
|
|
15293
|
-
headers: {
|
|
15294
|
-
Authorization: `Bearer ${this.config.accessToken}`
|
|
15295
|
-
}
|
|
15296
|
-
});
|
|
15297
|
-
if (!historyResponse.ok) {
|
|
15298
|
-
const error = await historyResponse.text();
|
|
15299
|
-
throw new Error(`Failed to fetch history: ${error}`);
|
|
15300
|
-
}
|
|
15301
|
-
const historyData = await historyResponse.json();
|
|
15302
|
-
if (!historyData.history) {
|
|
15303
|
-
return events;
|
|
15304
|
-
}
|
|
15305
|
-
for (const historyItem of historyData.history) {
|
|
15306
|
-
if (!historyItem.messagesAdded)
|
|
15307
|
-
continue;
|
|
15308
|
-
for (const added of historyItem.messagesAdded) {
|
|
15309
|
-
const message = await this.getMessage(added.message.id);
|
|
15310
|
-
if (!message)
|
|
15311
|
-
continue;
|
|
15312
|
-
const threadMessages = await this.getThreadMessages(message.threadId);
|
|
15313
|
-
const isFirstInThread = threadMessages.length === 1;
|
|
15314
|
-
const content = this.extractMessageContent(message);
|
|
15315
|
-
const subject = this.getHeader(message, "Subject") || "";
|
|
15316
|
-
const from = this.getHeader(message, "From") || "";
|
|
15317
|
-
events.push({
|
|
15318
|
-
platform: "gmail",
|
|
15319
|
-
threadId: message.threadId,
|
|
15320
|
-
content: `${subject}
|
|
15321
|
-
${content}`,
|
|
15322
|
-
isDescription: isFirstInThread,
|
|
15323
|
-
messageId: message.id,
|
|
15324
|
-
authorId: from,
|
|
15325
|
-
raw: message
|
|
15326
|
-
});
|
|
15327
|
-
}
|
|
15328
|
-
}
|
|
15329
|
-
return events;
|
|
15330
|
-
}
|
|
15331
|
-
async getMessage(messageId) {
|
|
15332
|
-
const response = await fetch(`${this.apiUrl}/users/me/messages/${messageId}?format=full`, {
|
|
15333
|
-
headers: {
|
|
15334
|
-
Authorization: `Bearer ${this.config.accessToken}`
|
|
15335
|
-
}
|
|
15336
|
-
});
|
|
15337
|
-
if (!response.ok) {
|
|
15338
|
-
console.error(`[GmailAdapter] Failed to fetch message ${messageId}`);
|
|
15339
|
-
return null;
|
|
15340
|
-
}
|
|
15341
|
-
return response.json();
|
|
15342
|
-
}
|
|
15343
|
-
async getThreadMessages(threadId) {
|
|
15344
|
-
const response = await fetch(`${this.apiUrl}/users/me/threads/${threadId}?format=metadata`, {
|
|
15345
|
-
headers: {
|
|
15346
|
-
Authorization: `Bearer ${this.config.accessToken}`
|
|
15347
|
-
}
|
|
15348
|
-
});
|
|
15349
|
-
if (!response.ok) {
|
|
15350
|
-
return [];
|
|
15351
|
-
}
|
|
15352
|
-
const thread = await response.json();
|
|
15353
|
-
return thread.messages || [];
|
|
15354
|
-
}
|
|
15355
|
-
getHeader(message, name) {
|
|
15356
|
-
return message.payload.headers.find((h3) => h3.name.toLowerCase() === name.toLowerCase())?.value;
|
|
15357
|
-
}
|
|
15358
|
-
extractMessageContent(message) {
|
|
15359
|
-
if (message.payload.body?.data) {
|
|
15360
|
-
return Buffer.from(message.payload.body.data, "base64").toString();
|
|
15361
|
-
}
|
|
15362
|
-
if (message.payload.parts) {
|
|
15363
|
-
for (const part of message.payload.parts) {
|
|
15364
|
-
if (part.mimeType === "text/plain" && part.body?.data) {
|
|
15365
|
-
return Buffer.from(part.body.data, "base64").toString();
|
|
15366
|
-
}
|
|
15367
|
-
}
|
|
15368
|
-
}
|
|
15369
|
-
return message.snippet || "";
|
|
15370
|
-
}
|
|
15371
|
-
async postResponse(threadId, message) {
|
|
15372
|
-
if (!this.config.accessToken) {
|
|
15373
|
-
throw new Error("Gmail access token not configured");
|
|
15374
|
-
}
|
|
15375
|
-
const threadMessages = await this.getThreadMessages(threadId);
|
|
15376
|
-
if (threadMessages.length === 0) {
|
|
15377
|
-
throw new Error(`Thread ${threadId} not found`);
|
|
15378
|
-
}
|
|
15379
|
-
const originalMessage = threadMessages[threadMessages.length - 1];
|
|
15380
|
-
const originalMessageId = this.getHeader(originalMessage, "Message-ID");
|
|
15381
|
-
const subject = this.getHeader(originalMessage, "Subject") || "";
|
|
15382
|
-
const to = this.getHeader(originalMessage, "From") || "";
|
|
15383
|
-
const replySubject = subject.startsWith("Re:") ? subject : `Re: ${subject}`;
|
|
15384
|
-
const email = [
|
|
15385
|
-
`To: ${to}`,
|
|
15386
|
-
`Subject: ${replySubject}`,
|
|
15387
|
-
`In-Reply-To: ${originalMessageId}`,
|
|
15388
|
-
`References: ${originalMessageId}`,
|
|
15389
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
15390
|
-
"",
|
|
15391
|
-
message
|
|
15392
|
-
].join(`\r
|
|
15393
|
-
`);
|
|
15394
|
-
const encodedEmail = Buffer.from(email).toString("base64url");
|
|
15395
|
-
const response = await fetch(`${this.apiUrl}/users/me/messages/send`, {
|
|
15396
|
-
method: "POST",
|
|
15397
|
-
headers: {
|
|
15398
|
-
Authorization: `Bearer ${this.config.accessToken}`,
|
|
15399
|
-
"Content-Type": "application/json"
|
|
15400
|
-
},
|
|
15401
|
-
body: JSON.stringify({
|
|
15402
|
-
raw: encodedEmail,
|
|
15403
|
-
threadId
|
|
15404
|
-
})
|
|
15405
|
-
});
|
|
15406
|
-
if (!response.ok) {
|
|
15407
|
-
const error = await response.text();
|
|
15408
|
-
throw new Error(`Failed to send reply: ${error}`);
|
|
15409
|
-
}
|
|
15410
|
-
console.log(`[GmailAdapter] Sent reply to thread ${threadId}`);
|
|
15411
|
-
}
|
|
15412
|
-
getAuthUrl(redirectUri, state) {
|
|
15413
|
-
const params = new URLSearchParams({
|
|
15414
|
-
client_id: this.config.clientId,
|
|
15415
|
-
redirect_uri: redirectUri,
|
|
15416
|
-
response_type: "code",
|
|
15417
|
-
scope: "https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/gmail.modify",
|
|
15418
|
-
state,
|
|
15419
|
-
access_type: "offline",
|
|
15420
|
-
prompt: "consent"
|
|
15421
|
-
});
|
|
15422
|
-
return `${this.authUrl}?${params.toString()}`;
|
|
15423
|
-
}
|
|
15424
|
-
async handleCallback(code, redirectUri) {
|
|
15425
|
-
const response = await fetch(this.tokenUrl, {
|
|
15426
|
-
method: "POST",
|
|
15427
|
-
headers: {
|
|
15428
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
15429
|
-
},
|
|
15430
|
-
body: new URLSearchParams({
|
|
15431
|
-
grant_type: "authorization_code",
|
|
15432
|
-
client_id: this.config.clientId,
|
|
15433
|
-
client_secret: this.config.clientSecret,
|
|
15434
|
-
redirect_uri: redirectUri,
|
|
15435
|
-
code
|
|
15436
|
-
})
|
|
15437
|
-
});
|
|
15438
|
-
if (!response.ok) {
|
|
15439
|
-
const error = await response.text();
|
|
15440
|
-
throw new Error(`Failed to exchange code: ${error}`);
|
|
15441
|
-
}
|
|
15442
|
-
const data = await response.json();
|
|
15443
|
-
return {
|
|
15444
|
-
accessToken: data.access_token,
|
|
15445
|
-
refreshToken: data.refresh_token
|
|
15446
|
-
};
|
|
15447
|
-
}
|
|
15448
|
-
async setupPushNotifications(topicName) {
|
|
15449
|
-
if (!this.config.accessToken) {
|
|
15450
|
-
throw new Error("Gmail access token not configured");
|
|
15451
|
-
}
|
|
15452
|
-
const response = await fetch(`${this.apiUrl}/users/me/watch`, {
|
|
15453
|
-
method: "POST",
|
|
15454
|
-
headers: {
|
|
15455
|
-
Authorization: `Bearer ${this.config.accessToken}`,
|
|
15456
|
-
"Content-Type": "application/json"
|
|
15457
|
-
},
|
|
15458
|
-
body: JSON.stringify({
|
|
15459
|
-
topicName,
|
|
15460
|
-
labelIds: ["INBOX"]
|
|
15461
|
-
})
|
|
15462
|
-
});
|
|
15463
|
-
if (!response.ok) {
|
|
15464
|
-
const error = await response.text();
|
|
15465
|
-
throw new Error(`Failed to setup watch: ${error}`);
|
|
15466
|
-
}
|
|
15467
|
-
const data = await response.json();
|
|
15468
|
-
console.log(`[GmailAdapter] Watch setup, historyId: ${data.historyId}, expires: ${data.expiration}`);
|
|
15469
|
-
return data;
|
|
15470
|
-
}
|
|
15471
|
-
}
|
|
15472
|
-
|
|
15473
15237
|
// src/adapters/github-adapter.ts
|
|
15474
15238
|
import { createHmac as createHmac2, createSign, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
15475
15239
|
import { readFileSync as readFileSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
@@ -16930,9 +16694,6 @@ function createServer(config, db, instanceManager, executor, obsidianWatcher) {
|
|
|
16930
16694
|
if (config.linear) {
|
|
16931
16695
|
adapters.set("linear", new LinearAdapter(config.linear));
|
|
16932
16696
|
}
|
|
16933
|
-
if (config.gmail) {
|
|
16934
|
-
adapters.set("gmail", new GmailAdapter(config.gmail));
|
|
16935
|
-
}
|
|
16936
16697
|
if (config.github) {
|
|
16937
16698
|
adapters.set("github", new GitHubAdapter(config.github));
|
|
16938
16699
|
}
|
|
@@ -16958,7 +16719,6 @@ function createServer(config, db, instanceManager, executor, obsidianWatcher) {
|
|
|
16958
16719
|
webhooks: publicUrl ? {
|
|
16959
16720
|
github: `${publicUrl}/webhook/github`,
|
|
16960
16721
|
linear: `${publicUrl}/webhook/linear`,
|
|
16961
|
-
gmail: `${publicUrl}/webhook/gmail`,
|
|
16962
16722
|
gitlab: `${publicUrl}/webhook/gitlab`,
|
|
16963
16723
|
jira: `${publicUrl}/webhook/jira`,
|
|
16964
16724
|
notion: `${publicUrl}/webhook/notion`
|
|
@@ -16966,12 +16726,10 @@ function createServer(config, db, instanceManager, executor, obsidianWatcher) {
|
|
|
16966
16726
|
oauth: publicUrl ? {
|
|
16967
16727
|
github: `${publicUrl}/setup/github`,
|
|
16968
16728
|
linear: `${publicUrl}/setup/linear`,
|
|
16969
|
-
gmail: `${publicUrl}/setup/gmail`,
|
|
16970
16729
|
notion: `${publicUrl}/setup/notion`
|
|
16971
16730
|
} : null,
|
|
16972
16731
|
platforms: {
|
|
16973
16732
|
linear: adapters.has("linear"),
|
|
16974
|
-
gmail: adapters.has("gmail"),
|
|
16975
16733
|
github: adapters.has("github"),
|
|
16976
16734
|
gitlab: adapters.has("gitlab"),
|
|
16977
16735
|
jira: adapters.has("jira"),
|
|
@@ -16980,8 +16738,7 @@ function createServer(config, db, instanceManager, executor, obsidianWatcher) {
|
|
|
16980
16738
|
},
|
|
16981
16739
|
authenticatedUsers: {
|
|
16982
16740
|
github: db.getPlatformUsername("github") || null,
|
|
16983
|
-
linear: db.getPlatformUsername("linear") || null
|
|
16984
|
-
google: db.getPlatformUsername("google") || null
|
|
16741
|
+
linear: db.getPlatformUsername("linear") || null
|
|
16985
16742
|
}
|
|
16986
16743
|
});
|
|
16987
16744
|
});
|
|
@@ -17200,10 +16957,9 @@ ${safeError}`, installationId);
|
|
|
17200
16957
|
try {
|
|
17201
16958
|
const redirectUri = `${config.publicUrl}/oauth/callback/${platform}`;
|
|
17202
16959
|
const tokens = await adapter.handleCallback(code, redirectUri);
|
|
17203
|
-
const oauthProvider = platform === "gmail" ? "google" : platform;
|
|
17204
16960
|
db.saveOAuthToken({
|
|
17205
16961
|
id: crypto.randomUUID(),
|
|
17206
|
-
provider:
|
|
16962
|
+
provider: platform,
|
|
17207
16963
|
user_id: "default",
|
|
17208
16964
|
access_token: tokens.accessToken,
|
|
17209
16965
|
refresh_token: tokens.refreshToken,
|
|
@@ -24082,7 +23838,7 @@ var SCHEMA = `
|
|
|
24082
23838
|
CREATE TABLE IF NOT EXISTS instances (
|
|
24083
23839
|
id TEXT PRIMARY KEY,
|
|
24084
23840
|
thread_id TEXT NOT NULL,
|
|
24085
|
-
platform TEXT NOT NULL CHECK (platform IN ('linear', '
|
|
23841
|
+
platform TEXT NOT NULL CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24086
23842
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed', 'idle', 'expired')),
|
|
24087
23843
|
working_dir TEXT NOT NULL,
|
|
24088
23844
|
original_prompt TEXT NOT NULL,
|
|
@@ -24112,7 +23868,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_instance ON messages (instance_id);
|
|
|
24112
23868
|
-- OAuth tokens table
|
|
24113
23869
|
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
|
24114
23870
|
id TEXT PRIMARY KEY,
|
|
24115
|
-
provider TEXT NOT NULL CHECK (provider IN ('linear', '
|
|
23871
|
+
provider TEXT NOT NULL CHECK (provider IN ('linear', 'github', 'gitlab', 'jira', 'notion')),
|
|
24116
23872
|
user_id TEXT NOT NULL,
|
|
24117
23873
|
access_token TEXT NOT NULL,
|
|
24118
23874
|
refresh_token TEXT,
|
|
@@ -24128,7 +23884,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_provider_user ON oauth_tokens (provi
|
|
|
24128
23884
|
-- Webhook configurations table
|
|
24129
23885
|
CREATE TABLE IF NOT EXISTS webhook_configs (
|
|
24130
23886
|
id TEXT PRIMARY KEY,
|
|
24131
|
-
platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', '
|
|
23887
|
+
platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24132
23888
|
webhook_id TEXT,
|
|
24133
23889
|
webhook_secret TEXT,
|
|
24134
23890
|
subscription_id TEXT,
|
|
@@ -24203,7 +23959,7 @@ class DatabaseManager {
|
|
|
24203
23959
|
this.db.exec("ALTER TABLE instances RENAME TO instances_old");
|
|
24204
23960
|
this.db.exec(`CREATE TABLE instances (
|
|
24205
23961
|
id TEXT PRIMARY KEY, thread_id TEXT NOT NULL,
|
|
24206
|
-
platform TEXT NOT NULL CHECK (platform IN ('linear', '
|
|
23962
|
+
platform TEXT NOT NULL CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24207
23963
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed', 'idle', 'expired')),
|
|
24208
23964
|
working_dir TEXT NOT NULL, original_prompt TEXT NOT NULL, completion_summary TEXT,
|
|
24209
23965
|
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, expires_at INTEGER NOT NULL
|
|
@@ -24225,7 +23981,7 @@ class DatabaseManager {
|
|
|
24225
23981
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_instance ON messages (instance_id)");
|
|
24226
23982
|
this.db.exec("ALTER TABLE oauth_tokens RENAME TO oauth_tokens_old");
|
|
24227
23983
|
this.db.exec(`CREATE TABLE oauth_tokens (
|
|
24228
|
-
id TEXT PRIMARY KEY, provider TEXT NOT NULL CHECK (provider IN ('linear', '
|
|
23984
|
+
id TEXT PRIMARY KEY, provider TEXT NOT NULL CHECK (provider IN ('linear', 'github', 'gitlab', 'jira', 'notion')),
|
|
24229
23985
|
user_id TEXT NOT NULL, access_token TEXT NOT NULL, refresh_token TEXT, expires_at INTEGER,
|
|
24230
23986
|
scope TEXT NOT NULL, platform_username TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
24231
23987
|
)`);
|
|
@@ -24234,7 +23990,7 @@ class DatabaseManager {
|
|
|
24234
23990
|
this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_provider_user ON oauth_tokens (provider, user_id)");
|
|
24235
23991
|
this.db.exec("ALTER TABLE webhook_configs RENAME TO webhook_configs_old");
|
|
24236
23992
|
this.db.exec(`CREATE TABLE webhook_configs (
|
|
24237
|
-
id TEXT PRIMARY KEY, platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', '
|
|
23993
|
+
id TEXT PRIMARY KEY, platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24238
23994
|
webhook_id TEXT, webhook_secret TEXT, subscription_id TEXT, created_at INTEGER NOT NULL
|
|
24239
23995
|
)`);
|
|
24240
23996
|
this.db.exec("INSERT INTO webhook_configs SELECT * FROM webhook_configs_old");
|
|
@@ -24256,7 +24012,7 @@ class DatabaseManager {
|
|
|
24256
24012
|
this.db.exec("ALTER TABLE instances RENAME TO instances_old");
|
|
24257
24013
|
this.db.exec(`CREATE TABLE instances (
|
|
24258
24014
|
id TEXT PRIMARY KEY, thread_id TEXT NOT NULL,
|
|
24259
|
-
platform TEXT NOT NULL CHECK (platform IN ('linear', '
|
|
24015
|
+
platform TEXT NOT NULL CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24260
24016
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'completed', 'failed', 'idle', 'expired')),
|
|
24261
24017
|
working_dir TEXT NOT NULL, original_prompt TEXT NOT NULL, completion_summary TEXT,
|
|
24262
24018
|
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, expires_at INTEGER NOT NULL
|
|
@@ -24278,7 +24034,7 @@ class DatabaseManager {
|
|
|
24278
24034
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_instance ON messages (instance_id)");
|
|
24279
24035
|
this.db.exec("ALTER TABLE oauth_tokens RENAME TO oauth_tokens_old");
|
|
24280
24036
|
this.db.exec(`CREATE TABLE oauth_tokens (
|
|
24281
|
-
id TEXT PRIMARY KEY, provider TEXT NOT NULL CHECK (provider IN ('linear', '
|
|
24037
|
+
id TEXT PRIMARY KEY, provider TEXT NOT NULL CHECK (provider IN ('linear', 'github', 'gitlab', 'jira', 'notion')),
|
|
24282
24038
|
user_id TEXT NOT NULL, access_token TEXT NOT NULL, refresh_token TEXT, expires_at INTEGER,
|
|
24283
24039
|
scope TEXT NOT NULL, platform_username TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
24284
24040
|
)`);
|
|
@@ -24287,7 +24043,7 @@ class DatabaseManager {
|
|
|
24287
24043
|
this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_provider_user ON oauth_tokens (provider, user_id)");
|
|
24288
24044
|
this.db.exec("ALTER TABLE webhook_configs RENAME TO webhook_configs_old");
|
|
24289
24045
|
this.db.exec(`CREATE TABLE webhook_configs (
|
|
24290
|
-
id TEXT PRIMARY KEY, platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', '
|
|
24046
|
+
id TEXT PRIMARY KEY, platform TEXT NOT NULL UNIQUE CHECK (platform IN ('linear', 'github', 'gitlab', 'jira', 'notion', 'obsidian')),
|
|
24291
24047
|
webhook_id TEXT, webhook_secret TEXT, subscription_id TEXT, created_at INTEGER NOT NULL
|
|
24292
24048
|
)`);
|
|
24293
24049
|
this.db.exec("INSERT INTO webhook_configs SELECT * FROM webhook_configs_old");
|
|
@@ -31894,7 +31650,7 @@ function createMCPServer(instanceManager, executor, config2, options) {
|
|
|
31894
31650
|
platform: {
|
|
31895
31651
|
type: "string",
|
|
31896
31652
|
description: "Target platform (default: github)",
|
|
31897
|
-
enum: ["linear", "
|
|
31653
|
+
enum: ["linear", "github", "gitlab", "jira", "notion", "obsidian"]
|
|
31898
31654
|
},
|
|
31899
31655
|
repo_url: {
|
|
31900
31656
|
type: "string",
|
|
@@ -31948,7 +31704,6 @@ function createMCPServer(instanceManager, executor, config2, options) {
|
|
|
31948
31704
|
case "list_platforms": {
|
|
31949
31705
|
const platforms = {
|
|
31950
31706
|
linear: !!(config2?.linear?.clientId || config2?.linear?.accessToken),
|
|
31951
|
-
gmail: !!config2?.gmail?.clientId,
|
|
31952
31707
|
github: !!(config2?.github?.clientId || config2?.github?.accessToken),
|
|
31953
31708
|
gitlab: !!config2?.gitlab?.accessToken,
|
|
31954
31709
|
jira: !!(config2?.jira?.domain && config2?.jira?.apiToken),
|
|
@@ -32267,14 +32022,12 @@ async function startMCPServer(instanceManager, executor, options) {
|
|
|
32267
32022
|
console.error(`[MCP] Webhook URLs (public):`);
|
|
32268
32023
|
console.error(` GitHub: ${publicUrl}/webhook/github`);
|
|
32269
32024
|
console.error(` Linear: ${publicUrl}/webhook/linear`);
|
|
32270
|
-
console.error(` Gmail: ${publicUrl}/webhook/gmail`);
|
|
32271
32025
|
console.error(`[MCP] OAuth Setup:`);
|
|
32272
32026
|
console.error(` GitHub: ${publicUrl}/setup/github`);
|
|
32273
32027
|
} else {
|
|
32274
32028
|
console.error(`[MCP] Webhook URLs (local only):`);
|
|
32275
32029
|
console.error(` GitHub: http://localhost:${httpPort}/webhook/github`);
|
|
32276
32030
|
console.error(` Linear: http://localhost:${httpPort}/webhook/linear`);
|
|
32277
|
-
console.error(` Gmail: http://localhost:${httpPort}/webhook/gmail`);
|
|
32278
32031
|
}
|
|
32279
32032
|
}
|
|
32280
32033
|
if (options?.config?.obsidian?.vaultPath && options.db) {
|
|
@@ -32373,13 +32126,6 @@ function getConfig() {
|
|
|
32373
32126
|
webhookSecret: process.env.LINEAR_WEBHOOK_SECRET,
|
|
32374
32127
|
accessToken: process.env.LINEAR_ACCESS_TOKEN
|
|
32375
32128
|
},
|
|
32376
|
-
gmail: {
|
|
32377
|
-
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
32378
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
32379
|
-
accessToken: process.env.GOOGLE_ACCESS_TOKEN,
|
|
32380
|
-
refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
|
|
32381
|
-
pubsubTopic: process.env.GOOGLE_PUBSUB_TOPIC
|
|
32382
|
-
},
|
|
32383
32129
|
github: {
|
|
32384
32130
|
clientId: process.env.GITHUB_CLIENT_ID,
|
|
32385
32131
|
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
@@ -32527,7 +32273,6 @@ ${safeError}`);
|
|
|
32527
32273
|
console.log(`
|
|
32528
32274
|
\uD83D\uDCCD Webhook URLs:`);
|
|
32529
32275
|
console.log(` Linear: ${config2.publicUrl}/webhook/linear`);
|
|
32530
|
-
console.log(` Gmail: ${config2.publicUrl}/webhook/gmail`);
|
|
32531
32276
|
console.log(` GitHub: ${config2.publicUrl}/webhook/github`);
|
|
32532
32277
|
console.log(` GitLab: ${config2.publicUrl}/webhook/gitlab`);
|
|
32533
32278
|
console.log(` Jira: ${config2.publicUrl}/webhook/jira`);
|
|
@@ -32535,7 +32280,6 @@ ${safeError}`);
|
|
|
32535
32280
|
console.log(`
|
|
32536
32281
|
\uD83D\uDD10 OAuth Setup:`);
|
|
32537
32282
|
console.log(` Linear: ${config2.publicUrl}/setup/linear`);
|
|
32538
|
-
console.log(` Gmail: ${config2.publicUrl}/setup/gmail`);
|
|
32539
32283
|
console.log(` GitHub: ${config2.publicUrl}/setup/github`);
|
|
32540
32284
|
console.log(` Notion: ${config2.publicUrl}/setup/notion`);
|
|
32541
32285
|
}
|
|
@@ -32567,7 +32311,6 @@ Shutting down...`);
|
|
|
32567
32311
|
`);
|
|
32568
32312
|
console.log("Platforms:");
|
|
32569
32313
|
console.log(` Linear: ${config2.linear?.accessToken ? "\u2705 Connected" : config2.linear?.clientId ? "\u26A0\uFE0F OAuth configured" : "\u274C Not configured"}`);
|
|
32570
|
-
console.log(` Gmail: ${config2.gmail?.accessToken ? "\u2705 Connected" : config2.gmail?.clientId ? "\u26A0\uFE0F OAuth configured" : "\u274C Not configured"}`);
|
|
32571
32314
|
console.log(` GitHub: ${config2.github?.accessToken ? "\u2705 Connected" : config2.github?.clientId ? "\u26A0\uFE0F OAuth configured" : "\u274C Not configured"}`);
|
|
32572
32315
|
console.log(` GitLab: ${config2.gitlab?.accessToken ? "\u2705 Connected" : "\u274C Not configured"}`);
|
|
32573
32316
|
console.log(` Jira: ${config2.jira?.apiToken ? "\u2705 Connected" : config2.jira?.domain ? "\u26A0\uFE0F Domain configured" : "\u274C Not configured"}`);
|
|
@@ -32648,8 +32391,8 @@ Database: \u274C Error - ${err}`);
|
|
|
32648
32391
|
}
|
|
32649
32392
|
db.close();
|
|
32650
32393
|
});
|
|
32651
|
-
program2.command("setup <platform>").description("Configure a platform (linear,
|
|
32652
|
-
const validPlatforms = ["linear", "
|
|
32394
|
+
program2.command("setup <platform>").description("Configure a platform (linear, github, jira, notion, obsidian)").action(async (platform) => {
|
|
32395
|
+
const validPlatforms = ["linear", "github", "jira", "notion", "obsidian"];
|
|
32653
32396
|
if (!validPlatforms.includes(platform)) {
|
|
32654
32397
|
console.error(`Invalid platform: ${platform}`);
|
|
32655
32398
|
console.log(`Valid platforms: ${validPlatforms.join(", ")}`);
|
|
@@ -32722,11 +32465,6 @@ Once configured, run 'dear-claude start' and Obsidian watcher will be active.`);
|
|
|
32722
32465
|
console.log(" LINEAR_CLIENT_SECRET");
|
|
32723
32466
|
console.log(`
|
|
32724
32467
|
(Get these from Linear Settings \u2192 API \u2192 OAuth Applications)`);
|
|
32725
|
-
} else if (platform === "gmail") {
|
|
32726
|
-
console.log(" GOOGLE_CLIENT_ID");
|
|
32727
|
-
console.log(" GOOGLE_CLIENT_SECRET");
|
|
32728
|
-
console.log(`
|
|
32729
|
-
(Get these from Google Cloud Console \u2192 APIs & Services \u2192 Credentials)`);
|
|
32730
32468
|
} else if (platform === "github") {
|
|
32731
32469
|
console.log(" GITHUB_CLIENT_ID");
|
|
32732
32470
|
console.log(" GITHUB_CLIENT_SECRET");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dear-claude",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"mcpName": "io.github.sns45/dear-claude",
|
|
5
|
-
"description": "MCP server that triggers local Claude Code instances from external platforms (GitHub, Linear, Jira, GitLab, Notion, Obsidian
|
|
5
|
+
"description": "MCP server that triggers local Claude Code instances from external platforms (GitHub, Linear, Jira, GitLab, Notion, Obsidian) when 'Dear Claude' is mentioned",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@anthropic-ai/claude-agent-sdk": "^0.2.25",
|
|
25
|
-
"@googleapis/gmail": "^2.0.0",
|
|
26
25
|
"@linear/sdk": "^29.0.0",
|
|
27
26
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
27
|
"@octokit/rest": "^20.0.0",
|
|
@@ -37,7 +36,6 @@
|
|
|
37
36
|
"claude-code",
|
|
38
37
|
"mcp",
|
|
39
38
|
"linear",
|
|
40
|
-
"gmail",
|
|
41
39
|
"github",
|
|
42
40
|
"gitlab",
|
|
43
41
|
"jira",
|