@kitae9999/openlog-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # OpenLog CLI
2
+
3
+ Official CLI and MCP server for OpenLog.
4
+
5
+ ```bash
6
+ npx -y @kitae9999/openlog-cli login
7
+ npx -y @kitae9999/openlog-cli whoami
8
+ npx -y @kitae9999/openlog-cli mcp
9
+ ```
10
+
11
+ MCP client configuration:
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "openlog": {
17
+ "command": "npx",
18
+ "args": ["-y", "@kitae9999/openlog-cli", "mcp"]
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Set `OPENLOG_API_BASE_URL` to point at a non-production API.
@@ -0,0 +1,64 @@
1
+ import { getApiBaseUrl } from "./config.js";
2
+ export class ApiError extends Error {
3
+ status;
4
+ url;
5
+ constructor(status, url, message) {
6
+ super(message);
7
+ this.status = status;
8
+ this.url = url;
9
+ this.name = "ApiError";
10
+ }
11
+ }
12
+ export class OpenLogApiClient {
13
+ apiBaseUrl;
14
+ accessToken;
15
+ constructor(options = {}) {
16
+ this.apiBaseUrl = (options.apiBaseUrl ?? getApiBaseUrl()).replace(/\/$/, "");
17
+ this.accessToken = options.accessToken;
18
+ }
19
+ async get(path) {
20
+ return this.request(path, { method: "GET" });
21
+ }
22
+ async post(path, body) {
23
+ return this.request(path, {
24
+ method: "POST",
25
+ headers: body ? { "content-type": "application/json" } : undefined,
26
+ body: body ? JSON.stringify(body) : undefined,
27
+ });
28
+ }
29
+ async postNoContent(path, body) {
30
+ await this.request(path, {
31
+ method: "POST",
32
+ headers: body ? { "content-type": "application/json" } : undefined,
33
+ body: body ? JSON.stringify(body) : undefined,
34
+ });
35
+ }
36
+ async request(path, init) {
37
+ const url = `${this.apiBaseUrl}${path}`;
38
+ const response = await fetch(url, {
39
+ ...init,
40
+ headers: {
41
+ ...(init.headers ?? {}),
42
+ ...(this.accessToken
43
+ ? { cookie: `openlog_access_token=${this.accessToken}` }
44
+ : {}),
45
+ },
46
+ });
47
+ if (!response.ok) {
48
+ throw new ApiError(response.status, url, await readErrorMessage(response));
49
+ }
50
+ if (response.status === 204) {
51
+ return undefined;
52
+ }
53
+ return (await response.json());
54
+ }
55
+ }
56
+ async function readErrorMessage(response) {
57
+ try {
58
+ const body = (await response.json());
59
+ return body.message ?? `OpenLog API request failed with ${response.status}`;
60
+ }
61
+ catch {
62
+ return `OpenLog API request failed with ${response.status}`;
63
+ }
64
+ }
@@ -0,0 +1,43 @@
1
+ import { mkdir, readFile, rm, writeFile, chmod } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { getApiBaseUrl, getAuthFilePath } from "./config.js";
4
+ export async function readAuthFile() {
5
+ try {
6
+ const content = await readFile(getAuthFilePath(), "utf8");
7
+ const parsed = JSON.parse(content);
8
+ if (!parsed.accessToken || !parsed.apiBaseUrl || !parsed.createdAt) {
9
+ return null;
10
+ }
11
+ return {
12
+ accessToken: parsed.accessToken,
13
+ apiBaseUrl: parsed.apiBaseUrl,
14
+ createdAt: parsed.createdAt,
15
+ };
16
+ }
17
+ catch (error) {
18
+ if (error &&
19
+ typeof error === "object" &&
20
+ "code" in error &&
21
+ error.code === "ENOENT") {
22
+ return null;
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ export async function writeAuthFile(accessToken) {
28
+ const authFile = {
29
+ accessToken,
30
+ apiBaseUrl: getApiBaseUrl(),
31
+ createdAt: new Date().toISOString(),
32
+ };
33
+ const authFilePath = getAuthFilePath();
34
+ await mkdir(path.dirname(authFilePath), { recursive: true, mode: 0o700 });
35
+ await writeFile(authFilePath, `${JSON.stringify(authFile, null, 2)}\n`, {
36
+ mode: 0o600,
37
+ });
38
+ await chmod(authFilePath, 0o600);
39
+ return authFile;
40
+ }
41
+ export async function deleteAuthFile() {
42
+ await rm(getAuthFilePath(), { force: true });
43
+ }
@@ -0,0 +1,20 @@
1
+ import { spawn } from "node:child_process";
2
+ export function openBrowser(url) {
3
+ const command = process.platform === "darwin"
4
+ ? "open"
5
+ : process.platform === "win32"
6
+ ? "cmd"
7
+ : "xdg-open";
8
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
9
+ try {
10
+ const child = spawn(command, args, {
11
+ detached: true,
12
+ stdio: "ignore",
13
+ });
14
+ child.unref();
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
package/dist/config.js ADDED
@@ -0,0 +1,10 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ export const DEFAULT_API_BASE_URL = "https://api.openlog.kr/api";
4
+ export function getApiBaseUrl() {
5
+ return (process.env.OPENLOG_API_BASE_URL ?? DEFAULT_API_BASE_URL).replace(/\/$/, "");
6
+ }
7
+ export function getAuthFilePath() {
8
+ return (process.env.OPENLOG_AUTH_FILE ??
9
+ path.join(os.homedir(), ".openlog", "auth.json"));
10
+ }
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import { deleteAuthFile, readAuthFile } from "./auth-store.js";
3
+ import { login } from "./login.js";
4
+ import { runMcpServer } from "./mcp-server.js";
5
+ import { installMcp, printMcpInstallHelp } from "./mcp-install.js";
6
+ import { ApiError, OpenLogApiClient } from "./api-client.js";
7
+ const command = process.argv[2] ?? "help";
8
+ const subcommand = process.argv[3];
9
+ try {
10
+ if (command === "login") {
11
+ await login();
12
+ }
13
+ else if (command === "logout") {
14
+ await deleteAuthFile();
15
+ console.log("Logged out.");
16
+ }
17
+ else if (command === "whoami") {
18
+ await whoami();
19
+ }
20
+ else if (command === "mcp") {
21
+ if (subcommand === "install") {
22
+ await installMcpCommand();
23
+ }
24
+ else if (subcommand === undefined ||
25
+ subcommand === "serve" ||
26
+ subcommand === "start") {
27
+ printMcpStartupHint();
28
+ await runMcpServer();
29
+ }
30
+ else {
31
+ printMcpInstallHelp();
32
+ process.exit(1);
33
+ }
34
+ }
35
+ else {
36
+ printHelp();
37
+ process.exit(command === "help" || command === "--help" || command === "-h" ? 0 : 1);
38
+ }
39
+ }
40
+ catch (error) {
41
+ console.error(formatCliError(error));
42
+ process.exit(1);
43
+ }
44
+ async function installMcpCommand() {
45
+ const client = process.argv[4];
46
+ const printOnly = process.argv.includes("--print");
47
+ if (client !== "codex" &&
48
+ client !== "claude-code" &&
49
+ client !== "claude") {
50
+ printMcpInstallHelp();
51
+ process.exit(1);
52
+ }
53
+ await installMcp({
54
+ client,
55
+ printOnly,
56
+ });
57
+ }
58
+ async function whoami() {
59
+ const authFile = await readAuthFile();
60
+ if (!authFile) {
61
+ throw new Error("Not logged in. Run `openlog login` first.");
62
+ }
63
+ const apiClient = new OpenLogApiClient({
64
+ accessToken: authFile.accessToken,
65
+ apiBaseUrl: authFile.apiBaseUrl,
66
+ });
67
+ const me = await apiClient.get("/auth/me");
68
+ console.log(JSON.stringify(me, null, 2));
69
+ }
70
+ function printMcpStartupHint() {
71
+ if (!process.stderr.isTTY) {
72
+ return;
73
+ }
74
+ console.error(`OpenLog MCP server is running over stdio.
75
+
76
+ This terminal is now reserved for MCP protocol traffic.
77
+ Press Ctrl+C to stop it.
78
+
79
+ To use it from an MCP client, add:
80
+ {
81
+ "mcpServers": {
82
+ "openlog": {
83
+ "command": "openlog",
84
+ "args": ["mcp"]
85
+ }
86
+ }
87
+ }
88
+
89
+ Available tools:
90
+ get_auth_status
91
+ get_me
92
+ list_my_notifications
93
+ list_my_liked_posts
94
+ get_post_detail
95
+ `);
96
+ }
97
+ function printHelp() {
98
+ console.log(`OpenLog CLI
99
+
100
+ Usage:
101
+ openlog login Sign in to OpenLog from the CLI
102
+ openlog logout Remove local OpenLog credentials
103
+ openlog whoami Print the current OpenLog user
104
+ openlog mcp Start the OpenLog MCP stdio server
105
+ openlog mcp install codex
106
+ Register OpenLog MCP with Codex
107
+ openlog mcp install claude-code
108
+ Register OpenLog MCP with Claude Code
109
+
110
+ Environment:
111
+ OPENLOG_API_BASE_URL Override the OpenLog API base URL
112
+ OPENLOG_AUTH_FILE Override the local auth file path
113
+ `);
114
+ }
115
+ function formatCliError(error) {
116
+ if (error instanceof ApiError && error.status === 404) {
117
+ const hint = error.url.includes("localhost:8080/auth/")
118
+ ? "\n\nLocal hint: if your backend uses SERVER_SERVLET_CONTEXT_PATH=/api, run with OPENLOG_API_BASE_URL=http://localhost:8080/api."
119
+ : "";
120
+ return `${error.message}\nRequested: ${error.url}${hint}`;
121
+ }
122
+ return error instanceof Error ? error.message : String(error);
123
+ }
package/dist/login.js ADDED
@@ -0,0 +1,38 @@
1
+ import { OpenLogApiClient } from "./api-client.js";
2
+ import { openBrowser } from "./browser.js";
3
+ import { writeAuthFile } from "./auth-store.js";
4
+ export async function login() {
5
+ const apiClient = new OpenLogApiClient();
6
+ const deviceLogin = await apiClient.post("/auth/device/start");
7
+ const opened = openBrowser(deviceLogin.verificationUriComplete);
8
+ console.log("OpenLog CLI login");
9
+ console.log("");
10
+ console.log(`Code: ${deviceLogin.userCode}`);
11
+ console.log(`URL: ${deviceLogin.verificationUriComplete}`);
12
+ console.log("");
13
+ console.log(opened
14
+ ? "The approval page was opened in your browser."
15
+ : "Open the URL above in your browser.");
16
+ console.log("Waiting for approval...");
17
+ const accessToken = await pollForAccessToken(apiClient, deviceLogin);
18
+ await writeAuthFile(accessToken);
19
+ console.log("Login successful.");
20
+ }
21
+ async function pollForAccessToken(apiClient, deviceLogin) {
22
+ const deadline = Date.now() + deviceLogin.expiresIn * 1000;
23
+ let interval = deviceLogin.interval;
24
+ while (Date.now() < deadline) {
25
+ await sleep(interval * 1000);
26
+ const response = await apiClient.post("/auth/device/token", { deviceCode: deviceLogin.deviceCode });
27
+ if (response.status === "APPROVED") {
28
+ return response.accessToken;
29
+ }
30
+ interval = response.interval ?? interval;
31
+ }
32
+ throw new Error("CLI login expired before approval completed.");
33
+ }
34
+ function sleep(milliseconds) {
35
+ return new Promise((resolve) => {
36
+ setTimeout(resolve, milliseconds);
37
+ });
38
+ }
@@ -0,0 +1,199 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { DEFAULT_API_BASE_URL, getApiBaseUrl } from "./config.js";
6
+ export async function installMcp(options) {
7
+ const serverConfig = buildServerConfig();
8
+ if (options.printOnly) {
9
+ printConfig(options.client, serverConfig);
10
+ return;
11
+ }
12
+ if (options.client === "codex") {
13
+ await installCodex(serverConfig);
14
+ return;
15
+ }
16
+ await installClaudeCode(serverConfig);
17
+ }
18
+ export function printMcpInstallHelp() {
19
+ console.log(`OpenLog MCP install
20
+
21
+ Usage:
22
+ openlog mcp install codex
23
+ openlog mcp install claude-code
24
+ openlog mcp install codex --print
25
+ openlog mcp install claude-code --print
26
+
27
+ Aliases:
28
+ claude Same as claude-code
29
+
30
+ Notes:
31
+ If OPENLOG_API_BASE_URL is set, it is added to the client config.
32
+ `);
33
+ }
34
+ function buildServerConfig() {
35
+ const apiBaseUrl = getApiBaseUrl();
36
+ const env = {};
37
+ if (apiBaseUrl !== DEFAULT_API_BASE_URL) {
38
+ env.OPENLOG_API_BASE_URL = apiBaseUrl;
39
+ }
40
+ return {
41
+ command: "openlog",
42
+ args: ["mcp"],
43
+ env,
44
+ };
45
+ }
46
+ async function installCodex(serverConfig) {
47
+ const args = ["mcp", "add", "openlog"];
48
+ for (const [name, value] of Object.entries(serverConfig.env)) {
49
+ args.push("--env", `${name}=${value}`);
50
+ }
51
+ args.push("--", serverConfig.command, ...serverConfig.args);
52
+ let registeredWithCodexCli = false;
53
+ try {
54
+ registeredWithCodexCli =
55
+ (await runCommand("codex", args, { stdio: "pipe" })) === "executed";
56
+ }
57
+ catch (error) {
58
+ warnCodexFallback(error);
59
+ }
60
+ if (registeredWithCodexCli) {
61
+ console.log("OpenLog MCP server registered with Codex.");
62
+ console.log("Run `/mcp` inside Codex to confirm it is connected.");
63
+ return;
64
+ }
65
+ await writeCodexConfig(serverConfig);
66
+ console.log("OpenLog MCP server added to ~/.codex/config.toml.");
67
+ console.log("Restart Codex or reload MCP servers, then run `/mcp` to confirm.");
68
+ }
69
+ async function installClaudeCode(serverConfig) {
70
+ const args = ["mcp", "add", "--transport", "stdio", "--scope", "user"];
71
+ for (const [name, value] of Object.entries(serverConfig.env)) {
72
+ args.push("--env", `${name}=${value}`);
73
+ }
74
+ args.push("openlog", "--", serverConfig.command, ...serverConfig.args);
75
+ const result = await runCommand("claude", args);
76
+ if (result !== "executed") {
77
+ throw new Error("Claude Code CLI was not found. Install Claude Code first, then run `openlog mcp install claude-code` again.");
78
+ }
79
+ console.log("OpenLog MCP server registered with Claude Code.");
80
+ console.log("Run `/mcp` inside Claude Code to confirm it is connected.");
81
+ }
82
+ async function writeCodexConfig(serverConfig) {
83
+ const configPath = path.join(os.homedir(), ".codex", "config.toml");
84
+ await mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
85
+ const currentConfig = await readTextIfExists(configPath);
86
+ const nextConfig = upsertCodexServerBlock(currentConfig, serverConfig);
87
+ await writeFile(configPath, nextConfig, { mode: 0o600 });
88
+ }
89
+ function upsertCodexServerBlock(currentConfig, serverConfig) {
90
+ const block = buildCodexServerBlock(serverConfig);
91
+ const blockPattern = /(?:^|\n)\[mcp_servers\.openlog]\n[\s\S]*?(?:\n\[mcp_servers\.openlog\.env]\n[\s\S]*?)?(?=\n\[(?!mcp_servers\.openlog(?:\.env)?])[^\]]+]|\s*$)/;
92
+ if (blockPattern.test(currentConfig)) {
93
+ return currentConfig.replace(blockPattern, `\n${block}`).trimStart() + "\n";
94
+ }
95
+ const separator = currentConfig.trim().length === 0 ? "" : "\n\n";
96
+ return `${currentConfig.trimEnd()}${separator}${block}\n`;
97
+ }
98
+ function buildCodexServerBlock(serverConfig) {
99
+ const lines = [
100
+ "[mcp_servers.openlog]",
101
+ `command = ${tomlString(serverConfig.command)}`,
102
+ `args = [${serverConfig.args.map(tomlString).join(", ")}]`,
103
+ ];
104
+ if (Object.keys(serverConfig.env).length > 0) {
105
+ lines.push("");
106
+ lines.push("[mcp_servers.openlog.env]");
107
+ for (const [name, value] of Object.entries(serverConfig.env)) {
108
+ lines.push(`${name} = ${tomlString(value)}`);
109
+ }
110
+ }
111
+ return lines.join("\n");
112
+ }
113
+ function printConfig(client, serverConfig) {
114
+ if (client === "codex") {
115
+ console.log(buildCodexServerBlock(serverConfig));
116
+ return;
117
+ }
118
+ console.log(JSON.stringify({
119
+ type: "stdio",
120
+ command: serverConfig.command,
121
+ args: serverConfig.args,
122
+ env: serverConfig.env,
123
+ }, null, 2));
124
+ }
125
+ async function readTextIfExists(filePath) {
126
+ try {
127
+ return await readFile(filePath, "utf8");
128
+ }
129
+ catch (error) {
130
+ if (error &&
131
+ typeof error === "object" &&
132
+ "code" in error &&
133
+ error.code === "ENOENT") {
134
+ return "";
135
+ }
136
+ throw error;
137
+ }
138
+ }
139
+ function tomlString(value) {
140
+ return JSON.stringify(value);
141
+ }
142
+ class CommandFailedError extends Error {
143
+ exitCode;
144
+ stderr;
145
+ constructor(command, exitCode, stderr) {
146
+ super(`${command} exited with code ${exitCode ?? "unknown"}.`);
147
+ this.exitCode = exitCode;
148
+ this.stderr = stderr;
149
+ }
150
+ }
151
+ async function runCommand(command, args, options = {}) {
152
+ return new Promise((resolve, reject) => {
153
+ const stdioMode = options.stdio ?? "inherit";
154
+ const stdio = stdioMode === "pipe" ? ["ignore", "ignore", "pipe"] : "inherit";
155
+ let stderr = "";
156
+ const child = spawn(command, args, {
157
+ stdio,
158
+ });
159
+ child.stderr?.setEncoding("utf8");
160
+ child.stderr?.on("data", (chunk) => {
161
+ stderr += chunk;
162
+ });
163
+ child.on("error", (error) => {
164
+ if (error.code === "ENOENT") {
165
+ resolve("missing");
166
+ return;
167
+ }
168
+ reject(error);
169
+ });
170
+ child.on("exit", (code) => {
171
+ if (code === 0) {
172
+ resolve("executed");
173
+ return;
174
+ }
175
+ reject(new CommandFailedError(command, code, stderr));
176
+ });
177
+ });
178
+ }
179
+ function warnCodexFallback(error) {
180
+ const reason = summarizeCommandError(error);
181
+ const suffix = reason.length > 0 ? ` (${reason})` : "";
182
+ console.warn(`Codex CLI registration failed${suffix}. Falling back to ~/.codex/config.toml.`);
183
+ }
184
+ function summarizeCommandError(error) {
185
+ if (error instanceof CommandFailedError) {
186
+ const firstStderrLine = error.stderr
187
+ .split(/\r?\n/)
188
+ .map((line) => line.trim())
189
+ .find((line) => line.length > 0);
190
+ return truncateReason(firstStderrLine ?? error.message);
191
+ }
192
+ if (error instanceof Error) {
193
+ return truncateReason(error.message);
194
+ }
195
+ return truncateReason(String(error));
196
+ }
197
+ function truncateReason(reason) {
198
+ return reason.length > 180 ? `${reason.slice(0, 177)}...` : reason;
199
+ }
@@ -0,0 +1,115 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { OpenLogApiClient } from "./api-client.js";
5
+ import { readAuthFile } from "./auth-store.js";
6
+ import { getApiBaseUrl } from "./config.js";
7
+ export async function runMcpServer() {
8
+ const server = new McpServer({
9
+ name: "openlog",
10
+ version: "0.1.0",
11
+ });
12
+ server.registerTool("get_auth_status", {
13
+ title: "Get OpenLog Auth Status",
14
+ description: "Check whether the local OpenLog CLI is authenticated.",
15
+ inputSchema: {},
16
+ }, async () => {
17
+ let apiBaseUrl = getApiBaseUrl();
18
+ try {
19
+ const authFile = await readAuthFile();
20
+ if (!authFile) {
21
+ return textResult({
22
+ authenticated: false,
23
+ apiBaseUrl,
24
+ });
25
+ }
26
+ apiBaseUrl = authFile.apiBaseUrl;
27
+ const me = await createAuthenticatedClient().then((client) => client.get("/auth/me"));
28
+ return textResult({
29
+ authenticated: true,
30
+ apiBaseUrl: authFile.apiBaseUrl,
31
+ user: me,
32
+ });
33
+ }
34
+ catch (error) {
35
+ return textResult({
36
+ authenticated: false,
37
+ apiBaseUrl,
38
+ error: error instanceof Error ? error.message : String(error),
39
+ });
40
+ }
41
+ });
42
+ server.registerTool("get_me", {
43
+ title: "Get OpenLog Me",
44
+ description: "Return the currently authenticated OpenLog user.",
45
+ inputSchema: {},
46
+ }, async () => withAuthenticatedClient((client) => client.get("/auth/me")));
47
+ server.registerTool("list_my_notifications", {
48
+ title: "List My OpenLog Notifications",
49
+ description: "Return notifications for the authenticated OpenLog user.",
50
+ inputSchema: {
51
+ size: z.number().int().min(1).max(20).default(20),
52
+ },
53
+ }, async ({ size }) => withAuthenticatedClient((client) => client.get(`/notifications?${new URLSearchParams({ size: String(size) })}`)));
54
+ server.registerTool("list_my_liked_posts", {
55
+ title: "List My Liked OpenLog Posts",
56
+ description: "Return posts liked by the authenticated OpenLog user.",
57
+ inputSchema: {
58
+ cursor: z.string().optional(),
59
+ size: z.number().int().min(1).max(20).default(10),
60
+ },
61
+ }, async ({ cursor, size }) => {
62
+ const params = new URLSearchParams({ size: String(size) });
63
+ if (cursor) {
64
+ params.set("cursor", cursor);
65
+ }
66
+ return withAuthenticatedClient((client) => client.get(`/users/me/liked-posts?${params}`));
67
+ });
68
+ server.registerTool("get_post_detail", {
69
+ title: "Get OpenLog Post Detail",
70
+ description: "Return a public OpenLog post detail by author username and post slug.",
71
+ inputSchema: {
72
+ username: z.string().min(1),
73
+ slug: z.string().min(1),
74
+ },
75
+ }, async ({ username, slug }) => withAuthenticatedClient((client) => client.get(`/users/${encodeURIComponent(username)}/posts/${encodeURIComponent(slug)}`)));
76
+ await server.connect(new StdioServerTransport());
77
+ }
78
+ async function createAuthenticatedClient() {
79
+ const authFile = await readAuthFile();
80
+ if (!authFile) {
81
+ throw new Error("Run `openlog login` before using OpenLog MCP tools.");
82
+ }
83
+ return new OpenLogApiClient({
84
+ accessToken: authFile.accessToken,
85
+ apiBaseUrl: authFile.apiBaseUrl,
86
+ });
87
+ }
88
+ async function withAuthenticatedClient(callback) {
89
+ try {
90
+ const client = await createAuthenticatedClient();
91
+ const result = await callback(client);
92
+ return textResult(result);
93
+ }
94
+ catch (error) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: error instanceof Error ? error.message : String(error),
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+ }
106
+ function textResult(value) {
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2),
112
+ },
113
+ ],
114
+ };
115
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@kitae9999/openlog-cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Official OpenLog CLI and MCP server.",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/kitae9999/OpenLog.git",
10
+ "directory": "packages/openlog-cli"
11
+ },
12
+ "bin": {
13
+ "openlog": "dist/index.js"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc && chmod 755 dist/index.js",
17
+ "dev": "tsx src/index.ts",
18
+ "prepack": "npm run build",
19
+ "start": "node dist/index.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.29.0",
30
+ "zod": "^4.4.3"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.8.0",
34
+ "tsx": "^4.22.0",
35
+ "typescript": "^6.0.3"
36
+ }
37
+ }