mikoshi 0.1.2 → 0.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NEETlabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROVENANCE.md ADDED
@@ -0,0 +1,14 @@
1
+ # Provenance
2
+
3
+ Mikoshi's agent system is an independent implementation.
4
+
5
+ - It is informed by black-box behavior of agentic developer tools.
6
+ - It does not copy proprietary source code, prompts, event names, or protocol strings from other products.
7
+ - Agent/runtime contracts in Mikoshi use `mk_`-prefixed event names, lifecycle types, and state transitions implemented in this repository.
8
+
9
+ Design goals:
10
+
11
+ - Deterministic state transitions (`IDLE`, `PLANNING`, `AWAITING_PERMISSION`, `EXECUTING`, `FINALIZING`).
12
+ - Auditable capability grants (`filesystem.write`, `process.spawn`, `network.external`).
13
+ - Typed runtime events rendered by UI; raw tool payloads are not intended for direct UI display.
14
+ - Multi-tool execution with sequential default and isolated parallel option.
package/README.md CHANGED
@@ -2,7 +2,13 @@
2
2
  npm install -g mikoshi
3
3
  ```
4
4
 
5
- ✨ Mikoshi — private local code search + MCP
5
+ ✨ Mikoshi — Context Engine for Private local code search + MCP
6
+
7
+ This system is an independent implementation inspired by publicly observable behavior of agentic developer tools.
8
+ The agent runtime, tool lifecycle, permission flow, and event protocol are clean-room implementations using `mk_`-prefixed types and events.
9
+
10
+ ## Provenance
11
+ See `PROVENANCE.md` for implementation provenance and design constraints.
6
12
 
7
13
  ## Install
8
14
  ```bash
@@ -20,6 +26,21 @@ MCP
20
26
  mikoshi-mcp
21
27
  ```
22
28
 
29
+ Interactive permissions
30
+ ```text
31
+ /permissions
32
+ # configure: filesystem.write, process.spawn, network.external
33
+ ```
34
+
35
+ Models
36
+ ```text
37
+ /models
38
+ # provider: openai or anthropic
39
+ # API keys are stored per provider+model
40
+ # openai models: gpt-5.2, gpt-5.2-codex, gpt-5.2-pro
41
+ # anthropic models: claude-sonnet-4-5, claude-haiku-4-5, claude-opus-4-6
42
+ ```
43
+
23
44
  Codex MCP config (config.toml)
24
45
  ```toml
25
46
  [mcp_servers.mikoshi]
@@ -37,5 +58,3 @@ npm install
37
58
  npm test
38
59
  mikoshi doctor
