@aliceshimada/mica 1.0.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1 - 2026-06-02
4
+
5
+ - Implement `mica status`, including dashboard token recovery from the current session.
6
+ - Implement `mica config codex|claude-desktop|cursor|opencode` snippets.
7
+ - Add `mica stop` and `mica restart`.
8
+ - Make `mica start` print current status instead of failing when a server is already running.
9
+
3
10
  ## 1.0.0 - 2026-06-02
4
11
 
5
12
  - Initial public release as `@aliceshimada/mica`.
package/README.md CHANGED
@@ -102,6 +102,9 @@ If MICA is installed on your `PATH`, the same release commands are:
102
102
  mica install
103
103
  mica start
104
104
  mica doctor
105
+ mica status
106
+ mica stop
107
+ mica restart
105
108
  ```
106
109
 
107
110
  Dashboard:
@@ -121,11 +124,41 @@ node dist/src/cli/index.js install --dry-run
121
124
  node dist/src/cli/index.js uninstall
122
125
  ```
123
126
 
127
+ `mica status` prints the current session file, server URL, version, PID, live agent/notebook counts, and the token-bearing dashboard URL. If a server is already running, `mica start` prints that same status instead of failing with a port-in-use error, so you can recover the dashboard token at any time.
128
+
124
129
  The legacy installer entry remains available for compatibility: `node scripts/install.js --dry-run`.
125
130
 
126
131
  ## MCP Client Config
127
132
 
128
- Use the built release entrypoint from your local checkout:
133
+ Print a copy-pasteable MCP config snippet:
134
+
135
+ ```bash
136
+ mica config codex
137
+ mica config claude-desktop
138
+ mica config cursor
139
+ mica config opencode
140
+ ```
141
+
142
+ MICA only prints snippets; it does not edit client config files for you.
143
+
144
+ For OpenCode, the snippet uses the validated local MCP shape:
145
+
146
+ ```json
147
+ {
148
+ "$schema": "https://opencode.ai/config.json",
149
+ "mcp": {
150
+ "mica": {
151
+ "type": "local",
152
+ "command": ["mica", "start"],
153
+ "enabled": true
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ When editing OpenCode config, restart OpenCode after saving; config is loaded at startup.
160
+
161
+ For manual setup from a local checkout, use the built release entrypoint:
129
162
 
130
163
  ```toml
131
164
  [mcp_servers.mica]
package/README.zh-CN.md CHANGED
@@ -102,6 +102,9 @@ node dist/src/cli/index.js start
102
102
  mica install
103
103
  mica start
104
104
  mica doctor
105
+ mica status
106
+ mica stop
107
+ mica restart
105
108
  ```
106
109
 
107
110
  Dashboard:
@@ -121,11 +124,41 @@ node dist/src/cli/index.js install --dry-run
121
124
  node dist/src/cli/index.js uninstall
122
125
  ```
123
126
 
127
+ `mica status` 会打印当前 session file、server URL、version、PID、live agent/notebook 数量,以及带 token 的 dashboard URL。如果 server 已经在运行,`mica start` 会打印同样的 status,而不是因为端口占用直接失败;因此你随时可以用它找回 dashboard token。
128
+
124
129
  兼容用的 legacy 安装入口仍然可用:`node scripts/install.js --dry-run`。
125
130
 
126
131
  ## MCP Client 配置
127
132
 
128
- 使用本地 checkout 中构建后的发布版入口:
133
+ 打印可复制的 MCP config snippet:
134
+
135
+ ```bash
136
+ mica config codex
137
+ mica config claude-desktop
138
+ mica config cursor
139
+ mica config opencode
140
+ ```
141
+
142
+ MICA 只打印配置片段,不会替你编辑 client config 文件。
143
+
144
+ OpenCode 的 snippet 使用经过验证的 local MCP 形状:
145
+
146
+ ```json
147
+ {
148
+ "$schema": "https://opencode.ai/config.json",
149
+ "mcp": {
150
+ "mica": {
151
+ "type": "local",
152
+ "command": ["mica", "start"],
153
+ "enabled": true
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ 编辑 OpenCode config 后需要重启 OpenCode;config 只在启动时加载。
160
+
161
+ 如果要从本地 checkout 手动设置,请使用构建后的发布版入口:
129
162
 
130
163
  ```toml
131
164
  [mcp_servers.mica]
@@ -2,7 +2,7 @@ import { timingSafeEqual } from "node:crypto";
2
2
  import http from "node:http";
3
3
  import { renderDashboard } from "./dashboard.js";
4
4
  const JSON_BODY_LIMIT_BYTES = 1024 * 1024;
