@linwheel/mcp-server 0.1.0 โ 0.2.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 +31 -1
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.js +28 -0
- package/dist/credentials.js.map +1 -0
- package/dist/device-login.d.ts +28 -0
- package/dist/device-login.js +30 -0
- package/dist/device-login.js.map +1 -0
- package/dist/index.js +71 -50
- package/dist/index.js.map +1 -1
- package/dist/tools/carousels-scheduled.d.ts +3 -0
- package/dist/tools/carousels-scheduled.js +9 -0
- package/dist/tools/carousels-scheduled.js.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/onboard.d.ts +3 -0
- package/dist/tools/onboard.js +25 -0
- package/dist/tools/onboard.js.map +1 -0
- package/dist/tools/post-carousel-caption.d.ts +3 -0
- package/dist/tools/post-carousel-caption.js +13 -0
- package/dist/tools/post-carousel-caption.js.map +1 -0
- package/dist/tools/post-carousel-compile.d.ts +3 -0
- package/dist/tools/post-carousel-compile.js +12 -0
- package/dist/tools/post-carousel-compile.js.map +1 -0
- package/dist/tools/post-carousel-delete.d.ts +3 -0
- package/dist/tools/post-carousel-delete.js +12 -0
- package/dist/tools/post-carousel-delete.js.map +1 -0
- package/dist/tools/post-carousel-regenerate.d.ts +3 -0
- package/dist/tools/post-carousel-regenerate.js +27 -0
- package/dist/tools/post-carousel-regenerate.js.map +1 -0
- package/dist/tools/voice-extract.d.ts +3 -0
- package/dist/tools/voice-extract.js +41 -0
- package/dist/tools/voice-extract.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -80,7 +80,14 @@ If it works, you're in.
|
|
|
80
80
|
|
|
81
81
|
## ๐ ๏ธ Tools
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
24 tools across 6 categories. Claude picks the right one โ just describe what you want.
|
|
84
|
+
|
|
85
|
+
### Onboarding
|
|
86
|
+
|
|
87
|
+
| Tool | What it does |
|
|
88
|
+
|------|-------------|
|
|
89
|
+
| `linwheel_onboard` | Guided setup โ walks you from zero to your first scheduled post (start here) |
|
|
90
|
+
| `linwheel_voice_extract` | Build a clean voice profile from samples โ rejects off-voice / AI-slop outliers |
|
|
84
91
|
|
|
85
92
|
### Content Creation
|
|
86
93
|
|
|
@@ -212,6 +219,29 @@ The MCP server is a thin client. All heavy lifting (LLM calls, image generation,
|
|
|
212
219
|
|
|
213
220
|
---
|
|
214
221
|
|
|
222
|
+
## ๐ข Releasing (maintainers)
|
|
223
|
+
|
|
224
|
+
The package publishes to npm via **trusted publishing (OIDC)** โ there is **no `NPM_TOKEN`**. The workflow (`.github/workflows/publish-mcp.yml`) mints a short-lived token from the GitHub id-token at publish time, and provenance is attached automatically. The trusted publisher is configured on npmjs.com (package โ Settings โ Trusted Publisher โ GitHub Actions โ `Peleke/linwheel` โ `publish-mcp.yml` โ environment `npm`).
|
|
225
|
+
|
|
226
|
+
To cut a release:
|
|
227
|
+
|
|
228
|
+
1. Bump the version in **`mcp/package.json`**, then sync the lockfile and the server version string:
|
|
229
|
+
```bash
|
|
230
|
+
cd mcp
|
|
231
|
+
# edit package.json version, then:
|
|
232
|
+
npm install --package-lock-only # sync mcp/package-lock.json
|
|
233
|
+
# also bump the version in src/index.ts (McpServer { version })
|
|
234
|
+
```
|
|
235
|
+
2. Merge the bump to `main` (via PR).
|
|
236
|
+
3. **Publish by pushing a tag** that points at the merged commit:
|
|
237
|
+
```bash
|
|
238
|
+
git tag mcp-v<version> origin/main && git push origin mcp-v<version>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> โ ๏ธ **Publish only via a `mcp-v*` tag โ not `workflow_dispatch`.** The `npm` GitHub Environment has a deployment-branch rule that permits **tags matching `mcp-v*`** only. A manual `workflow_dispatch` from `main` is rejected with *"Branch 'main' is not allowed to deploy to npm due to environment protection rules."* If you re-run after a workflow fix, move the tag to the fixed commit first (`git tag -d`, delete the remote tag, re-tag `origin/main`, push) so GitHub reads the corrected workflow from the tag's commit.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
215
245
|
<div align="center">
|
|
216
246
|
|
|
217
247
|
Built with [Model Context Protocol](https://modelcontextprotocol.io) ยท Powered by [Claude](https://claude.ai)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local credential storage for the device-login flow.
|
|
3
|
+
*
|
|
4
|
+
* After the user approves a device authorization in the browser, the MCP client
|
|
5
|
+
* captures the minted key and writes it to ~/.linwheel/credentials.json. On the
|
|
6
|
+
* next startup, the server reads it as a fallback when LINWHEEL_API_KEY is unset
|
|
7
|
+
* โ the same pattern as `vercel login` (~/.vercel).
|
|
8
|
+
*/
|
|
9
|
+
export interface LinWheelCredentials {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
signingSecret?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function credentialsPath(): string;
|
|
14
|
+
export declare function readCredentials(): Promise<LinWheelCredentials | null>;
|
|
15
|
+
export declare function writeCredentials(creds: LinWheelCredentials): Promise<string>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
const DIR = join(homedir(), ".linwheel");
|
|
5
|
+
const FILE = join(DIR, "credentials.json");
|
|
6
|
+
export function credentialsPath() {
|
|
7
|
+
return FILE;
|
|
8
|
+
}
|
|
9
|
+
export async function readCredentials() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await fs.readFile(FILE, "utf8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (parsed && typeof parsed.apiKey === "string" && parsed.apiKey.length > 0) {
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function writeCredentials(creds) {
|
|
23
|
+
// 0700 dir / 0600 file โ the key is a secret; keep it owner-only.
|
|
24
|
+
await fs.mkdir(DIR, { recursive: true, mode: 0o700 });
|
|
25
|
+
await fs.writeFile(FILE, JSON.stringify(creds, null, 2) + "\n", { mode: 0o600 });
|
|
26
|
+
return FILE;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAgB5B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AACzC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;AAE3C,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACtD,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5E,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAA0B;IAC/D,kEAAkE;IAClE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-login client โ the MCP side of the `vercel login`-style flow.
|
|
3
|
+
*
|
|
4
|
+
* `start` kicks off a device authorization and returns a URL for the user to
|
|
5
|
+
* approve in the browser; `poll` checks whether they've approved yet and, once
|
|
6
|
+
* they have, returns the minted key (once).
|
|
7
|
+
*/
|
|
8
|
+
export interface DeviceStart {
|
|
9
|
+
deviceCode: string;
|
|
10
|
+
userCode: string;
|
|
11
|
+
verificationUri: string;
|
|
12
|
+
verificationUriComplete: string;
|
|
13
|
+
interval: number;
|
|
14
|
+
expiresIn: number;
|
|
15
|
+
}
|
|
16
|
+
export type DevicePoll = {
|
|
17
|
+
status: "pending";
|
|
18
|
+
} | {
|
|
19
|
+
status: "denied";
|
|
20
|
+
} | {
|
|
21
|
+
status: "expired";
|
|
22
|
+
} | {
|
|
23
|
+
status: "approved";
|
|
24
|
+
apiKey: string;
|
|
25
|
+
signingSecret: string | null;
|
|
26
|
+
};
|
|
27
|
+
export declare function startDeviceLogin(baseUrl: string): Promise<DeviceStart>;
|
|
28
|
+
export declare function pollDeviceLogin(baseUrl: string, deviceCode: string): Promise<DevicePoll>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-login client โ the MCP side of the `vercel login`-style flow.
|
|
3
|
+
*
|
|
4
|
+
* `start` kicks off a device authorization and returns a URL for the user to
|
|
5
|
+
* approve in the browser; `poll` checks whether they've approved yet and, once
|
|
6
|
+
* they have, returns the minted key (once).
|
|
7
|
+
*/
|
|
8
|
+
export async function startDeviceLogin(baseUrl) {
|
|
9
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/auth/device/start`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { "Content-Type": "application/json" },
|
|
12
|
+
body: "{}",
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
throw new Error(`Could not start LinWheel login (HTTP ${res.status}).`);
|
|
16
|
+
}
|
|
17
|
+
return (await res.json());
|
|
18
|
+
}
|
|
19
|
+
export async function pollDeviceLogin(baseUrl, deviceCode) {
|
|
20
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/auth/device/poll`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify({ deviceCode }),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`Could not check LinWheel login status (HTTP ${res.status}).`);
|
|
27
|
+
}
|
|
28
|
+
return (await res.json());
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=device-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-login.js","sourceRoot":"","sources":["../src/device-login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe;IACpD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,wBAAwB,EAAE;QAC7E,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAe,EACf,UAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,EAAE;QAC5E,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,CAAC;KACrC,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,+CAA+C,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;AAC1C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,75 +3,96 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { LinWheelClient } from "./client.js";
|
|
5
5
|
import { registerAllTools } from "./tools/index.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { readCredentials, writeCredentials } from "./credentials.js";
|
|
8
|
+
import { startDeviceLogin, pollDeviceLogin } from "./device-login.js";
|
|
6
9
|
const BASE_URL = process.env.LINWHEEL_BASE_URL ?? "https://www.linwheel.io";
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
// Key resolution: LINWHEEL_API_KEY env var first, then the saved credentials
|
|
11
|
+
// file written by the device-login flow (~/.linwheel/credentials.json).
|
|
12
|
+
const stored = process.env.LINWHEEL_API_KEY ? null : await readCredentials();
|
|
13
|
+
const API_KEY = process.env.LINWHEEL_API_KEY ?? stored?.apiKey;
|
|
14
|
+
const SIGNING_SECRET = process.env.LINWHEEL_SIGNING_SECRET ?? stored?.signingSecret ?? undefined;
|
|
15
|
+
const server = new McpServer({ name: "linwheel", version: "0.2.1" }, { capabilities: { tools: {} } });
|
|
9
16
|
if (API_KEY) {
|
|
10
17
|
const client = new LinWheelClient({
|
|
11
18
|
baseUrl: BASE_URL,
|
|
12
19
|
apiKey: API_KEY,
|
|
13
|
-
signingSecret:
|
|
20
|
+
signingSecret: SIGNING_SECRET,
|
|
14
21
|
timeoutMs: parseInt(process.env.LINWHEEL_TIMEOUT_MS ?? "60000", 10),
|
|
15
22
|
});
|
|
16
23
|
registerAllTools(server, client);
|
|
17
24
|
}
|
|
18
25
|
else {
|
|
19
|
-
// No
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// No key configured โ expose the guided device login. Call once to get an
|
|
27
|
+
// approval URL, then again with the deviceCode to capture and save the key.
|
|
28
|
+
server.tool("linwheel_setup", "Connect LinWheel by logging in from the browser โ no manual key copying. Call with no arguments to get an approval URL; after you approve in the browser, call it again with the deviceCode it gave you to finish. Your key is saved to ~/.linwheel/credentials.json automatically; then restart your MCP client to load the tools.", {
|
|
29
|
+
deviceCode: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("The deviceCode returned by the first call. Pass it after approving in the browser to finish login."),
|
|
33
|
+
}, async ({ deviceCode }) => {
|
|
34
|
+
if (!deviceCode) {
|
|
35
|
+
const start = await startDeviceLogin(BASE_URL);
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: [
|
|
41
|
+
"# Connect LinWheel",
|
|
42
|
+
"",
|
|
43
|
+
"1. Open this URL and approve the device:",
|
|
44
|
+
` ${start.verificationUriComplete}`,
|
|
45
|
+
` (verification code: ${start.userCode})`,
|
|
46
|
+
"",
|
|
47
|
+
"2. After approving in the browser, call `linwheel_setup` again with:",
|
|
48
|
+
` deviceCode: "${start.deviceCode}"`,
|
|
49
|
+
"",
|
|
50
|
+
"I'll capture your key and save it automatically. The code expires in ~10 minutes.",
|
|
51
|
+
].join("\n"),
|
|
28
52
|
},
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
}
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const poll = await pollDeviceLogin(BASE_URL, deviceCode);
|
|
57
|
+
if (poll.status === "approved") {
|
|
58
|
+
const path = await writeCredentials({
|
|
59
|
+
apiKey: poll.apiKey,
|
|
60
|
+
signingSecret: poll.signingSecret,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: [
|
|
67
|
+
"โ
Connected to LinWheel.",
|
|
68
|
+
"",
|
|
69
|
+
`Saved your key to ${path}.`,
|
|
70
|
+
"Restart your MCP client (or Claude Code) to load the LinWheel tools, then run `linwheel_onboard` to get started.",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (poll.status === "pending") {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: "Still waiting for approval. Approve in the browser, then call `linwheel_setup` again with the same deviceCode.",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
32
86
|
return {
|
|
33
87
|
content: [
|
|
34
88
|
{
|
|
35
89
|
type: "text",
|
|
36
|
-
text:
|
|
37
|
-
"# LinWheel Setup",
|
|
38
|
-
"",
|
|
39
|
-
"No API key configured. Follow these steps:",
|
|
40
|
-
"",
|
|
41
|
-
"## 1. Create an account",
|
|
42
|
-
`Sign up at: ${BASE_URL}/login`,
|
|
43
|
-
"",
|
|
44
|
-
"## 2. Generate an API key",
|
|
45
|
-
`Go to: ${BASE_URL}/settings`,
|
|
46
|
-
'Click "Generate API Key" and copy the key (starts with lw_sk_).',
|
|
47
|
-
"The key is shown only once โ save it somewhere safe.",
|
|
48
|
-
"",
|
|
49
|
-
"## 3. Add to your MCP config",
|
|
50
|
-
"",
|
|
51
|
-
"**Claude Code** โ run this in your terminal:",
|
|
52
|
-
"```",
|
|
53
|
-
'claude mcp add linwheel -- npx -y @linwheel/mcp-server',
|
|
54
|
-
"```",
|
|
55
|
-
"Then set the env var in your `.mcp.json`:",
|
|
56
|
-
"",
|
|
57
|
-
"```json",
|
|
58
|
-
configExample,
|
|
59
|
-
"```",
|
|
60
|
-
"",
|
|
61
|
-
"## 4. Restart Claude Code",
|
|
62
|
-
"After updating config, restart Claude Code to load LinWheel tools.",
|
|
63
|
-
"",
|
|
64
|
-
"## 5. Verify",
|
|
65
|
-
'Ask Claude: "List my LinWheel posts" โ if it works, you\'re all set.',
|
|
66
|
-
"",
|
|
67
|
-
`Dashboard: ${BASE_URL}/dashboard`,
|
|
68
|
-
`Docs: https://github.com/Peleke/linwheel/tree/main/mcp#readme`,
|
|
69
|
-
].join("\n"),
|
|
90
|
+
text: `Login ${poll.status}. Start over: call \`linwheel_setup\` with no arguments.`,
|
|
70
91
|
},
|
|
71
92
|
],
|
|
72
93
|
};
|
|
73
94
|
});
|
|
74
|
-
console.error("LinWheel MCP: No
|
|
95
|
+
console.error("LinWheel MCP: No key configured. Run the linwheel_setup tool to log in.");
|
|
75
96
|
}
|
|
76
97
|
const transport = new StdioServerTransport();
|
|
77
98
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function register(server, client) {
|
|
2
|
+
server.tool("linwheel_carousels_scheduled", "List all scheduled carousels as calendar items. Returns both post carousels and article carousels with status='scheduled', including parent content references, caption, pageCount, scheduledAt, and autoPublish. Use to see what carousels are queued for publication. ~500 tokens.", {}, async () => {
|
|
3
|
+
const result = await client.get("/api/agent/carousels/scheduled");
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
6
|
+
};
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=carousels-scheduled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"carousels-scheduled.js","sourceRoot":"","sources":["../../src/tools/carousels-scheduled.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,8BAA8B,EAC9B,sRAAsR,EACtR,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAClE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -14,8 +14,15 @@ import * as postCarousel from "./post-carousel.js";
|
|
|
14
14
|
import * as postCarouselCompanion from "./post-carousel-companion.js";
|
|
15
15
|
import * as postCarouselStatus from "./post-carousel-status.js";
|
|
16
16
|
import * as postCarouselSchedule from "./post-carousel-schedule.js";
|
|
17
|
+
import * as postCarouselCompile from "./post-carousel-compile.js";
|
|
18
|
+
import * as postCarouselRegenerate from "./post-carousel-regenerate.js";
|
|
19
|
+
import * as postCarouselDelete from "./post-carousel-delete.js";
|
|
20
|
+
import * as postCarouselCaption from "./post-carousel-caption.js";
|
|
21
|
+
import * as carouselsScheduled from "./carousels-scheduled.js";
|
|
17
22
|
import * as postComment from "./post-comment.js";
|
|
18
23
|
import * as voiceProfiles from "./voice-profiles.js";
|
|
24
|
+
import * as voiceExtract from "./voice-extract.js";
|
|
25
|
+
import * as onboard from "./onboard.js";
|
|
19
26
|
import * as brandStyles from "./brand-styles.js";
|
|
20
27
|
const ALL_TOOLS = [
|
|
21
28
|
analyze,
|
|
@@ -34,8 +41,15 @@ const ALL_TOOLS = [
|
|
|
34
41
|
postCarouselCompanion,
|
|
35
42
|
postCarouselStatus,
|
|
36
43
|
postCarouselSchedule,
|
|
44
|
+
postCarouselCompile,
|
|
45
|
+
postCarouselRegenerate,
|
|
46
|
+
postCarouselDelete,
|
|
47
|
+
postCarouselCaption,
|
|
48
|
+
carouselsScheduled,
|
|
37
49
|
postComment,
|
|
38
50
|
voiceProfiles,
|
|
51
|
+
voiceExtract,
|
|
52
|
+
onboard,
|
|
39
53
|
brandStyles,
|
|
40
54
|
];
|
|
41
55
|
export function registerAllTools(server, client) {
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,qBAAqB,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,oBAAoB,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,MAAM,SAAS,GAAG;IAChB,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,OAAO;IACP,UAAU;IACV,WAAW;IACX,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,MAAsB;IACxE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,qBAAqB,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,oBAAoB,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,mBAAmB,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,sBAAsB,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,mBAAmB,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,kBAAkB,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,MAAM,SAAS,GAAG;IAChB,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,OAAO;IACP,UAAU;IACV,WAAW;IACX,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,oBAAoB;IACpB,mBAAmB;IACnB,sBAAsB;IACtB,kBAAkB;IAClB,mBAAmB;IACnB,kBAAkB;IAClB,WAAW;IACX,aAAa;IACb,YAAY;IACZ,OAAO;IACP,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,MAAsB;IACxE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_onboard", 'Guided onboarding โ walks you from zero to your first scheduled LinkedIn post. Call it with no arguments (or step="status") to see exactly where you are and what to do next: it derives your progress from live data (voice profile, drafts, scheduled posts), explains WHY each step matters, and hands back the precise next tool to call. It performs the voice-profile step directly โ call with step="voice" and 3+ of your own writing samples to build and activate a voice profile (off-voice/AI-slop samples are dropped automatically). If you are new to LinWheel, start here.', {
|
|
4
|
+
step: z
|
|
5
|
+
.enum(["status", "voice", "compose", "schedule", "done"])
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('Which step to run. Default "status" reports your current state and the recommended next action. "voice" builds a voice profile from `samples`.'),
|
|
8
|
+
samples: z
|
|
9
|
+
.array(z.string())
|
|
10
|
+
.optional()
|
|
11
|
+
.describe('For step="voice": 3+ of your own writing samples (posts, articles) to extract a voice profile from.'),
|
|
12
|
+
voiceName: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('For step="voice": an optional name for the created profile.'),
|
|
16
|
+
}, async (args) => {
|
|
17
|
+
const result = await client.post("/api/agent/onboard", args);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=onboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/tools/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,4jBAA4jB,EAC5jB;QACE,IAAI,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;aACxD,QAAQ,EAAE;aACV,QAAQ,CACP,gJAAgJ,CACjJ;QACH,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CACP,qGAAqG,CACtG;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,6DAA6D,CAAC;KAC3E,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACjE;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_caption", "Update the companion caption text for a post's carousel without regenerating slides. Use this to edit the LinkedIn caption that accompanies the carousel PDF. Returns the updated caption and carouselId. ~100 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel caption to update"),
|
|
5
|
+
caption: z.string().describe("The new caption text for the carousel"),
|
|
6
|
+
}, async ({ postId, caption }) => {
|
|
7
|
+
const result = await client.put(`/api/agent/posts/${postId}/carousel`, { caption });
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=post-carousel-caption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-caption.js","sourceRoot":"","sources":["../../src/tools/post-carousel-caption.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,wNAAwN,EACxN;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;QAC3E,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;KACtE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAC7B,oBAAoB,MAAM,WAAW,EACrC,EAAE,OAAO,EAAE,CACZ,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_compile", "Compile carousel slides into a PDF document. Use this when a carousel has slides but no PDF (carouselUrl is null) โ typically for carousels created before automatic PDF compilation was added. Returns carouselId, carouselUrl, and pageCount. ~150 tokens. Call linwheel_post_carousel_status first to check if compilation is needed.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel to compile"),
|
|
5
|
+
}, async ({ postId }) => {
|
|
6
|
+
const result = await client.post(`/api/agent/posts/${postId}/carousel/compile`, {});
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=post-carousel-compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-compile.js","sourceRoot":"","sources":["../../src/tools/post-carousel-compile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,0UAA0U,EAC1U;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;KACrE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAC9B,oBAAoB,MAAM,mBAAmB,EAC7C,EAAE,CACH,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_delete", "Delete a carousel from a post entirely. Use this to remove an incorrectly generated or unwanted carousel. Cannot delete published carousels. Returns success confirmation. ~100 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel to delete"),
|
|
5
|
+
}, async ({ postId }) => {
|
|
6
|
+
const result = await client.delete(`/api/agent/posts/${postId}/carousel`);
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=post-carousel-delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-delete.js","sourceRoot":"","sources":["../../src/tools/post-carousel-delete.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,+BAA+B,EAC/B,yLAAyL,EACzL;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;KACpE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,oBAAoB,MAAM,WAAW,CACtC,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_regenerate", "Regenerate a single slide in an existing carousel. Use this to fix a typo or rephrase one slide without regenerating the entire carousel. Specify the 0-based slideIndex and the new headlineText. Returns the updated slide. ~200 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel slide to regenerate"),
|
|
5
|
+
slideIndex: z
|
|
6
|
+
.number()
|
|
7
|
+
.int()
|
|
8
|
+
.min(0)
|
|
9
|
+
.describe("0-based index of the slide to regenerate"),
|
|
10
|
+
headlineText: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("New headline text for the slide"),
|
|
13
|
+
caption: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("New caption for the slide (omit to keep current)"),
|
|
17
|
+
}, async ({ postId, slideIndex, headlineText, caption }) => {
|
|
18
|
+
const body = { slideIndex, headlineText };
|
|
19
|
+
if (caption !== undefined)
|
|
20
|
+
body.caption = caption;
|
|
21
|
+
const result = await client.patch(`/api/agent/posts/${postId}/carousel`, body);
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=post-carousel-regenerate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-regenerate.js","sourceRoot":"","sources":["../../src/tools/post-carousel-regenerate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,mCAAmC,EACnC,4OAA4O,EAC5O;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;QAC7E,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,0CAA0C,CAAC;QACvD,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,CAAC,iCAAiC,CAAC;QAC9C,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kDAAkD,CAAC;KAChE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE;QACtD,MAAM,IAAI,GAA4B,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAElD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,oBAAoB,MAAM,WAAW,EACrC,IAAI,CACL,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_voice_extract", "Extract a clean voice profile from writing samples โ or re-extract from an existing profile to de-poison it. Runs a validation pass that judges each sample against the dominant voice and REJECTS off-voice outliers (generic/AI-slop/boilerplate, or a different register) so they don't poison few-shot style matching, then synthesizes concrete style notes from the samples that survive. By default it's a dry run that returns per-sample keep/reject verdicts with reasons; set save=true to persist the curated profile. With fromProfileId + updateInPlace=true it cleans an existing profile in place.", {
|
|
4
|
+
samples: z
|
|
5
|
+
.array(z.string())
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Raw candidate writing samples to extract a voice from."),
|
|
8
|
+
fromProfileId: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Re-extract from an existing profile's samples (de-poison). Provide this or samples (or both)."),
|
|
12
|
+
save: z
|
|
13
|
+
.boolean()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Persist the curated profile. Default false โ a dry run returns verdicts without saving."),
|
|
16
|
+
updateInPlace: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("With fromProfileId + save: update that profile in place instead of creating a new one."),
|
|
20
|
+
name: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Name for the saved profile (defaults to the suggested name)."),
|
|
24
|
+
activate: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Set the profile as active when saving."),
|
|
28
|
+
minKeep: z
|
|
29
|
+
.number()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Minimum samples to keep (default 2); guards against over-aggressive rejection producing a too-thin profile."),
|
|
32
|
+
}, async (args) => {
|
|
33
|
+
const result = await client.post("/api/agent/voice-profiles/extract", args);
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=voice-extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-extract.js","sourceRoot":"","sources":["../../src/tools/voice-extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,olBAAolB,EACplB;QACE,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,wDAAwD,CAAC;QACrE,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,+FAA+F,CAChG;QACH,IAAI,EAAE,CAAC;aACJ,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CACP,yFAAyF,CAC1F;QACH,aAAa,EAAE,CAAC;aACb,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CACP,wFAAwF,CACzF;QACH,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,8DAA8D,CAAC;QAC3E,QAAQ,EAAE,CAAC;aACR,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,wCAAwC,CAAC;QACrD,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,6GAA6G,CAC9G;KACJ,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAC9B,mCAAmC,EACnC,IAAI,CACL,CAAC;QACF,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACjE;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linwheel/mcp-server",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server for LinWheel โ AI-powered LinkedIn content engine. Draft, schedule, and publish posts from Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"zod": "^3.24.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.5.0",
|
|
24
25
|
"tsx": "^4.19.0",
|
|
25
26
|
"typescript": "^5.9.3",
|
|
26
27
|
"vitest": "^3.2.1"
|