39
60
  ```
40
-
41
- Note: First run may download the local embedding model once.
package/package.json CHANGED
@@ -1,23 +1,27 @@
1
1
  {
2
2
  "name": "mikoshi",
3
- "version": "0.1.2",
4
- "description": "Private local code search + MCP",
3
+ "version": "0.1.4",
4
+ "description": "Context Engine for Private local code search + MCP",
5
+ "license": "MIT",
5
6
  "type": "module",
7
+ "homepage": "https://NEET.gg",
6
8
  "bin": {
7
9
  "mikoshi": "src/mikoshi/cli.js",
8
10
  "mikoshi-mcp": "src/mikoshi/mcp_server/server.js"
9
11
  },
10
12
  "files": [
11
13
  "src/mikoshi/**",
12
- "README.md"
14
+ "README.md",
15
+ "PROVENANCE.md"
13
16
  ],
14
17
  "scripts": {
15
18
  "test": "node --test",
16
- "lint": "node --check ./src/mikoshi/cli.js"
19
+ "lint": "node --check ./src/mikoshi/cli.js",
20
+ "audit:unused": "node scripts/audit-unused.js",
21
+ "prepack": "node scripts/set-git-head.js"
17
22
  },
18
- "dependencies": {},
19
- "optionalDependencies": {
20
- "@xenova/transformers": "^2.17.2",
21
- "onnxruntime-node": "^1.20.0"
22
- }
23
+ "dependencies": {
24
+ "@xenova/transformers": "^2.17.2"
25
+ },
26
+ "gitHead": "0c4ae59"
23
27
  }
@@ -0,0 +1,291 @@
1
+ export const mk_AgentState = Object.freeze({
2
+ IDLE: "IDLE",
3
+ PLANNING: "PLANNING",
4
+ AWAITING_PERMISSION: "AWAITING_PERMISSION",
5
+ EXECUTING: "EXECUTING",
6
+ FINALIZING: "FINALIZING",
7
+ });
8
+
9
+ function mk_nowIso() {
10
+ return new Date().toISOString();
11
+ }
12
+
13
+ function mk_safeText(value) {
14
+ return String(value || "").trim();
15
+ }
16
+
17
+ function mk_bool(value) {
18
+ return value === true;
19
+ }
20
+
21
+ export class mk_AgentRuntime {
22
+ constructor({
23
+ sessionId,
24
+ onEvent,
25
+ requestCapability,
26
+ executeTool,
27
+ deriveCapability,
28
+ resolveCapabilityMode,
29
+ } = {}) {
30
+ this.sessionId = mk_safeText(sessionId) || `mk_${Date.now().toString(36)}`;
31
+ this.state = mk_AgentState.IDLE;
32
+ this.pendingTools = [];
33
+ this.grantedCapabilities = new Set();
34
+ this.executionLog = [];
35
+ this._toolCounter = 0;
36
+ this._onEvent = typeof onEvent === "function" ? onEvent : () => {};
37
+ this._requestCapability =
38
+ typeof requestCapability === "function" ? requestCapability : async () => true;
39
+ this._executeTool =
40
+ typeof executeTool === "function" ? executeTool : async () => ({ output: {} });
41
+ this._deriveCapability = typeof deriveCapability === "function" ? deriveCapability : () => null;
42
+ this._resolveCapabilityMode =
43
+ typeof resolveCapabilityMode === "function" ? resolveCapabilityMode : () => "prompt";
44
+ }
45
+
46
+ emit(event) {
47
+ if (!event || typeof event !== "object") return;
48
+ const payload = { ...event };
49
+ this._onEvent(payload);
50
+ }
51
+
52
+ setState(state, context = undefined) {
53
+ if (!state || this.state === state) return;
54
+ this.state = state;
55
+ this.emit({
56
+ type: "mk_agent.state",
57
+ state,
58
+ context: context && typeof context === "object" ? context : undefined,
59
+ });
60
+ }
61
+
62
+ setIntent(goal, meta = undefined) {
63
+ this.emit({
64
+ type: "mk_agent.intent",
65
+ goal: mk_safeText(goal),
66
+ meta: meta && typeof meta === "object" ? meta : undefined,
67
+ });
68
+ }
69
+
70
+ queueTools(calls) {
71
+ const items = Array.isArray(calls) ? calls : [];
72
+ const queued = [];
73
+ for (const call of items) {
74
+ if (!call || !call.toolName) continue;
75
+ this._toolCounter += 1;
76
+ const execution = {
77
+ id: `mk_tool_${this._toolCounter}`,
78
+ toolName: mk_safeText(call.toolName),
79
+ inputs: call.inputs && typeof call.inputs === "object" ? call.inputs : {},
80
+ approved: mk_bool(call.approved),
81
+ executedAt: undefined,
82
+ output: undefined,
83
+ error: "",
84
+ isolated: mk_bool(call.isolated),
85
+ providerToolCallId: mk_safeText(call.providerToolCallId),
86
+ display: mk_safeText(call.display) || mk_safeText(call.toolName),
87
+ finalized: false,
88
+ };
89
+ this.pendingTools.push(execution);
90
+ queued.push(execution);
91
+ }
92
+ return queued;
93
+ }
94
+
95
+ async executePendingTools({ parallelIsolated = false } = {}) {
96
+ if (!this.pendingTools.length) {
97
+ return { toolResults: [], stop: false };
98
+ }
99
+ this.setState(mk_AgentState.EXECUTING, { pending: this.pendingTools.length });
100
+
101
+ const toolResults = [];
102
+ let stop = false;
103
+ const sequential = this.pendingTools.filter((tool) => !tool.isolated || !parallelIsolated);
104
+ const parallel = this.pendingTools.filter((tool) => tool.isolated && parallelIsolated);
105
+ const totalSteps = this.pendingTools.length;
106
+ let stepCursor = 0;
107
+
108
+ for (const tool of sequential) {
109
+ stepCursor += 1;
110
+ const outcome = await this._executeSingleTool(tool, stepCursor, totalSteps);
111
+ toolResults.push(outcome.toolResult);
112
+ if (outcome.stop) {
113
+ stop = true;
114
+ break;
115
+ }
116
+ }
117
+
118
+ if (!stop && parallel.length) {
119
+ const baseStep = stepCursor;
120
+ const results = await Promise.all(
121
+ parallel.map(async (tool, idx) => ({
122
+ idx,
123
+ outcome: await this._executeSingleTool(tool, baseStep + idx + 1, totalSteps),
124
+ }))
125
+ );
126
+ results
127
+ .sort((a, b) => a.idx - b.idx)
128
+ .forEach(({ outcome }) => {
129
+ toolResults.push(outcome.toolResult);
130
+ if (outcome.stop) stop = true;
131
+ });
132
+ }
133
+
134
+ this.pendingTools = this.pendingTools.filter((tool) => !tool.finalized);
135
+ return { toolResults, stop };
136
+ }
137
+
138
+ finalize() {
139
+ this.setState(mk_AgentState.FINALIZING, { pending: this.pendingTools.length });
140
+ this.pendingTools = [];
141
+ this.setState(mk_AgentState.IDLE, { pending: 0 });
142
+ }
143
+
144
+ async _executeSingleTool(tool, step, totalSteps) {
145
+ const capability = mk_safeText(this._deriveCapability(tool)) || "";
146
+ if (capability && !tool.approved) {
147
+ const mode = mk_safeText(
148
+ this._resolveCapabilityMode({
149
+ capability,
150
+ toolExecution: tool,
151
+ })
152
+ ).toLowerCase();
153
+ const isPromptMode = !mode || mode === "prompt";
154
+ if (isPromptMode) {
155
+ this.setState(mk_AgentState.AWAITING_PERMISSION, {
156
+ capability,
157
+ tool_id: tool.id,
158
+ });
159
+ this.emit({
160
+ type: "mk_capability.request",
161
+ capability,
162
+ reason: tool.display || tool.toolName,
163
+ });
164
+ }
165
+ const granted = await this._requestCapability({
166
+ capability,
167
+ reason: tool.display || tool.toolName,
168
+ toolExecution: tool,
169
+ });
170
+ tool.approved = mk_bool(granted);
171
+ if (tool.approved) this.grantedCapabilities.add(capability);
172
+ if (isPromptMode || !tool.approved) {
173
+ this.emit({
174
+ type: "mk_capability.receipt",
175
+ capability,
176
+ granted: tool.approved,
177
+ details: {
178
+ tool_id: tool.id,
179
+ tool: tool.toolName,
180
+ },
181
+ });
182
+ }
183
+ if (isPromptMode) {
184
+ this.setState(mk_AgentState.EXECUTING, { pending: this.pendingTools.length });
185
+ }
186
+ }
187
+
188
+ this.emit({
189
+ type: "mk_tool.progress",
190
+ tool_id: tool.id,
191
+ step,
192
+ total_steps: totalSteps,
193
+ });
194
+
195
+ if (!tool.approved && capability) {
196
+ const error = `Permission denied for ${capability}.`;
197
+ tool.executedAt = mk_nowIso();
198
+ tool.error = error;
199
+ tool.output = { error, capability };
200
+ tool.finalized = true;
201
+ const toolResult = {
202
+ id: tool.id,
203
+ tool_call_id: tool.providerToolCallId || "",
204
+ toolUseId: tool.providerToolCallId || "",
205
+ tool: tool.toolName,
206
+ input: tool.inputs || {},
207
+ ok: false,
208
+ output: tool.output,
209
+ summary: `denied ${capability}`,
210
+ };
211
+ this.executionLog.push({
212
+ ...toolResult,
213
+ ts: tool.executedAt,
214
+ });
215
+ this.emit({
216
+ type: "mk_tool.result",
217
+ tool_id: tool.id,
218
+ success: false,
219
+ output: tool.output,
220
+ error,
221
+ summary: `denied ${capability}`,
222
+ });
223
+ return { toolResult, stop: false };
224
+ }
225
+
226
+ try {
227
+ const result = await this._executeTool(tool);
228
+ tool.executedAt = mk_nowIso();
229
+ tool.output = result?.output || {};
230
+ const summary = mk_safeText(result?.summary);
231
+ const toolError = mk_safeText(tool.output?.error);
232
+ tool.error = toolError;
233
+ tool.finalized = true;
234
+ const toolResult = {
235
+ id: tool.id,
236
+ tool_call_id: tool.providerToolCallId || "",
237
+ toolUseId: tool.providerToolCallId || "",
238
+ tool: tool.toolName,
239
+ input: tool.inputs || {},
240
+ ok: !toolError,
241
+ output: tool.output,
242
+ summary,
243
+ };
244
+ if (mk_safeText(result?.preview)) {
245
+ toolResult.preview = mk_safeText(result.preview);
246
+ }
247
+ this.executionLog.push({
248
+ ...toolResult,
249
+ ts: tool.executedAt,
250
+ });
251
+ this.emit({
252
+ type: "mk_tool.result",
253
+ tool_id: tool.id,
254
+ success: !toolError,
255
+ output: tool.output,
256
+ error: toolError || undefined,
257
+ summary: summary || (toolError ? toolError : "completed"),
258
+ });
259
+ return { toolResult, stop: mk_bool(result?.stop) };
260
+ } catch (err) {
261
+ const error = mk_safeText(err?.message || err) || "Tool execution failed.";
262
+ tool.executedAt = mk_nowIso();
263
+ tool.error = error;
264
+ tool.output = { error };
265
+ tool.finalized = true;
266
+ const toolResult = {
267
+ id: tool.id,
268
+ tool_call_id: tool.providerToolCallId || "",
269
+ toolUseId: tool.providerToolCallId || "",
270
+ tool: tool.toolName,
271
+ input: tool.inputs || {},
272
+ ok: false,
273
+ output: tool.output,
274
+ summary: error,
275
+ };
276
+ this.executionLog.push({
277
+ ...toolResult,
278
+ ts: tool.executedAt,
279
+ });
280
+ this.emit({
281
+ type: "mk_tool.result",
282
+ tool_id: tool.id,
283
+ success: false,
284
+ output: tool.output,
285
+ error,
286
+ summary: error,
287
+ });
288
+ return { toolResult, stop: false };
289
+ }
290
+ }
291
+ }
@@ -8,7 +8,8 @@ import { DEFAULT_FEATURES, DEFAULT_PLAN } from "./entitlements.js";
8
8
  export const DEFAULT_API_BASE_URL = "https://neet.gg";
9
9
 
10
10
  const AUTH_FILENAME = "auth.json";
11
- const CONFIG_FILENAME = "config.json";
11
+ const CONFIG_FILENAME = "auth-config.json";
12
+ const LEGACY_CONFIG_FILENAME = "config.json";
12
13
 
13
14
  export class AuthError extends Error {}
14
15
 
@@ -25,18 +26,25 @@ export function configPath() {
25
26
  return path.join(indexRoot(), CONFIG_FILENAME);
26
27
  }
27
28
 
29
+ function legacyConfigPath() {
30
+ return path.join(indexRoot(), LEGACY_CONFIG_FILENAME);
31
+ }
32
+
28
33
  export function loadBrokerConfig() {
29
- const cfgPath = configPath();
30
- if (!fs.existsSync(cfgPath)) {
31
- return { api_base_url: DEFAULT_API_BASE_URL };
32
- }
33
- try {
34
- const data = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
35
- const apiBase = String(data.api_base_url || "").trim().replace(/\/+$/, "");
36
- return { api_base_url: apiBase || DEFAULT_API_BASE_URL };
37
- } catch {
38
- return { api_base_url: DEFAULT_API_BASE_URL };
34
+ const candidates = [configPath(), legacyConfigPath()];
35
+ for (const cfgPath of candidates) {
36
+ if (!fs.existsSync(cfgPath)) continue;
37
+ try {
38
+ const data = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
39
+ const apiBase = String(data.api_base_url || "").trim().replace(/\/+$/, "");
40
+ if (apiBase) {
41
+ return { api_base_url: apiBase };
42
+ }
43
+ } catch {
44
+ // ignore invalid config and continue
45
+ }
39
46
  }
47
+ return { api_base_url: DEFAULT_API_BASE_URL };
40
48
  }
41
49
 
42
50
  export function saveBrokerConfig(apiBaseUrl) {
@@ -150,20 +158,41 @@ async function promptLine(prompt) {
150
158
  }
151
159
 
152
160
  function parsePastePayload(text) {
153
- let data;
154
- try {
155
- data = JSON.parse(text);
156
- } catch (err) {
157
- throw new AuthError("Invalid JSON response.");
161
+ const input = String(text || "").trim();
162
+ if (!input) throw new AuthError("Missing response.");
163
+ if (input.startsWith("{")) {
164
+ let data;
165
+ try {
166
+ data = JSON.parse(input);
167
+ } catch {
168
+ throw new AuthError("Invalid response payload.");
169
+ }
170
+ if (!data || typeof data !== "object") throw new AuthError("Invalid response payload.");
171
+ const code = String(data.code || "");
172
+ const state = String(data.state || "");
173
+ if (!code || !state) throw new AuthError("Missing code or state.");
174
+ return { code, state };
175
+ }
176
+
177
+ const queryPart = input.includes("?") ? input.split("?").pop() : input;
178
+ const hashPart = queryPart.includes("#") ? queryPart.split("#").pop() : queryPart;
179
+ const params = new URLSearchParams(hashPart);
180
+ const codeFromParams = params.get("code");
181
+ const stateFromParams = params.get("state");
182
+ if (codeFromParams && stateFromParams) {
183
+ return { code: codeFromParams, state: stateFromParams };
158
184
  }
159
- if (!data || typeof data !== "object") throw new AuthError("Invalid JSON response.");
160
- const code = String(data.code || "");
161
- const state = String(data.state || "");
162
- if (!code || !state) throw new AuthError("Missing code or state.");
163
- return { code, state };
185
+
186
+ const codeMatch = input.match(/code\s*[:=]\s*([A-Za-z0-9._~-]+)/i);
187
+ const stateMatch = input.match(/state\s*[:=]\s*([A-Za-z0-9._~-]+)/i);
188
+ if (codeMatch && stateMatch) {
189
+ return { code: codeMatch[1], state: stateMatch[1] };
190
+ }
191
+
192
+ throw new AuthError("Missing code or state.");
164
193
  }
165
194
 
166
- export async function login() {
195
+ export async function login({ promptLabel } = {}) {
167
196
  const broker = loadBrokerConfig();
168
197
  const state = generateState();
169
198
  const verifier = generateCodeVerifier();
@@ -172,7 +201,8 @@ export async function login() {
172
201
 
173
202
  await openUrl(url);
174
203
 
175
- const pasted = await promptLine("Paste the JSON response here: ");
204
+ const promptText = String(promptLabel || "Paste the response here: ");
205
+ const pasted = await promptLine(promptText);
176
206
  const { code, state: returnedState } = parsePastePayload(pasted);
177
207
  if (returnedState !== state) {
178
208
  throw new AuthError("Invalid state.");