@luanpdd/kit-mcp 1.1.0 → 1.2.3
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 +180 -0
- package/README.md +32 -0
- package/bin/ui.js +74 -0
- package/package.json +2 -1
- package/src/cli/index.js +211 -10
- package/src/mcp-server/index.js +53 -6
- package/src/ui/auto-spawn.js +108 -0
- package/src/ui/browser.js +78 -0
- package/src/ui/client.js +115 -0
- package/src/ui/events.js +65 -0
- package/src/ui/lockfile.js +147 -0
- package/src/ui/port.js +67 -0
- package/src/ui/server.js +432 -0
- package/src/ui/static/index.html +1022 -0
- package/src/ui/wrapper.js +119 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/ui/wrapper.js
|
|
2
|
+
// Wrap an existing onProgress callback so that calls also publish to the sidecar.
|
|
3
|
+
// Used at callsites (CLI handlers, MCP tool handlers) — NEVER imported by core
|
|
4
|
+
// (`syncTo`, `applyReverse`). The Stable API of core stays untouched (REQ).
|
|
5
|
+
//
|
|
6
|
+
// Also exports redactPath: a helper that scrubs the user's $HOME and the project
|
|
7
|
+
// root from any string before it leaves this process. Applied uniformly here so
|
|
8
|
+
// that path-leak protection is centralized (REQ SEC-05).
|
|
9
|
+
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { publish } from './client.js';
|
|
13
|
+
import { makeEvent, newRunId } from './events.js';
|
|
14
|
+
|
|
15
|
+
// Convert any value into a payload-safe shape with paths redacted.
|
|
16
|
+
// We touch strings only — numbers/booleans/null pass through. Nested objects
|
|
17
|
+
// and arrays are walked.
|
|
18
|
+
const HOME = os.homedir() || '';
|
|
19
|
+
|
|
20
|
+
function escapeForReplace(s) {
|
|
21
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function redactPath(value, projectRoot) {
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
let out = value;
|
|
27
|
+
if (projectRoot) {
|
|
28
|
+
const re = new RegExp(escapeForReplace(projectRoot), 'g');
|
|
29
|
+
out = out.replace(re, '<project>');
|
|
30
|
+
}
|
|
31
|
+
if (HOME) {
|
|
32
|
+
const re = new RegExp(escapeForReplace(HOME), 'g');
|
|
33
|
+
out = out.replace(re, '~');
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map((v) => redactPath(v, projectRoot));
|
|
39
|
+
}
|
|
40
|
+
if (value && typeof value === 'object') {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const k of Object.keys(value)) {
|
|
43
|
+
out[k] = redactPath(value[k], projectRoot);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// wrapProgressForUi(originalCb, ctx) returns a function with the same signature
|
|
51
|
+
// as the existing onProgress callback. Calls originalCb (terminal output) AND
|
|
52
|
+
// publishes to the sidecar. The sidecar publish is fire-and-forget; the wrapped
|
|
53
|
+
// callback never throws even if the sidecar isn't running.
|
|
54
|
+
//
|
|
55
|
+
// ctx: { projectRoot, runId?, tool? }
|
|
56
|
+
// - projectRoot — required for redaction + lockfile resolution
|
|
57
|
+
// - runId — defaults to a fresh runId per wrapper instance
|
|
58
|
+
// - tool — short label (e.g. 'sync', 'reverse-sync', 'gates') for grouping
|
|
59
|
+
export function wrapProgressForUi(originalCb, ctx) {
|
|
60
|
+
if (typeof originalCb !== 'function' && originalCb != null) {
|
|
61
|
+
throw new TypeError('originalCb must be a function or null/undefined');
|
|
62
|
+
}
|
|
63
|
+
if (!ctx || typeof ctx.projectRoot !== 'string') {
|
|
64
|
+
throw new TypeError('wrapProgressForUi requires ctx.projectRoot: string');
|
|
65
|
+
}
|
|
66
|
+
const projectRoot = ctx.projectRoot;
|
|
67
|
+
const runId = ctx.runId ?? newRunId();
|
|
68
|
+
const tool = ctx.tool ?? null;
|
|
69
|
+
|
|
70
|
+
// Best-effort fire-and-forget. We deliberately swallow errors — the wrapper
|
|
71
|
+
// must never break the caller because the optional UI isn't up.
|
|
72
|
+
function emit(event) {
|
|
73
|
+
publish(event, { projectRoot }).catch(() => { /* noop */ });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Emit a run.start as soon as the wrapper is created. Caller can also emit
|
|
77
|
+
// run.end manually (or use the `done` helper below).
|
|
78
|
+
emit(makeEvent({
|
|
79
|
+
type: 'run.start',
|
|
80
|
+
runId,
|
|
81
|
+
payload: redactPath({ tool, projectRoot, ts: Date.now() }, projectRoot),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
function wrapped(progress) {
|
|
85
|
+
// Forward to the original callback first — if the user supplied none, skip.
|
|
86
|
+
if (typeof originalCb === 'function') {
|
|
87
|
+
try { originalCb(progress); } catch { /* surface from caller, not us */ }
|
|
88
|
+
}
|
|
89
|
+
// Convert the canonical onProgress shape ({percent, label, kind}) into a
|
|
90
|
+
// sidecar 'progress' event. Pass extra fields through unchanged (redacted).
|
|
91
|
+
const safe = redactPath({ tool, ...progress }, projectRoot);
|
|
92
|
+
emit(makeEvent({ type: 'progress', runId, payload: safe }));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Helpers for the caller — not strictly part of the onProgress signature, so
|
|
96
|
+
// we attach them as properties.
|
|
97
|
+
wrapped.runId = runId;
|
|
98
|
+
wrapped.emit = (type, payload) => emit(makeEvent({
|
|
99
|
+
type,
|
|
100
|
+
runId,
|
|
101
|
+
payload: redactPath(payload, projectRoot),
|
|
102
|
+
}));
|
|
103
|
+
wrapped.done = (payload = {}) => emit(makeEvent({
|
|
104
|
+
type: 'run.end',
|
|
105
|
+
runId,
|
|
106
|
+
payload: redactPath({ tool, ...payload }, projectRoot),
|
|
107
|
+
}));
|
|
108
|
+
wrapped.error = (err) => emit(makeEvent({
|
|
109
|
+
type: 'error',
|
|
110
|
+
runId,
|
|
111
|
+
payload: redactPath({
|
|
112
|
+
tool,
|
|
113
|
+
message: err?.message ?? String(err),
|
|
114
|
+
code: err?.code ?? null,
|
|
115
|
+
}, projectRoot),
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
return wrapped;
|
|
119
|
+
}
|