rote-toolkit 0.2.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 ADDED
@@ -0,0 +1,125 @@
1
+ # Rote Toolkit
2
+
3
+ Rote Toolkit 是一个基于 TypeScript 的增强工具包,主要用于在终端或 AI Agents 侧连接和增强你的 [Rote](https://rote.ink) 笔记系统。基于 Rote OpenKey API 授权,即插即用,无需繁复的登录流程。
4
+
5
+ ## 特性
6
+
7
+ - **CLI 模式**:通过终端快速记笔记、搜索笔记。
8
+ - **MCP 模式**:作为 Model Context Protocol 服务端,让 AI (Claude/Cursor) 能够安全、规范地读写 Rote 笔记。
9
+ - **无感鉴权**:只需一次配置 OpenKey 即可长期使用。
10
+
11
+ ## 安装
12
+
13
+ > 要求 Node.js v18 或更高版本。
14
+
15
+ ```bash
16
+ npm install -g rote-toolkit
17
+ ```
18
+
19
+ ## 配置鉴权
20
+
21
+ 运行以下命令进行全局配置:
22
+
23
+ ```bash
24
+ rote login
25
+ ```
26
+
27
+ 系统会提示你输入:
28
+ 1. **Rote API URL**:例如 `https://your-rote-domain.com`
29
+ 2. **OpenKey**:你的 API 密钥
30
+
31
+ 凭证会保存在本地:`~/.rote-toolkit/config.json`。
32
+
33
+ ## CLI 模式使用指南
34
+
35
+ ### 1) 快速记录笔记
36
+
37
+ ```bash
38
+ rote add "今天学到了 MCP 协议,非常有趣!"
39
+ ```
40
+
41
+ 附带标签:
42
+
43
+ ```bash
44
+ rote add "实现了一个新的前端组件" -t "代码,前端,React"
45
+ ```
46
+
47
+ ### 2) 搜索笔记
48
+
49
+ ```bash
50
+ rote search "MCP"
51
+ ```
52
+
53
+ ## MCP 模式使用指南
54
+
55
+ 启动 MCP Server:
56
+
57
+ ```bash
58
+ rote mcp
59
+ ```
60
+
61
+ 或独立命令:
62
+
63
+ ```bash
64
+ rote-mcp
65
+ ```
66
+
67
+ ### Claude Desktop 配置示例
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "rote-toolkit": {
73
+ "command": "npx",
74
+ "args": [
75
+ "-y",
76
+ "rote-toolkit",
77
+ "mcp"
78
+ ]
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### AI 可使用的能力 (Tools)
85
+
86
+ - `rote_create_note`
87
+ - `rote_search_notes`
88
+ - `rote_list_notes`
89
+
90
+ ## 本地开发
91
+ [text](../../../Downloads/API-KEY-GUIDE.md)
92
+ ```bash
93
+ npm install
94
+ npm run build
95
+ npm run dev -- --help
96
+ ```
97
+
98
+ ## 发布到 npm
99
+
100
+ 首次发布前先登录:
101
+
102
+ ```bash
103
+ npm login
104
+ ```
105
+
106
+ 自动构建 + 自动升级版本 + 发布:
107
+
108
+ ```bash
109
+ npm run release:patch
110
+ ```
111
+
112
+ 也支持:
113
+
114
+ ```bash
115
+ npm run release:minor
116
+ npm run release:major
117
+ ```
118
+
119
+ 发布脚本会执行:
120
+ 1. 检查 git 工作区是否干净
121
+ 2. 检查 npm 登录状态
122
+ 3. `npm run build`
123
+ 4. `npm pack --dry-run`
124
+ 5. `npm version <patch|minor|major>`
125
+ 6. `npm publish`
package/dist/api.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { AddReactionInput, CreateArticleInput, CreateNoteInput, ListNotesInput, RemoveReactionInput, RemoveReactionResponse, RoteArticle, RoteNote, RotePermissions, RoteProfile, RoteReaction, SearchNotesInput, ToolkitConfig, UpdateProfileInput } from "./types.js";
2
+ export declare class RoteClient {
3
+ private readonly apiUrl;
4
+ private readonly openKey;
5
+ constructor(config?: ToolkitConfig);
6
+ createNote(input: CreateNoteInput): Promise<RoteNote>;
7
+ searchNotes(input: SearchNotesInput): Promise<RoteNote[]>;
8
+ listNotes(input?: ListNotesInput): Promise<RoteNote[]>;
9
+ createArticle(input: CreateArticleInput): Promise<RoteArticle>;
10
+ addReaction(input: AddReactionInput): Promise<RoteReaction>;
11
+ removeReaction(input: RemoveReactionInput): Promise<RemoveReactionResponse>;
12
+ getProfile(): Promise<RoteProfile>;
13
+ updateProfile(input: UpdateProfileInput): Promise<RoteProfile>;
14
+ getPermissions(): Promise<RotePermissions>;
15
+ private request;
16
+ }
package/dist/api.js ADDED
@@ -0,0 +1,120 @@
1
+ import { loadConfig } from "./config.js";
2
+ export class RoteClient {
3
+ apiUrl;
4
+ openKey;
5
+ constructor(config) {
6
+ const resolved = config ?? loadConfig();
7
+ this.apiUrl = resolved.apiUrl;
8
+ this.openKey = resolved.openKey;
9
+ }
10
+ async createNote(input) {
11
+ if (!input.content?.trim()) {
12
+ throw new Error("content is required");
13
+ }
14
+ const body = {
15
+ openkey: this.openKey,
16
+ content: input.content,
17
+ title: input.title ?? "",
18
+ tags: input.tags ?? [],
19
+ state: input.state ?? "private",
20
+ type: input.type ?? "rote",
21
+ pin: input.pin ?? false,
22
+ ...(input.articleId ? { articleId: input.articleId } : {}),
23
+ };
24
+ return this.request("/v2/api/openkey/notes", {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify(body),
28
+ });
29
+ }
30
+ async searchNotes(input) {
31
+ if (!input.keyword?.trim()) {
32
+ throw new Error("keyword is required");
33
+ }
34
+ const params = new URLSearchParams({
35
+ openkey: this.openKey,
36
+ keyword: input.keyword,
37
+ limit: String(input.limit ?? 10),
38
+ skip: String(input.skip ?? 0),
39
+ });
40
+ return this.request(`/v2/api/openkey/notes/search?${params.toString()}`);
41
+ }
42
+ async listNotes(input = {}) {
43
+ const params = new URLSearchParams({
44
+ openkey: this.openKey,
45
+ limit: String(input.limit ?? 10),
46
+ skip: String(input.skip ?? 0),
47
+ });
48
+ return this.request(`/v2/api/openkey/notes?${params.toString()}`);
49
+ }
50
+ async createArticle(input) {
51
+ if (!input.content?.trim()) {
52
+ throw new Error("content is required");
53
+ }
54
+ const body = {
55
+ openkey: this.openKey,
56
+ content: input.content,
57
+ };
58
+ return this.request("/v2/api/openkey/articles", {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify(body),
62
+ });
63
+ }
64
+ async addReaction(input) {
65
+ const body = {
66
+ openkey: this.openKey,
67
+ type: input.type,
68
+ roteid: input.roteid,
69
+ ...(input.metadata ? { metadata: input.metadata } : {}),
70
+ };
71
+ return this.request("/v2/api/openkey/reactions", {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify(body),
75
+ });
76
+ }
77
+ async removeReaction(input) {
78
+ const params = new URLSearchParams({ openkey: this.openKey });
79
+ return this.request(`/v2/api/openkey/reactions/${encodeURIComponent(input.roteid)}/${encodeURIComponent(input.type)}?${params.toString()}`, { method: "DELETE" });
80
+ }
81
+ async getProfile() {
82
+ const params = new URLSearchParams({ openkey: this.openKey });
83
+ return this.request(`/v2/api/openkey/profile?${params.toString()}`);
84
+ }
85
+ async updateProfile(input) {
86
+ const body = {
87
+ openkey: this.openKey,
88
+ ...input,
89
+ };
90
+ return this.request("/v2/api/openkey/profile", {
91
+ method: "PUT",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify(body),
94
+ });
95
+ }
96
+ async getPermissions() {
97
+ const params = new URLSearchParams({ openkey: this.openKey });
98
+ return this.request(`/v2/api/openkey/permissions?${params.toString()}`);
99
+ }
100
+ async request(path, init) {
101
+ const url = `${this.apiUrl}${path}`;
102
+ const response = await fetch(url, init);
103
+ const text = await response.text();
104
+ let payload = null;
105
+ try {
106
+ payload = JSON.parse(text);
107
+ }
108
+ catch {
109
+ // keep null, handled below
110
+ }
111
+ if (!response.ok) {
112
+ const message = payload?.message || text || `HTTP ${response.status}`;
113
+ throw new Error(`Rote API request failed: ${message}`);
114
+ }
115
+ if (!payload) {
116
+ throw new Error("Rote API returned non-JSON response.");
117
+ }
118
+ return payload.data;
119
+ }
120
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { stdin as input, stdout as output } from "node:process";
5
+ import { getConfigPath, saveConfig } from "./config.js";
6
+ import { RoteClient } from "./api.js";
7
+ import { printNotes } from "./output.js";
8
+ import { startMcpServer } from "./mcp.js";
9
+ const program = new Command();
10
+ program.name("rote").description("Rote Toolkit CLI").version("0.1.0");
11
+ program
12
+ .command("login")
13
+ .description("Configure Rote API URL and OpenKey")
14
+ .action(async () => {
15
+ const rl = createInterface({ input, output });
16
+ try {
17
+ const apiUrl = await rl.question("Rote API URL: ");
18
+ const openKey = await rl.question("OpenKey: ");
19
+ saveConfig({ apiUrl, openKey });
20
+ console.log(`Saved config to ${getConfigPath()}`);
21
+ }
22
+ finally {
23
+ rl.close();
24
+ }
25
+ });
26
+ program
27
+ .command("add")
28
+ .description("Create a note")
29
+ .argument("<content>", "note content")
30
+ .option("-t, --tags <tags>", "comma-separated tags")
31
+ .option("--title <title>", "title")
32
+ .option("--state <state>", "note state", "private")
33
+ .option("--article-id <articleId>", "bind to an existing article")
34
+ .action(async (content, options) => {
35
+ const client = new RoteClient();
36
+ const tags = parseTags(options.tags);
37
+ const note = await client.createNote({
38
+ content,
39
+ tags,
40
+ title: options.title,
41
+ state: options.state,
42
+ articleId: options.articleId,
43
+ });
44
+ console.log(`Created note: ${note.id}`);
45
+ });
46
+ program
47
+ .command("article")
48
+ .description("Manage articles")
49
+ .argument("<action>", "action to perform (add)")
50
+ .argument("[content]", "article content")
51
+ .action(async (action, content) => {
52
+ if (action === "add" && content) {
53
+ const client = new RoteClient();
54
+ const article = await client.createArticle({ content });
55
+ console.log(`Created article: ${article.id}`);
56
+ }
57
+ else {
58
+ console.error("Invalid action or missing content");
59
+ }
60
+ });
61
+ program
62
+ .command("reaction")
63
+ .description("Manage reactions")
64
+ .argument("<action>", "action to perform (add|remove)")
65
+ .argument("<roteid>", "note ID")
66
+ .argument("<type>", "reaction type (e.g., like)")
67
+ .action(async (action, roteid, type) => {
68
+ const client = new RoteClient();
69
+ if (action === "add") {
70
+ const reaction = await client.addReaction({ roteid, type });
71
+ console.log(`Added reaction: ${reaction.id}`);
72
+ }
73
+ else if (action === "remove") {
74
+ const result = await client.removeReaction({ roteid, type });
75
+ console.log(`Removed reactions count: ${result.count}`);
76
+ }
77
+ else {
78
+ console.error("Invalid action");
79
+ }
80
+ });
81
+ program
82
+ .command("profile")
83
+ .description("Manage user profile")
84
+ .argument("<action>", "action to perform (get|update)")
85
+ .option("--nickname <nickname>", "new nickname")
86
+ .option("--description <description>", "new description")
87
+ .option("--username <username>", "new username")
88
+ .action(async (action, options) => {
89
+ const client = new RoteClient();
90
+ if (action === "get") {
91
+ const profile = await client.getProfile();
92
+ console.log(JSON.stringify(profile, null, 2));
93
+ }
94
+ else if (action === "update") {
95
+ const profile = await client.updateProfile({
96
+ nickname: options.nickname,
97
+ description: options.description,
98
+ username: options.username,
99
+ });
100
+ console.log(`Updated profile for: ${profile.username}`);
101
+ }
102
+ else {
103
+ console.error("Invalid action");
104
+ }
105
+ });
106
+ program
107
+ .command("permissions")
108
+ .description("Check API key permissions")
109
+ .action(async () => {
110
+ const client = new RoteClient();
111
+ const result = await client.getPermissions();
112
+ console.log(`Permissions: ${result.permissions.join(", ")}`);
113
+ });
114
+ program
115
+ .command("search")
116
+ .description("Search notes by keyword")
117
+ .argument("<keyword>", "search keyword")
118
+ .option("-l, --limit <limit>", "max results", parseInt, 10)
119
+ .option("-s, --skip <skip>", "offset", parseInt, 0)
120
+ .action(async (keyword, options) => {
121
+ const client = new RoteClient();
122
+ const notes = await client.searchNotes({
123
+ keyword,
124
+ limit: options.limit,
125
+ skip: options.skip,
126
+ });
127
+ printNotes(notes);
128
+ });
129
+ program
130
+ .command("mcp")
131
+ .description("Start MCP server over stdio")
132
+ .action(async () => {
133
+ await startMcpServer();
134
+ });
135
+ program.parseAsync(process.argv).catch((error) => {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ console.error(`Error: ${message}`);
138
+ process.exit(1);
139
+ });
140
+ function parseTags(tagsRaw) {
141
+ if (!tagsRaw) {
142
+ return [];
143
+ }
144
+ return tagsRaw
145
+ .split(",")
146
+ .map((tag) => tag.trim())
147
+ .filter(Boolean);
148
+ }
@@ -0,0 +1,5 @@
1
+ import type { ToolkitConfig } from './types.js';
2
+ export declare function getConfigPath(): string;
3
+ export declare function saveConfig(config: ToolkitConfig): void;
4
+ export declare function loadConfig(): ToolkitConfig;
5
+ export declare function normalizeApiUrl(apiUrl: string): string;
package/dist/config.js ADDED
@@ -0,0 +1,37 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const CONFIG_DIR = join(homedir(), '.rote-toolkit');
5
+ const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
6
+ export function getConfigPath() {
7
+ return CONFIG_PATH;
8
+ }
9
+ export function saveConfig(config) {
10
+ if (!config.apiUrl || !config.openKey) {
11
+ throw new Error('apiUrl and openKey are required.');
12
+ }
13
+ const normalized = {
14
+ apiUrl: normalizeApiUrl(config.apiUrl),
15
+ openKey: config.openKey.trim(),
16
+ };
17
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
18
+ writeFileSync(CONFIG_PATH, JSON.stringify(normalized, null, 2), 'utf8');
19
+ }
20
+ export function loadConfig() {
21
+ if (!existsSync(CONFIG_PATH)) {
22
+ throw new Error(`Config not found at ${CONFIG_PATH}. Run "rote login" first.`);
23
+ }
24
+ const raw = readFileSync(CONFIG_PATH, 'utf8');
25
+ const parsed = JSON.parse(raw);
26
+ if (!parsed.apiUrl || !parsed.openKey) {
27
+ throw new Error(`Invalid config at ${CONFIG_PATH}. Run "rote login" again.`);
28
+ }
29
+ return {
30
+ apiUrl: normalizeApiUrl(parsed.apiUrl),
31
+ openKey: parsed.openKey.trim(),
32
+ };
33
+ }
34
+ export function normalizeApiUrl(apiUrl) {
35
+ const trimmed = apiUrl.trim().replace(/\/$/, '');
36
+ return trimmed;
37
+ }
@@ -0,0 +1,3 @@
1
+ export * from './api.js';
2
+ export * from './config.js';
3
+ export * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './api.js';
2
+ export * from './config.js';
3
+ export * from './types.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { startMcpServer } from './mcp.js';
3
+ startMcpServer().catch((error) => {
4
+ const message = error instanceof Error ? error.message : String(error);
5
+ console.error(`Failed to start MCP server: ${message}`);
6
+ process.exit(1);
7
+ });
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function startMcpServer(): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,210 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import * as z from "zod/v4";
4
+ import { RoteClient } from "./api.js";
5
+ import { truncateSingleLine } from "./output.js";
6
+ export async function startMcpServer() {
7
+ const client = new RoteClient();
8
+ const server = new McpServer({
9
+ name: "rote-toolkit",
10
+ version: "0.1.0",
11
+ });
12
+ server.registerTool("rote_create_note", {
13
+ description: "Create a note in Rote via OpenKey API.",
14
+ inputSchema: {
15
+ content: z.string().min(1).describe("Note content"),
16
+ title: z.string().optional().describe("Optional note title"),
17
+ tags: z.array(z.string()).optional().describe("Optional list of tags"),
18
+ state: z.string().optional().describe("Note state, default private"),
19
+ articleId: z
20
+ .string()
21
+ .optional()
22
+ .describe("Optional article ID to bind to"),
23
+ },
24
+ }, async ({ content, title, tags, state, articleId }) => {
25
+ const note = await client.createNote({
26
+ content,
27
+ title,
28
+ tags,
29
+ state,
30
+ articleId,
31
+ });
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `Created note ${note.id}: ${truncateSingleLine(note.content, 100)}`,
37
+ },
38
+ ],
39
+ };
40
+ });
41
+ server.registerTool("rote_create_article", {
42
+ description: "Create an article in Rote via OpenKey API.",
43
+ inputSchema: {
44
+ content: z.string().min(1).describe("Article content"),
45
+ },
46
+ }, async ({ content }) => {
47
+ const article = await client.createArticle({ content });
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `Created article ${article.id}: ${truncateSingleLine(article.content, 100)}`,
53
+ },
54
+ ],
55
+ };
56
+ });
57
+ server.registerTool("rote_add_reaction", {
58
+ description: "Add a reaction to a note.",
59
+ inputSchema: {
60
+ roteid: z.string().describe("Note ID"),
61
+ type: z.string().describe("Reaction type (e.g., like)"),
62
+ metadata: z
63
+ .record(z.string(), z.unknown())
64
+ .optional()
65
+ .describe("Optional metadata"),
66
+ },
67
+ }, async ({ roteid, type, metadata }) => {
68
+ const reaction = await client.addReaction({ roteid, type, metadata });
69
+ return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: `Added reaction ${reaction.id} of type ${reaction.type} to note ${reaction.roteid}`,
74
+ },
75
+ ],
76
+ };
77
+ });
78
+ server.registerTool("rote_remove_reaction", {
79
+ description: "Remove a reaction from a note.",
80
+ inputSchema: {
81
+ roteid: z.string().describe("Note ID"),
82
+ type: z.string().describe("Reaction type (e.g., like)"),
83
+ },
84
+ }, async ({ roteid, type }) => {
85
+ const result = await client.removeReaction({ roteid, type });
86
+ return {
87
+ content: [
88
+ {
89
+ type: "text",
90
+ text: `Removed reactions count: ${result.count}`,
91
+ },
92
+ ],
93
+ };
94
+ });
95
+ server.registerTool("rote_get_profile", {
96
+ description: "Get user profile information.",
97
+ inputSchema: {},
98
+ }, async () => {
99
+ const profile = await client.getProfile();
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: JSON.stringify(profile, null, 2),
105
+ },
106
+ ],
107
+ };
108
+ });
109
+ server.registerTool("rote_update_profile", {
110
+ description: "Update user profile information.",
111
+ inputSchema: {
112
+ nickname: z.string().optional().describe("New nickname"),
113
+ description: z.string().optional().describe("New description"),
114
+ avatar: z.string().optional().describe("New avatar URL"),
115
+ cover: z.string().optional().describe("New cover URL"),
116
+ username: z.string().optional().describe("New username"),
117
+ },
118
+ }, async ({ nickname, description, avatar, cover, username }) => {
119
+ const profile = await client.updateProfile({
120
+ nickname,
121
+ description,
122
+ avatar,
123
+ cover,
124
+ username,
125
+ });
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `Updated profile for: ${profile.username}`,
131
+ },
132
+ ],
133
+ };
134
+ });
135
+ server.registerTool("rote_get_permissions", {
136
+ description: "Check API key permissions.",
137
+ inputSchema: {},
138
+ }, async () => {
139
+ const result = await client.getPermissions();
140
+ return {
141
+ content: [
142
+ {
143
+ type: "text",
144
+ text: `Permissions: ${result.permissions.join(", ")}`,
145
+ },
146
+ ],
147
+ };
148
+ });
149
+ server.registerTool("rote_search_notes", {
150
+ description: "Search notes in Rote by keyword.",
151
+ inputSchema: {
152
+ keyword: z.string().min(1).describe("Search keyword"),
153
+ limit: z
154
+ .number()
155
+ .int()
156
+ .min(1)
157
+ .max(50)
158
+ .optional()
159
+ .describe("Max results, default 10"),
160
+ skip: z
161
+ .number()
162
+ .int()
163
+ .min(0)
164
+ .optional()
165
+ .describe("Pagination offset, default 0"),
166
+ },
167
+ }, async ({ keyword, limit, skip }) => {
168
+ const notes = await client.searchNotes({ keyword, limit, skip });
169
+ const lines = notes.map((note, i) => `${i + 1}. ${truncateSingleLine(note.content, 100)}`);
170
+ return {
171
+ content: [
172
+ {
173
+ type: "text",
174
+ text: lines.length > 0 ? lines.join("\n") : "No notes found.",
175
+ },
176
+ ],
177
+ };
178
+ });
179
+ server.registerTool("rote_list_notes", {
180
+ description: "List recent notes in Rote.",
181
+ inputSchema: {
182
+ limit: z
183
+ .number()
184
+ .int()
185
+ .min(1)
186
+ .max(50)
187
+ .optional()
188
+ .describe("Max results, default 10"),
189
+ skip: z
190
+ .number()
191
+ .int()
192
+ .min(0)
193
+ .optional()
194
+ .describe("Pagination offset, default 0"),
195
+ },
196
+ }, async ({ limit, skip }) => {
197
+ const notes = await client.listNotes({ limit, skip });
198
+ const lines = notes.map((note, i) => `${i + 1}. ${truncateSingleLine(note.content, 100)}`);
199
+ return {
200
+ content: [
201
+ {
202
+ type: "text",
203
+ text: lines.length > 0 ? lines.join("\n") : "No notes found.",
204
+ },
205
+ ],
206
+ };
207
+ });
208
+ const transport = new StdioServerTransport();
209
+ await server.connect(transport);
210
+ }
@@ -0,0 +1,3 @@
1
+ import type { RoteNote } from './types.js';
2
+ export declare function printNotes(notes: RoteNote[]): void;
3
+ export declare function truncateSingleLine(text: string, max?: number): string;
package/dist/output.js ADDED
@@ -0,0 +1,19 @@
1
+ export function printNotes(notes) {
2
+ if (!Array.isArray(notes) || notes.length === 0) {
3
+ console.log('No notes found.');
4
+ return;
5
+ }
6
+ notes.forEach((note, idx) => {
7
+ const tags = note.tags?.length ? ` [${note.tags.join(', ')}]` : '';
8
+ const title = note.title ? `${note.title} - ` : '';
9
+ const content = truncateSingleLine(note.content || '', 120);
10
+ console.log(`${idx + 1}. ${title}${content}${tags}`);
11
+ });
12
+ }
13
+ export function truncateSingleLine(text, max = 120) {
14
+ const compact = text.replace(/\s+/g, ' ').trim();
15
+ if (compact.length <= max) {
16
+ return compact;
17
+ }
18
+ return `${compact.slice(0, Math.max(0, max - 3))}...`;
19
+ }
@@ -0,0 +1,90 @@
1
+ export interface ToolkitConfig {
2
+ apiUrl: string;
3
+ openKey: string;
4
+ }
5
+ export interface RoteNote {
6
+ id: string;
7
+ content: string;
8
+ title?: string;
9
+ tags?: string[];
10
+ state?: string;
11
+ type?: string;
12
+ createdAt?: string;
13
+ updatedAt?: string;
14
+ }
15
+ export interface ApiEnvelope<T> {
16
+ code: number;
17
+ message: string;
18
+ data: T;
19
+ }
20
+ export interface CreateNoteInput {
21
+ content: string;
22
+ title?: string;
23
+ tags?: string[];
24
+ state?: string;
25
+ type?: string;
26
+ pin?: boolean;
27
+ articleId?: string;
28
+ }
29
+ export interface RoteArticle {
30
+ id: string;
31
+ content: string;
32
+ authorId: string;
33
+ createdAt: string;
34
+ updatedAt: string;
35
+ }
36
+ export interface CreateArticleInput {
37
+ content: string;
38
+ }
39
+ export interface RoteReaction {
40
+ id: string;
41
+ type: string;
42
+ roteid: string;
43
+ userid: string;
44
+ }
45
+ export interface AddReactionInput {
46
+ type: string;
47
+ roteid: string;
48
+ metadata?: Record<string, unknown>;
49
+ }
50
+ export interface RemoveReactionInput {
51
+ type: string;
52
+ roteid: string;
53
+ }
54
+ export interface RemoveReactionResponse {
55
+ count: number;
56
+ }
57
+ export interface RoteProfile {
58
+ id: string;
59
+ email: string;
60
+ emailVerified?: boolean;
61
+ username: string;
62
+ nickname: string;
63
+ description: string;
64
+ avatar: string;
65
+ cover: string;
66
+ role: string;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ allowExplore?: boolean;
70
+ oauthBindings?: unknown[];
71
+ }
72
+ export interface UpdateProfileInput {
73
+ nickname?: string;
74
+ description?: string;
75
+ avatar?: string;
76
+ cover?: string;
77
+ username?: string;
78
+ }
79
+ export interface RotePermissions {
80
+ permissions: string[];
81
+ }
82
+ export interface SearchNotesInput {
83
+ keyword: string;
84
+ limit?: number;
85
+ skip?: number;
86
+ }
87
+ export interface ListNotesInput {
88
+ limit?: number;
89
+ skip?: number;
90
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "rote-toolkit",
3
+ "version": "0.2.0",
4
+ "description": "CLI and MCP toolkit for Rote OpenKey API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "rote": "dist/cli.js",
9
+ "rote-mcp": "dist/mcp-standalone.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "dev": "tsx src/cli.ts",
18
+ "mcp": "tsx src/mcp-standalone.ts",
19
+ "start": "node dist/cli.js",
20
+ "prepublishOnly": "npm run build",
21
+ "release": "bash scripts/release.sh",
22
+ "release:patch": "bash scripts/release.sh patch",
23
+ "release:minor": "bash scripts/release.sh minor",
24
+ "release:major": "bash scripts/release.sh major"
25
+ },
26
+ "keywords": [
27
+ "rote",
28
+ "cli",
29
+ "mcp",
30
+ "openkey"
31
+ ],
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.18.0",
38
+ "commander": "^14.0.1",
39
+ "zod": "^4.1.5"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^24.3.0",
43
+ "tsx": "^4.20.3",
44
+ "typescript": "^5.9.2"
45
+ }
46
+ }