5
- const DEFAULT_VERSION = "0.1.0";
5
+ const DEFAULT_VERSION = "1.0.1";
6
6
  export async function createBunHttpApp({ state, host = "127.0.0.1", port, authToken, version = DEFAULT_VERSION }) {
7
7
  const runtimeInfo = {
8
8
  host,
@@ -8,7 +8,7 @@ import { loadRuntimeConfig } from "../runtime/config.js";
8
8
  import { writeSessionFile } from "../runtime/session.js";
9
9
  import { createBunHttpApp } from "./httpServer.js";
10
10
  const MCP_SERVER_NAME = "mica-bun";
11
- const MICA_PACKAGE_VERSION = "0.1.0";
11
+ const MICA_PACKAGE_VERSION = "1.0.1";
12
12
  export async function startBunRuntime(deps = {}) {
13
13
  const config = deps.runtimeConfig ?? loadRuntimeConfig();
14
14
  const bridgeOnly = deps.bridgeOnly ?? config.bridgeOnly;
@@ -0,0 +1,36 @@
1
+ const CLIENTS = ["codex", "claude-desktop", "cursor", "opencode"];
2
+ export function runConfigCommand(argv) {
3
+ const client = argv[0];
4
+ if (!client || !CLIENTS.includes(client)) {
5
+ return {
6
+ exitCode: 1,
7
+ output: `Usage: mica config <${CLIENTS.join("|")}>\n`,
8
+ };
9
+ }
10
+ if (client === "codex") {
11
+ return ok(`[mcp_servers.mica]\ncommand = "mica"\nargs = ["start"]\n`);
12
+ }
13
+ if (client === "opencode") {
14
+ return ok(`${JSON.stringify({
15
+ $schema: "https://opencode.ai/config.json",
16
+ mcp: {
17
+ mica: {
18
+ type: "local",
19
+ command: ["mica", "start"],
20
+ enabled: true,
21
+ },
22
+ },
23
+ }, null, 2)}\n`);
24
+ }
25
+ return ok(`${JSON.stringify({
26
+ mcpServers: {
27
+ mica: {
28
+ command: "mica",
29
+ args: ["start"],
30
+ },
31
+ },
32
+ }, null, 2)}\n`);
33
+ }
34
+ function ok(output) {
35
+ return { exitCode: 0, output };
36
+ }
@@ -3,7 +3,10 @@ import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { startBunRuntime } from "../bun/index.js";
6
+ import { runConfigCommand } from "./configSnippets.js";
6
7
  import { runDoctor } from "./doctor.js";
8
+ import { runStatusCommand } from "./status.js";
9
+ import { runStopCommand } from "./stop.js";
7
10
  // ---------------------------------------------------------------------------
8
11
  // Helpers
9
12
  // ---------------------------------------------------------------------------
@@ -25,6 +28,8 @@ export function helpText() {
25
28
 
26
29
  Commands:
27
30
  start Start the MICA bridge runtime (default)
31
+ stop Stop the running MICA bridge runtime
32
+ restart Stop then start the MICA bridge runtime
28
33
  install [options] Install MICA bridge into Wolfram
29
34
  uninstall [options] Uninstall MICA bridge from Wolfram
30
35
  doctor Diagnose MICA bridge configuration
@@ -32,6 +37,7 @@ Commands:
32
37
  config codex Configure for Codex
33
38
  config claude-desktop Configure for Claude Desktop
34
39
  config cursor Configure for Cursor
40
+ config opencode Configure for OpenCode
35
41
 
36
42
  Options:
37
43
  --help, -h Show this help message
@@ -44,6 +50,9 @@ export async function runCli(argv, deps) {
44
50
  const startRuntime = deps?.startRuntime;
45
51
  const runInstaller = deps?.runInstaller;
46
52
  const _runDoctor = deps?.runDoctor;
53
+ const _runStatus = deps?.runStatus;
54
+ const _runConfig = deps?.runConfig;
55
+ const _runStop = deps?.runStop;
47
56
  const command = argv[0];
48
57
  // --help / -h
49
58
  if (command === "--help" || command === "-h") {
@@ -72,6 +81,13 @@ export async function runCli(argv, deps) {
72
81
  }
73
82
  // start or no args
74
83
  if (command === "start" || command === undefined) {
84
+ if (_runStatus) {
85
+ const status = await _runStatus();
86
+ if (status.running) {
87
+ stdout.write(status.output);
88
+ return status.exitCode;
89
+ }
90
+ }
75
91
  if (!startRuntime) {
76
92
  stderr.write("Error: startRuntime not available\n");
77
93
  return 1;
@@ -90,10 +106,52 @@ export async function runCli(argv, deps) {
90
106
  stdout.write(output);
91
107
  return exitCode;
92
108
  }
93
- // Future commands (listed in help but not yet implemented)
94
- if (command === "status" || command === "config") {
95
- stderr.write(`Command '${command}' is not yet implemented. Use --help for available commands.\n`);
96
- return 1;
109
+ // status
110
+ if (command === "status") {
111
+ if (!_runStatus) {
112
+ stderr.write("Error: runStatus not available\n");
113
+ return 1;
114
+ }
115
+ const { exitCode, output } = await _runStatus();
116
+ stdout.write(output);
117
+ return exitCode;
118
+ }
119
+ // config
120
+ if (command === "config") {
121
+ if (!_runConfig) {
122
+ stderr.write("Error: runConfig not available\n");
123
+ return 1;
124
+ }
125
+ const { exitCode, output } = _runConfig(argv.slice(1));
126
+ stdout.write(output);
127
+ return exitCode;
128
+ }
129
+ // stop
130
+ if (command === "stop") {
131
+ if (!_runStop) {
132
+ stderr.write("Error: runStop not available\n");
133
+ return 1;
134
+ }
135
+ const { exitCode, output } = await _runStop();
136
+ stdout.write(output);
137
+ return exitCode;
138
+ }
139
+ // restart
140
+ if (command === "restart") {
141
+ if (!_runStop) {
142
+ stderr.write("Error: runStop not available\n");
143
+ return 1;
144
+ }
145
+ if (!startRuntime) {
146
+ stderr.write("Error: startRuntime not available\n");
147
+ return 1;
148
+ }
149
+ const stopResult = await _runStop();
150
+ if (stopResult.output)
151
+ stdout.write(stopResult.output);
152
+ const runtime = await startRuntime();
153
+ await runtime.keepAlive;
154
+ return 0;
97
155
  }
98
156
  // Unknown command
99
157
  stderr.write(`Unknown command: ${command}\n`);
@@ -109,6 +167,9 @@ async function main() {
109
167
  const exitCode = await runCli(process.argv.slice(2), {
110
168
  startRuntime: async () => startBunRuntime(),
111
169
  runInstaller,
170
+ runStatus: async () => runStatusCommand(),
171
+ runConfig: runConfigCommand,
172
+ runStop: async () => runStopCommand(),
112
173
  runDoctor: async () => runDoctor({
113
174
  projectRoot,
114
175
  detectWolframUserBase: () => detectWolframUserBase(),
@@ -0,0 +1,81 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { defaultSessionFile } from "../runtime/config.js";
3
+ export async function runStatusCommand(deps = {}) {
4
+ const env = deps.env ?? process.env;
5
+ const _exists = deps.exists ?? existsSync;
6
+ const _readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
7
+ const _fetch = deps.fetch ??
8
+ (typeof globalThis.fetch === "function"
9
+ ? (url, init) => globalThis.fetch(url, init)
10
+ : undefined);
11
+ const lines = ["MICA status", ""];
12
+ const sessionFile = env.MICA_SESSION_FILE ?? defaultSessionFile(env);
13
+ const fail = (label, detail) => lines.push(`FAIL ${label}: ${detail}`);
14
+ const ok = (label, detail) => lines.push(`OK ${label}: ${detail}`);
15
+ if (!_exists(sessionFile)) {
16
+ fail("Session file", `${sessionFile} (not found)`);
17
+ lines.push("FIX Run: mica start");
18
+ return result(1, lines, false);
19
+ }
20
+ let session;
21
+ try {
22
+ session = JSON.parse(_readFile(sessionFile));
23
+ }
24
+ catch (error) {
25
+ fail("Session file", error instanceof Error ? error.message : String(error));
26
+ lines.push("FIX Run: mica start");
27
+ return result(1, lines, false);
28
+ }
29
+ const baseUrl = session.baseUrl ?? `http://${session.host ?? "127.0.0.1"}:${session.port ?? 19791}`;
30
+ ok("Session file", sessionFile);
31
+ ok("Session target", baseUrl);
32
+ if (!session.authToken) {
33
+ fail("Auth token", "missing in session file");
34
+ lines.push("FIX Restart MICA with: mica start");
35
+ return result(1, lines, false);
36
+ }
37
+ if (!_fetch) {
38
+ fail("Server /status reachable", "fetch unavailable");
39
+ return result(1, lines, false);
40
+ }
41
+ try {
42
+ const response = await _fetch(`${baseUrl}/status`, {
43
+ headers: { Authorization: `Bearer ${session.authToken}` },
44
+ });
45
+ if (response.status === 401) {
46
+ fail("Auth token", "401 Unauthorized");
47
+ lines.push("FIX Restart MICA with: mica start");
48
+ return result(1, lines, false);
49
+ }
50
+ if (response.status !== 200) {
51
+ fail("Server /status reachable", `HTTP ${response.status}`);
52
+ lines.push("FIX Run: mica start");
53
+ return result(1, lines, false);
54
+ }
55
+ const body = (await response.json());
56
+ const server = readRecord(body.server);
57
+ const agents = Array.isArray(body.agents) ? body.agents : [];
58
+ const notebooks = Array.isArray(body.notebooks) ? body.notebooks : [];
59
+ ok("Server", String(server.state ?? "running"));
60
+ ok("Version", String(server.version ?? session.version ?? "unknown"));
61
+ if (typeof session.pid === "number")
62
+ ok("PID", String(session.pid));
63
+ ok("Agents", String(agents.length));
64
+ ok("Notebooks", String(notebooks.length));
65
+ lines.push(`Dashboard: ${baseUrl}/#token=${session.authToken}`);
66
+ return result(0, lines, true);
67
+ }
68
+ catch (error) {
69
+ fail("Server /status reachable", error instanceof Error ? error.message : String(error));
70
+ lines.push("FIX Run: mica start");
71
+ return result(1, lines, false);
72
+ }
73
+ }
74
+ function readRecord(value) {
75
+ return value && typeof value === "object" && !Array.isArray(value)
76
+ ? value
77
+ : {};
78
+ }
79
+ function result(exitCode, lines, running) {
80
+ return { exitCode, output: `${lines.join("\n")}\n`, running };
81
+ }
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
2
+ import { defaultSessionFile } from "../runtime/config.js";
3
+ export async function runStopCommand(deps = {}) {
4
+ const env = deps.env ?? process.env;
5
+ const sessionFile = env.MICA_SESSION_FILE ?? defaultSessionFile(env);
6
+ const _exists = deps.exists ?? existsSync;
7
+ const _readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
8
+ const _kill = deps.kill ?? ((pid, signal) => { process.kill(pid, signal); });
9
+ const _unlink = deps.unlink ?? ((p) => { unlinkSync(p); });
10
+ if (!_exists(sessionFile)) {
11
+ return { exitCode: 1, output: `MICA is not running\nSession file not found: ${sessionFile}\n` };
12
+ }
13
+ let session;
14
+ try {
15
+ session = JSON.parse(_readFile(sessionFile));
16
+ }
17
+ catch (error) {
18
+ return { exitCode: 1, output: `Cannot read session file: ${error instanceof Error ? error.message : String(error)}\n` };
19
+ }
20
+ if (typeof session.pid !== "number" || !Number.isInteger(session.pid) || session.pid <= 0) {
21
+ return { exitCode: 1, output: "Cannot stop MICA: session file has no valid pid\n" };
22
+ }
23
+ try {
24
+ _kill(session.pid, "SIGTERM");
25
+ }
26
+ catch (error) {
27
+ return { exitCode: 1, output: `Cannot stop MICA pid ${session.pid}: ${error instanceof Error ? error.message : String(error)}\n` };
28
+ }
29
+ try {
30
+ _unlink(sessionFile);
31
+ }
32
+ catch {
33
+ // The process may have already removed or replaced it; stopping still succeeded.
34
+ }
35
+ return { exitCode: 0, output: `MICA stopped\nPID: ${session.pid}\n` };
36
+ }
@@ -33,7 +33,7 @@ export const MICA_AGENT_INSTRUCTIONS = [
33
33
  "Tools:",
34
34
  ...TOOL_GUIDE.map(([name, description]) => `- ${name}: ${description}`),
35
35
  ].join("\n");
36
- export function createMicaMcpServer(name, version = "0.1.0") {
36
+ export function createMicaMcpServer(name, version = "1.0.1") {
37
37
  return new McpServer({ name, version }, { instructions: MICA_AGENT_INSTRUCTIONS });
38
38
  }
39
39
  export function registerMicaPrompts(server) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliceshimada/mica",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Local MCP bridge for controlling live Wolfram Desktop / Mathematica notebooks.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/bun/index.ts CHANGED
@@ -11,7 +11,7 @@ import { writeSessionFile } from "../runtime/session.js";
11
11
  import { createBunHttpApp } from "./httpServer.js";
12
12
 
13
13
  const MCP_SERVER_NAME = "mica-bun";
14
- const MICA_PACKAGE_VERSION = "0.1.0";
14
+ const MICA_PACKAGE_VERSION = "1.0.1";
15
15
 
16
16
  export type BunRuntimeDeps = {
17
17
  bridgeOnly?: boolean;