@dayby/mcp-server 0.1.0 → 0.3.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/README.md +58 -7
- package/dist/auth.d.ts +16 -0
- package/dist/auth.js +142 -0
- package/dist/auth.test.d.ts +1 -0
- package/dist/auth.test.js +105 -0
- package/dist/dayby-client.d.ts +5 -1
- package/dist/dayby-client.js +9 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +133 -11
- package/dist/sanitizer.test.d.ts +1 -0
- package/dist/sanitizer.test.js +87 -0
- package/dist/visibility.d.ts +8 -0
- package/dist/visibility.js +16 -0
- package/dist/visibility.test.d.ts +1 -0
- package/dist/visibility.test.js +36 -0
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -5,11 +5,10 @@ Post your dev progress to [DayBy.dev](https://dayby.dev) from Claude, Cursor, or
|
|
|
5
5
|
## How It Works
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
publish_post → DayBy API (only sanitized content sent)
|
|
8
|
+
draft_post → sanitized locally, never touches network
|
|
9
|
+
→ Claude shows you a clean preview
|
|
10
|
+
→ you approve
|
|
11
|
+
→ publish_post → DayBy API (sanitized content only)
|
|
13
12
|
```
|
|
14
13
|
|
|
15
14
|
**The raw context from your codebase never touches the network.** Only the sanitized, approved version gets published.
|
|
@@ -23,6 +22,9 @@ You're coding with Claude → learn something cool →
|
|
|
23
22
|
| `check_content` | Dry-run: see what would get stripped | ❌ No |
|
|
24
23
|
| `publish_post` | Publish an approved draft to DayBy | ✅ Yes (sanitized only) |
|
|
25
24
|
| `list_posts` | List your recent DayBy posts | ✅ Yes |
|
|
25
|
+
| `get_post` | Fetch a single post by slug | ✅ Yes |
|
|
26
|
+
| `update_post` | Update title, content, or visibility | ✅ Yes |
|
|
27
|
+
| `delete_post` | Permanently delete a post | ✅ Yes |
|
|
26
28
|
|
|
27
29
|
## What Gets Stripped (Automatically)
|
|
28
30
|
|
|
@@ -58,23 +60,48 @@ Create `~/.dayby/sanitizer.json`:
|
|
|
58
60
|
|
|
59
61
|
### 3. Install
|
|
60
62
|
|
|
63
|
+
**Option A — npx (no install needed):**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx @dayby/mcp-server
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Option B — global install:**
|
|
70
|
+
|
|
61
71
|
```bash
|
|
62
72
|
npm install -g @dayby/mcp-server
|
|
63
73
|
```
|
|
64
74
|
|
|
75
|
+
**Option C — from source:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/ja-roque/dayby-mcp-server.git
|
|
79
|
+
cd dayby-mcp-server
|
|
80
|
+
npm install && npm run build
|
|
81
|
+
```
|
|
82
|
+
|
|
65
83
|
### 4. Add to Claude Code / Claude Desktop / Cursor
|
|
66
84
|
|
|
67
85
|
**Claude Code (simplest):**
|
|
86
|
+
|
|
68
87
|
```bash
|
|
69
88
|
claude mcp add dayby -- dayby-mcp
|
|
70
89
|
```
|
|
71
90
|
|
|
91
|
+
Or with npx:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
claude mcp add dayby -- npx @dayby/mcp-server
|
|
95
|
+
```
|
|
96
|
+
|
|
72
97
|
**Claude Desktop** (`claude_desktop_config.json`):
|
|
98
|
+
|
|
73
99
|
```json
|
|
74
100
|
{
|
|
75
101
|
"mcpServers": {
|
|
76
102
|
"dayby": {
|
|
77
|
-
"command": "
|
|
103
|
+
"command": "npx",
|
|
104
|
+
"args": ["@dayby/mcp-server"],
|
|
78
105
|
"env": {
|
|
79
106
|
"DAYBY_API_KEY": "your-api-key-here"
|
|
80
107
|
}
|
|
@@ -84,11 +111,13 @@ claude mcp add dayby -- dayby-mcp
|
|
|
84
111
|
```
|
|
85
112
|
|
|
86
113
|
**Cursor** (`.cursor/mcp.json`):
|
|
114
|
+
|
|
87
115
|
```json
|
|
88
116
|
{
|
|
89
117
|
"mcpServers": {
|
|
90
118
|
"dayby": {
|
|
91
|
-
"command": "
|
|
119
|
+
"command": "npx",
|
|
120
|
+
"args": ["@dayby/mcp-server"],
|
|
92
121
|
"env": {
|
|
93
122
|
"DAYBY_API_KEY": "your-api-key-here"
|
|
94
123
|
}
|
|
@@ -119,6 +148,28 @@ Claude will use `draft_post` to sanitize locally, show you a preview, and only p
|
|
|
119
148
|
| `DAYBY_BLOCKED_TERMS` | Comma-separated blocked terms | (none) |
|
|
120
149
|
| `DAYBY_BLOCKED_DOMAINS` | Comma-separated blocked domains | (none) |
|
|
121
150
|
|
|
151
|
+
## Troubleshooting
|
|
152
|
+
|
|
153
|
+
**`dayby-mcp: command not found` after global install**
|
|
154
|
+
|
|
155
|
+
Your npm global bin isn't in your PATH. Run:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
source ~/.bashrc
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Or add this to your `~/.bashrc` / `~/.zshrc`:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
export PATH="$(npm bin -g):$PATH"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Then restart your terminal or run `source ~/.bashrc` again.
|
|
168
|
+
|
|
169
|
+
**MCP server not showing up in Claude**
|
|
170
|
+
|
|
171
|
+
Restart Claude Code / Claude Desktop after adding the MCP config.
|
|
172
|
+
|
|
122
173
|
## License
|
|
123
174
|
|
|
124
175
|
MIT
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DayBy MCP — device authorization flow.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* dayby-mcp auth → interactive OAuth-style flow, saves token
|
|
6
|
+
* dayby-mcp auth --logout → clears saved credentials
|
|
7
|
+
*/
|
|
8
|
+
interface Credentials {
|
|
9
|
+
token: string;
|
|
10
|
+
api_url: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadCredentials(): Credentials | null;
|
|
13
|
+
export declare function clearCredentials(): void;
|
|
14
|
+
export declare function getStoredToken(apiUrl?: string): string | null;
|
|
15
|
+
export declare function runAuthFlow(apiUrl?: string): Promise<void>;
|
|
16
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DayBy MCP — device authorization flow.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* dayby-mcp auth → interactive OAuth-style flow, saves token
|
|
7
|
+
* dayby-mcp auth --logout → clears saved credentials
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.loadCredentials = loadCredentials;
|
|
44
|
+
exports.clearCredentials = clearCredentials;
|
|
45
|
+
exports.getStoredToken = getStoredToken;
|
|
46
|
+
exports.runAuthFlow = runAuthFlow;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
// --- Config ---
|
|
51
|
+
const DEFAULT_API_URL = 'https://dayby.dev';
|
|
52
|
+
const CREDENTIALS_DIR = path.join(process.env.HOME || '~', '.dayby');
|
|
53
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
54
|
+
const POLL_INTERVAL_MS = 2000;
|
|
55
|
+
// --- Storage ---
|
|
56
|
+
function loadCredentials() {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf-8'));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function saveCredentials(creds) {
|
|
65
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
66
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
67
|
+
}
|
|
68
|
+
function clearCredentials() {
|
|
69
|
+
try {
|
|
70
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
71
|
+
console.log('Logged out. Credentials cleared.');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
console.log('No credentials found.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function getStoredToken(apiUrl = DEFAULT_API_URL) {
|
|
78
|
+
const creds = loadCredentials();
|
|
79
|
+
if (!creds)
|
|
80
|
+
return null;
|
|
81
|
+
// If the stored token is for a different server, ignore it
|
|
82
|
+
if (creds.api_url !== apiUrl)
|
|
83
|
+
return null;
|
|
84
|
+
return creds.token;
|
|
85
|
+
}
|
|
86
|
+
// --- Browser ---
|
|
87
|
+
function openBrowser(url) {
|
|
88
|
+
const display = process.env.DISPLAY || ':0';
|
|
89
|
+
const cmd = process.platform === 'darwin' ? `open "${url}"` :
|
|
90
|
+
process.platform === 'win32' ? `start "" "${url}"` :
|
|
91
|
+
`DISPLAY=${display} xdg-open "${url}"`;
|
|
92
|
+
(0, child_process_1.exec)(cmd, (err) => {
|
|
93
|
+
if (err) {
|
|
94
|
+
console.error(`Could not open browser: ${err.message}`);
|
|
95
|
+
console.log(`Please open this URL manually:\n ${url}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// --- Poll ---
|
|
100
|
+
async function pollForToken(apiUrl, code) {
|
|
101
|
+
const url = `${apiUrl}/api/v2/auth/device/${code}`;
|
|
102
|
+
for (;;) {
|
|
103
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
104
|
+
const res = await fetch(url);
|
|
105
|
+
if (res.status === 200) {
|
|
106
|
+
const data = await res.json();
|
|
107
|
+
return data.token;
|
|
108
|
+
}
|
|
109
|
+
if (res.status === 410) {
|
|
110
|
+
throw new Error('Authorization expired. Please run `dayby-mcp auth` again.');
|
|
111
|
+
}
|
|
112
|
+
if (res.status !== 202) {
|
|
113
|
+
throw new Error(`Unexpected response while waiting for authorization (${res.status}).`);
|
|
114
|
+
}
|
|
115
|
+
// 202 = still pending, keep polling
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// --- Main flow ---
|
|
119
|
+
async function runAuthFlow(apiUrl = DEFAULT_API_URL) {
|
|
120
|
+
console.log('\n🔐 DayBy Authentication\n');
|
|
121
|
+
console.log(`Connecting to ${apiUrl}...`);
|
|
122
|
+
// 1. Request a device code
|
|
123
|
+
const res = await fetch(`${apiUrl}/api/v2/auth/device`, { method: 'POST' });
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
console.error(`\n❌ Could not reach DayBy (HTTP ${res.status}). Check your connection.\n`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const { code, verification_uri, expires_in } = await res.json();
|
|
129
|
+
const expiresMinutes = Math.round(expires_in / 60);
|
|
130
|
+
// 2. Open browser
|
|
131
|
+
console.log('Opening DayBy in your browser...\n');
|
|
132
|
+
console.log(` URL: ${verification_uri}`);
|
|
133
|
+
console.log(` Code: ${code}`);
|
|
134
|
+
console.log(` Expires in ${expiresMinutes} minutes\n`);
|
|
135
|
+
openBrowser(verification_uri);
|
|
136
|
+
// 3. Poll until the user clicks Authorize
|
|
137
|
+
console.log('Waiting for you to authorize in the browser...');
|
|
138
|
+
const token = await pollForToken(apiUrl, code);
|
|
139
|
+
// 4. Save
|
|
140
|
+
saveCredentials({ token, api_url: apiUrl });
|
|
141
|
+
console.log('\n✅ Authenticated! You can now use DayBy MCP.\n');
|
|
142
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
// We test the exported functions directly
|
|
40
|
+
const auth_js_1 = require("./auth.js");
|
|
41
|
+
const CREDENTIALS_DIR = path.join(process.env.HOME || '~', '.dayby');
|
|
42
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
43
|
+
const BACKUP_FILE = CREDENTIALS_FILE + '.test-backup';
|
|
44
|
+
(0, vitest_1.describe)('Auth', () => {
|
|
45
|
+
let originalCredentials = null;
|
|
46
|
+
(0, vitest_1.beforeEach)(() => {
|
|
47
|
+
// Back up existing credentials
|
|
48
|
+
try {
|
|
49
|
+
originalCredentials = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
originalCredentials = null;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.afterEach)(() => {
|
|
56
|
+
// Restore original credentials
|
|
57
|
+
if (originalCredentials) {
|
|
58
|
+
fs.writeFileSync(CREDENTIALS_FILE, originalCredentials, { mode: 0o600 });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.describe)('loadCredentials', () => {
|
|
62
|
+
(0, vitest_1.it)('returns null when no credentials file exists', () => {
|
|
63
|
+
// Temporarily rename the file
|
|
64
|
+
const tempPath = CREDENTIALS_FILE + '.tmp';
|
|
65
|
+
try {
|
|
66
|
+
fs.renameSync(CREDENTIALS_FILE, tempPath);
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
try {
|
|
70
|
+
const result = (0, auth_js_1.loadCredentials)();
|
|
71
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
try {
|
|
75
|
+
fs.renameSync(tempPath, CREDENTIALS_FILE);
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.it)('returns credentials when file exists', () => {
|
|
81
|
+
const creds = (0, auth_js_1.loadCredentials)();
|
|
82
|
+
// If credentials exist on this machine, they should have token and api_url
|
|
83
|
+
if (creds) {
|
|
84
|
+
(0, vitest_1.expect)(creds).toHaveProperty('token');
|
|
85
|
+
(0, vitest_1.expect)(creds).toHaveProperty('api_url');
|
|
86
|
+
(0, vitest_1.expect)(typeof creds.token).toBe('string');
|
|
87
|
+
(0, vitest_1.expect)(typeof creds.api_url).toBe('string');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.describe)('getStoredToken', () => {
|
|
92
|
+
(0, vitest_1.it)('returns null for a non-matching API URL', () => {
|
|
93
|
+
const token = (0, auth_js_1.getStoredToken)('https://not-the-right-server.com');
|
|
94
|
+
(0, vitest_1.expect)(token).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.it)('returns token for the correct API URL', () => {
|
|
97
|
+
const token = (0, auth_js_1.getStoredToken)('https://dayby.dev');
|
|
98
|
+
// Token exists on this dev machine
|
|
99
|
+
if (token) {
|
|
100
|
+
(0, vitest_1.expect)(typeof token).toBe('string');
|
|
101
|
+
(0, vitest_1.expect)(token.length).toBeGreaterThan(0);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
package/dist/dayby-client.d.ts
CHANGED
|
@@ -28,7 +28,11 @@ export interface PostsListResponse {
|
|
|
28
28
|
export declare class DayByClient {
|
|
29
29
|
private apiUrl;
|
|
30
30
|
private apiKey;
|
|
31
|
-
|
|
31
|
+
private resolveKey?;
|
|
32
|
+
constructor(config: DayByConfig & {
|
|
33
|
+
resolveKey?: () => string;
|
|
34
|
+
});
|
|
35
|
+
private getApiKey;
|
|
32
36
|
private request;
|
|
33
37
|
listPosts(page?: number, perPage?: number): Promise<PostsListResponse>;
|
|
34
38
|
getPost(slug: string): Promise<{
|
package/dist/dayby-client.js
CHANGED
|
@@ -7,14 +7,22 @@ exports.DayByClient = void 0;
|
|
|
7
7
|
class DayByClient {
|
|
8
8
|
apiUrl;
|
|
9
9
|
apiKey;
|
|
10
|
+
resolveKey;
|
|
10
11
|
constructor(config) {
|
|
11
12
|
this.apiUrl = config.apiUrl.replace(/\/$/, '');
|
|
12
13
|
this.apiKey = config.apiKey;
|
|
14
|
+
this.resolveKey = config.resolveKey;
|
|
15
|
+
}
|
|
16
|
+
getApiKey() {
|
|
17
|
+
if (this.resolveKey)
|
|
18
|
+
return this.resolveKey();
|
|
19
|
+
return this.apiKey;
|
|
13
20
|
}
|
|
14
21
|
async request(method, path, body) {
|
|
15
22
|
const url = `${this.apiUrl}${path}`;
|
|
23
|
+
const apiKey = this.getApiKey();
|
|
16
24
|
const headers = {
|
|
17
|
-
'Authorization': `Bearer ${
|
|
25
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
18
26
|
'Content-Type': 'application/json',
|
|
19
27
|
'Accept': 'application/json',
|
|
20
28
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,9 @@
|
|
|
6
6
|
* All content is sanitized locally before it ever touches the network.
|
|
7
7
|
*
|
|
8
8
|
* Flow: draft_post → review → publish_post (nothing leaves without approval)
|
|
9
|
+
*
|
|
10
|
+
* Auth:
|
|
11
|
+
* Run `dayby-mcp auth` once to authorize via OAuth (no API key needed).
|
|
12
|
+
* Or set DAYBY_API_KEY env var for the legacy API key flow.
|
|
9
13
|
*/
|
|
10
14
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* All content is sanitized locally before it ever touches the network.
|
|
8
8
|
*
|
|
9
9
|
* Flow: draft_post → review → publish_post (nothing leaves without approval)
|
|
10
|
+
*
|
|
11
|
+
* Auth:
|
|
12
|
+
* Run `dayby-mcp auth` once to authorize via OAuth (no API key needed).
|
|
13
|
+
* Or set DAYBY_API_KEY env var for the legacy API key flow.
|
|
10
14
|
*/
|
|
11
15
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
16
|
if (k2 === undefined) k2 = k;
|
|
@@ -47,13 +51,29 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
47
51
|
const zod_1 = require("zod");
|
|
48
52
|
const sanitizer_js_1 = require("./sanitizer.js");
|
|
49
53
|
const dayby_client_js_1 = require("./dayby-client.js");
|
|
54
|
+
const auth_js_1 = require("./auth.js");
|
|
55
|
+
const visibility_js_1 = require("./visibility.js");
|
|
50
56
|
const fs = __importStar(require("fs"));
|
|
51
57
|
const path = __importStar(require("path"));
|
|
58
|
+
// --- Auth subcommand ---
|
|
59
|
+
async function handleAuthCommand() {
|
|
60
|
+
const args = process.argv.slice(3); // after "auth"
|
|
61
|
+
if (args.includes('--logout')) {
|
|
62
|
+
(0, auth_js_1.clearCredentials)();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const apiUrl = process.env.DAYBY_API_URL || 'https://dayby.dev';
|
|
66
|
+
await (0, auth_js_1.runAuthFlow)(apiUrl);
|
|
67
|
+
}
|
|
52
68
|
function loadConfig() {
|
|
53
|
-
// 1. Check env vars
|
|
54
69
|
const apiUrl = process.env.DAYBY_API_URL || 'https://dayby.dev';
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
// 1. Env var takes priority (legacy)
|
|
71
|
+
let apiKey = process.env.DAYBY_API_KEY || '';
|
|
72
|
+
// 2. Fall back to stored token from `dayby-mcp auth`
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
apiKey = (0, auth_js_1.getStoredToken)(apiUrl) || '';
|
|
75
|
+
}
|
|
76
|
+
// 3. Load sanitizer config from file if it exists
|
|
57
77
|
let sanitizerConfig = {};
|
|
58
78
|
const configPaths = [
|
|
59
79
|
path.join(process.env.HOME || '~', '.dayby', 'sanitizer.json'),
|
|
@@ -69,7 +89,7 @@ function loadConfig() {
|
|
|
69
89
|
// File doesn't exist, that's fine
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
|
-
//
|
|
92
|
+
// 4. Also load from env (comma-separated)
|
|
73
93
|
if (process.env.DAYBY_BLOCKED_TERMS) {
|
|
74
94
|
sanitizerConfig.blockedTerms = [
|
|
75
95
|
...(sanitizerConfig.blockedTerms || []),
|
|
@@ -90,9 +110,21 @@ function generateDraftId() {
|
|
|
90
110
|
}
|
|
91
111
|
// --- Main ---
|
|
92
112
|
async function main() {
|
|
113
|
+
// Handle `dayby-mcp auth [--logout]` subcommand
|
|
114
|
+
if (process.argv[2] === 'auth') {
|
|
115
|
+
await handleAuthCommand();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
93
118
|
const config = loadConfig();
|
|
94
119
|
const sanitizer = new sanitizer_js_1.Sanitizer(config.sanitizer);
|
|
95
|
-
const client = new dayby_client_js_1.DayByClient({
|
|
120
|
+
const client = new dayby_client_js_1.DayByClient({
|
|
121
|
+
apiUrl: config.apiUrl,
|
|
122
|
+
apiKey: config.apiKey,
|
|
123
|
+
resolveKey: () => {
|
|
124
|
+
// Re-check env and stored credentials on every call
|
|
125
|
+
return process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
96
128
|
const server = new mcp_js_1.McpServer({
|
|
97
129
|
name: 'dayby',
|
|
98
130
|
version: '0.1.0',
|
|
@@ -117,6 +149,7 @@ async function main() {
|
|
|
117
149
|
sanitizedTitle: titleResult.clean,
|
|
118
150
|
sanitizedContent: contentResult.clean,
|
|
119
151
|
strippedItems: allStripped,
|
|
152
|
+
visibility,
|
|
120
153
|
createdAt: new Date(),
|
|
121
154
|
};
|
|
122
155
|
drafts.set(draftId, draft);
|
|
@@ -149,7 +182,8 @@ async function main() {
|
|
|
149
182
|
draft_id: zod_1.z.string().describe('The draft ID from draft_post'),
|
|
150
183
|
title: zod_1.z.string().optional().describe('Updated title (will be re-sanitized)'),
|
|
151
184
|
content: zod_1.z.string().optional().describe('Updated content (will be re-sanitized)'),
|
|
152
|
-
|
|
185
|
+
visibility: zod_1.z.enum(['published', 'draft']).optional().describe('Updated visibility'),
|
|
186
|
+
}, async ({ draft_id, title, content, visibility }) => {
|
|
153
187
|
const draft = drafts.get(draft_id);
|
|
154
188
|
if (!draft) {
|
|
155
189
|
return {
|
|
@@ -167,9 +201,13 @@ async function main() {
|
|
|
167
201
|
draft.originalContent = content;
|
|
168
202
|
draft.strippedItems.push(...result.stripped);
|
|
169
203
|
}
|
|
204
|
+
if (visibility) {
|
|
205
|
+
draft.visibility = visibility;
|
|
206
|
+
}
|
|
170
207
|
let response = `✏️ **Draft Updated** (ID: ${draft_id})\n\n`;
|
|
171
208
|
response += `**Title:** ${draft.sanitizedTitle}\n\n`;
|
|
172
209
|
response += `**Content:**\n${draft.sanitizedContent}\n\n`;
|
|
210
|
+
response += `**Visibility:** ${draft.visibility}\n`;
|
|
173
211
|
response += `\n👉 Use **publish_post** with draft ID: \`${draft_id}\` when ready.`;
|
|
174
212
|
return { content: [{ type: 'text', text: response }] };
|
|
175
213
|
});
|
|
@@ -187,11 +225,12 @@ async function main() {
|
|
|
187
225
|
content: [{ type: 'text', text: `❌ Draft not found: ${draft_id}. Use draft_post to create a new one.` }],
|
|
188
226
|
};
|
|
189
227
|
}
|
|
190
|
-
|
|
228
|
+
const currentKey = process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey;
|
|
229
|
+
if (!currentKey) {
|
|
191
230
|
return {
|
|
192
231
|
content: [{
|
|
193
232
|
type: 'text',
|
|
194
|
-
text: '❌
|
|
233
|
+
text: '❌ Not authenticated. Run `dayby-mcp auth` in your terminal to connect your DayBy account.',
|
|
195
234
|
}],
|
|
196
235
|
};
|
|
197
236
|
}
|
|
@@ -200,6 +239,7 @@ async function main() {
|
|
|
200
239
|
const result = await client.createPost({
|
|
201
240
|
title: draft.sanitizedTitle,
|
|
202
241
|
content: draft.sanitizedContent,
|
|
242
|
+
visibility: (0, visibility_js_1.toApiVisibility)(draft.visibility),
|
|
203
243
|
});
|
|
204
244
|
let response = `✅ **Published to DayBy!**\n\n`;
|
|
205
245
|
response += `**Title:** ${result.post.title}\n`;
|
|
@@ -234,9 +274,9 @@ async function main() {
|
|
|
234
274
|
page: zod_1.z.number().default(1).describe('Page number'),
|
|
235
275
|
per_page: zod_1.z.number().default(10).describe('Posts per page'),
|
|
236
276
|
}, async ({ page, per_page }) => {
|
|
237
|
-
if (!config.apiKey) {
|
|
277
|
+
if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
|
|
238
278
|
return {
|
|
239
|
-
content: [{ type: 'text', text: '❌
|
|
279
|
+
content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }],
|
|
240
280
|
};
|
|
241
281
|
}
|
|
242
282
|
try {
|
|
@@ -245,7 +285,7 @@ async function main() {
|
|
|
245
285
|
for (const post of result.posts) {
|
|
246
286
|
response += `• **${post.title}** — ${post.url}\n`;
|
|
247
287
|
response += ` ${post.content.slice(0, 100)}${post.content.length > 100 ? '...' : ''}\n`;
|
|
248
|
-
response += ` _${post.created_at.slice(0, 10)} · ${post.visibility}_\n\n`;
|
|
288
|
+
response += ` _${post.created_at.slice(0, 10)} · ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}_\n\n`;
|
|
249
289
|
}
|
|
250
290
|
return { content: [{ type: 'text', text: response }] };
|
|
251
291
|
}
|
|
@@ -259,6 +299,88 @@ async function main() {
|
|
|
259
299
|
}
|
|
260
300
|
});
|
|
261
301
|
// ========================================
|
|
302
|
+
// Tool: get_post
|
|
303
|
+
// ========================================
|
|
304
|
+
server.tool('get_post', 'Get a single DayBy post by its slug.', {
|
|
305
|
+
slug: zod_1.z.string().describe('The post slug'),
|
|
306
|
+
}, async ({ slug }) => {
|
|
307
|
+
if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
|
|
308
|
+
return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const result = await client.getPost(slug);
|
|
312
|
+
const post = result.post;
|
|
313
|
+
let response = `📄 **${post.title}**\n\n`;
|
|
314
|
+
response += `**Slug:** ${post.slug}\n`;
|
|
315
|
+
response += `**URL:** ${post.url}\n`;
|
|
316
|
+
response += `**Visibility:** ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}\n`;
|
|
317
|
+
response += `**Created:** ${post.created_at.slice(0, 10)}\n\n`;
|
|
318
|
+
response += post.content;
|
|
319
|
+
return { content: [{ type: 'text', text: response }] };
|
|
320
|
+
}
|
|
321
|
+
catch (e) {
|
|
322
|
+
return {
|
|
323
|
+
content: [{ type: 'text', text: `❌ Failed to get post: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
// ========================================
|
|
328
|
+
// Tool: update_post
|
|
329
|
+
// ========================================
|
|
330
|
+
server.tool('update_post', 'Update an existing DayBy post by slug. Content is sanitized locally before sending.', {
|
|
331
|
+
slug: zod_1.z.string().describe('The post slug to update'),
|
|
332
|
+
title: zod_1.z.string().optional().describe('New title (will be sanitized)'),
|
|
333
|
+
content: zod_1.z.string().optional().describe('New content (will be sanitized)'),
|
|
334
|
+
visibility: zod_1.z.enum(['published', 'draft']).optional().describe('New visibility'),
|
|
335
|
+
}, async ({ slug, title, content, visibility }) => {
|
|
336
|
+
if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
|
|
337
|
+
return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
|
|
338
|
+
}
|
|
339
|
+
const params = {};
|
|
340
|
+
if (title)
|
|
341
|
+
params.title = sanitizer.sanitize(title).clean;
|
|
342
|
+
if (content)
|
|
343
|
+
params.content = sanitizer.sanitize(content).clean;
|
|
344
|
+
if (visibility)
|
|
345
|
+
params.visibility = (0, visibility_js_1.toApiVisibility)(visibility);
|
|
346
|
+
if (Object.keys(params).length === 0) {
|
|
347
|
+
return { content: [{ type: 'text', text: '❌ Provide at least one field to update (title, content, or visibility).' }] };
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const result = await client.updatePost(slug, params);
|
|
351
|
+
const post = result.post;
|
|
352
|
+
let response = `✅ **Post Updated**\n\n`;
|
|
353
|
+
response += `**Title:** ${post.title}\n`;
|
|
354
|
+
response += `**URL:** ${post.url}\n`;
|
|
355
|
+
response += `**Visibility:** ${(0, visibility_js_1.fromApiVisibility)(post.visibility)}\n`;
|
|
356
|
+
return { content: [{ type: 'text', text: response }] };
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
return {
|
|
360
|
+
content: [{ type: 'text', text: `❌ Failed to update post: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
// ========================================
|
|
365
|
+
// Tool: delete_post
|
|
366
|
+
// ========================================
|
|
367
|
+
server.tool('delete_post', 'Permanently delete a DayBy post by its slug.', {
|
|
368
|
+
slug: zod_1.z.string().describe('The post slug to delete'),
|
|
369
|
+
}, async ({ slug }) => {
|
|
370
|
+
if (!(process.env.DAYBY_API_KEY || (0, auth_js_1.getStoredToken)(config.apiUrl) || config.apiKey)) {
|
|
371
|
+
return { content: [{ type: 'text', text: '❌ Not authenticated. Run `dayby-mcp auth` to connect your DayBy account.' }] };
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const result = await client.deletePost(slug);
|
|
375
|
+
return { content: [{ type: 'text', text: `✅ ${result.message || 'Post deleted.'}` }] };
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
return {
|
|
379
|
+
content: [{ type: 'text', text: `❌ Failed to delete post: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
// ========================================
|
|
262
384
|
// Tool: check_content
|
|
263
385
|
// Dry-run sanitization — see what would get stripped without creating a draft.
|
|
264
386
|
// ========================================
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const sanitizer_js_1 = require("./sanitizer.js");
|
|
5
|
+
(0, vitest_1.describe)('Sanitizer', () => {
|
|
6
|
+
const sanitizer = new sanitizer_js_1.Sanitizer();
|
|
7
|
+
(0, vitest_1.describe)('default patterns', () => {
|
|
8
|
+
(0, vitest_1.it)('strips API keys', () => {
|
|
9
|
+
const result = sanitizer.sanitize('My api_key=sk_live_abc123def456ghi789jkl012');
|
|
10
|
+
(0, vitest_1.expect)(result.clean).not.toContain('sk_live_abc123def456ghi789jkl012');
|
|
11
|
+
(0, vitest_1.expect)(result.stripped.length).toBeGreaterThan(0);
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)('strips AWS access keys', () => {
|
|
14
|
+
const result = sanitizer.sanitize('Key: AKIAIOSFODNN7EXAMPLE');
|
|
15
|
+
(0, vitest_1.expect)(result.clean).not.toContain('AKIAIOSFODNN7EXAMPLE');
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)('strips private IPs', () => {
|
|
18
|
+
const result = sanitizer.sanitize('Server at 192.168.1.100');
|
|
19
|
+
(0, vitest_1.expect)(result.clean).not.toContain('192.168.1.100');
|
|
20
|
+
(0, vitest_1.expect)(result.clean).toContain('[REDACTED]');
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('strips email addresses', () => {
|
|
23
|
+
const result = sanitizer.sanitize('Contact me at user@company.com');
|
|
24
|
+
(0, vitest_1.expect)(result.clean).not.toContain('user@company.com');
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)('strips database URLs', () => {
|
|
27
|
+
const result = sanitizer.sanitize('postgres://user:pass@localhost/db');
|
|
28
|
+
(0, vitest_1.expect)(result.clean).not.toContain('postgres://');
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)('strips file paths with usernames', () => {
|
|
31
|
+
const result = sanitizer.sanitize('File at /home/javier/project');
|
|
32
|
+
(0, vitest_1.expect)(result.clean).not.toContain('/home/javier');
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('strips JWT tokens', () => {
|
|
35
|
+
const result = sanitizer.sanitize('Token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.abc123def456');
|
|
36
|
+
(0, vitest_1.expect)(result.clean).not.toContain('eyJhbGci');
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)('strips GitHub tokens', () => {
|
|
39
|
+
const result = sanitizer.sanitize('ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl');
|
|
40
|
+
(0, vitest_1.expect)(result.clean).not.toContain('ghp_');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
(0, vitest_1.describe)('safe content', () => {
|
|
44
|
+
(0, vitest_1.it)('leaves clean text untouched', () => {
|
|
45
|
+
const text = 'I built a feature using React and TypeScript today.';
|
|
46
|
+
const result = sanitizer.sanitize(text);
|
|
47
|
+
(0, vitest_1.expect)(result.clean).toBe(text);
|
|
48
|
+
(0, vitest_1.expect)(result.stripped).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)('leaves public IPs alone', () => {
|
|
51
|
+
const result = sanitizer.sanitize('Server at 8.8.8.8');
|
|
52
|
+
(0, vitest_1.expect)(result.clean).toContain('8.8.8.8');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.describe)('blocked terms', () => {
|
|
56
|
+
(0, vitest_1.it)('strips configured blocked terms', () => {
|
|
57
|
+
const s = new sanitizer_js_1.Sanitizer({ blockedTerms: ['Acme Corp'] });
|
|
58
|
+
const result = s.sanitize('Working on the Acme Corp project');
|
|
59
|
+
(0, vitest_1.expect)(result.clean).not.toContain('Acme Corp');
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.it)('strips blocked names', () => {
|
|
62
|
+
const s = new sanitizer_js_1.Sanitizer({ blockedNames: ['John Doe'] });
|
|
63
|
+
const result = s.sanitize('Paired with John Doe on the fix');
|
|
64
|
+
(0, vitest_1.expect)(result.clean).not.toContain('John Doe');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
(0, vitest_1.describe)('blocked domains', () => {
|
|
68
|
+
(0, vitest_1.it)('strips blocked domain URLs', () => {
|
|
69
|
+
const s = new sanitizer_js_1.Sanitizer({ blockedDomains: ['internal.corp.com'] });
|
|
70
|
+
const result = s.sanitize('Check https://jira.internal.corp.com/browse/TICK-123');
|
|
71
|
+
(0, vitest_1.expect)(result.clean).not.toContain('internal.corp.com');
|
|
72
|
+
(0, vitest_1.expect)(result.clean).toContain('[REDACTED-URL]');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.describe)('check', () => {
|
|
76
|
+
(0, vitest_1.it)('returns safe for clean content', () => {
|
|
77
|
+
const result = sanitizer.check('Just a normal post about coding.');
|
|
78
|
+
(0, vitest_1.expect)(result.safe).toBe(true);
|
|
79
|
+
(0, vitest_1.expect)(result.issues).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)('returns unsafe for sensitive content', () => {
|
|
82
|
+
const result = sanitizer.check('My api_key=super_secret_key_1234567890');
|
|
83
|
+
(0, vitest_1.expect)(result.safe).toBe(false);
|
|
84
|
+
(0, vitest_1.expect)(result.issues.length).toBeGreaterThan(0);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visibility mapping between MCP (user-facing) and DayBy API values.
|
|
3
|
+
*
|
|
4
|
+
* MCP uses "published"/"draft" (intuitive for users).
|
|
5
|
+
* DayBy API uses "publik"/"draft"/"hidden" (Rails enum).
|
|
6
|
+
*/
|
|
7
|
+
export declare function toApiVisibility(v: string): string;
|
|
8
|
+
export declare function fromApiVisibility(v: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Visibility mapping between MCP (user-facing) and DayBy API values.
|
|
4
|
+
*
|
|
5
|
+
* MCP uses "published"/"draft" (intuitive for users).
|
|
6
|
+
* DayBy API uses "publik"/"draft"/"hidden" (Rails enum).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.toApiVisibility = toApiVisibility;
|
|
10
|
+
exports.fromApiVisibility = fromApiVisibility;
|
|
11
|
+
function toApiVisibility(v) {
|
|
12
|
+
return v === 'published' ? 'publik' : v;
|
|
13
|
+
}
|
|
14
|
+
function fromApiVisibility(v) {
|
|
15
|
+
return v === 'publik' ? 'published' : v;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const visibility_js_1 = require("./visibility.js");
|
|
5
|
+
(0, vitest_1.describe)('Visibility mapping', () => {
|
|
6
|
+
(0, vitest_1.describe)('toApiVisibility', () => {
|
|
7
|
+
(0, vitest_1.it)('maps "published" to "publik"', () => {
|
|
8
|
+
(0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('published')).toBe('publik');
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.it)('passes "draft" through unchanged', () => {
|
|
11
|
+
(0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('draft')).toBe('draft');
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)('passes "hidden" through unchanged', () => {
|
|
14
|
+
(0, vitest_1.expect)((0, visibility_js_1.toApiVisibility)('hidden')).toBe('hidden');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.describe)('fromApiVisibility', () => {
|
|
18
|
+
(0, vitest_1.it)('maps "publik" to "published"', () => {
|
|
19
|
+
(0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('publik')).toBe('published');
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('passes "draft" through unchanged', () => {
|
|
22
|
+
(0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('draft')).toBe('draft');
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)('passes "hidden" through unchanged', () => {
|
|
25
|
+
(0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)('hidden')).toBe('hidden');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.describe)('round-trip', () => {
|
|
29
|
+
(0, vitest_1.it)('published -> publik -> published', () => {
|
|
30
|
+
(0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)((0, visibility_js_1.toApiVisibility)('published'))).toBe('published');
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)('draft -> draft -> draft', () => {
|
|
33
|
+
(0, vitest_1.expect)((0, visibility_js_1.fromApiVisibility)((0, visibility_js_1.toApiVisibility)('draft'))).toBe('draft');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dayby/mcp-server",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "DayBy MCP Server — Post your dev progress from Claude, Cursor, or any MCP client. Local sanitization built in.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,15 +13,25 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
15
|
"dev": "tsc --watch",
|
|
16
|
-
"start": "node dist/index.js"
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
17
19
|
},
|
|
18
|
-
"keywords": [
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"dayby",
|
|
23
|
+
"developer",
|
|
24
|
+
"journal",
|
|
25
|
+
"claude"
|
|
26
|
+
],
|
|
19
27
|
"license": "MIT",
|
|
20
28
|
"dependencies": {
|
|
21
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
|
+
"zod": "^3.22.0"
|
|
22
31
|
},
|
|
23
32
|
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
24
34
|
"typescript": "^5.3.0",
|
|
25
|
-
"
|
|
35
|
+
"vitest": "^4.1.5"
|
|
26
36
|
}
|
|
27
37
|
}
|