@ppdocs/mcp 3.2.14 → 3.2.16

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/dist/cli.js CHANGED
@@ -90,7 +90,7 @@ ppdocs MCP CLI
90
90
 
91
91
  Commands:
92
92
  init Initialize ppdocs config + install workflow templates
93
- agent Start PPDocs Agent (Web UI + File Sync)
93
+ webui Start PPDocs Web UI (MCP management + File Sync)
94
94
 
95
95
  Usage:
96
96
  npx @ppdocs/mcp init -p <projectId> -k <key> [options]
@@ -124,8 +124,8 @@ export async function runCli(args) {
124
124
  await initProject(opts);
125
125
  return 'exit';
126
126
  }
127
- if (cmd === 'agent' || cmd === 'webui') {
128
- // 动态导入 Agent 入口 (Web UI + 多项目管理) — 长驻进程,不退出
127
+ if (cmd === 'webui') {
128
+ // 动态导入 WebUI 入口 (Web UI + 多项目管理) — 长驻进程,不退出
129
129
  await import('./agent.js');
130
130
  return 'keep-alive';
131
131
  }
package/dist/index.js CHANGED
@@ -26,9 +26,11 @@ if (args.length > 0) {
26
26
  if (result === 'exit')
27
27
  process.exit(0);
28
28
  if (result === 'keep-alive') {
29
- // agent/webui 长驻模式,不继续执行 MCP Server
30
- // @ts-ignore: 阻止后续 main() 执行
31
- await new Promise(() => { }); // 永不 resolve,进程由 agent.ts 的 HTTP 服务保活
29
+ // webui 长驻模式,不继续执行 MCP Server
30
+ // 用 setInterval 保活,避免 Node.js 'unsettled top-level await' 警告
31
+ setInterval(() => { }, 1 << 30);
32
+ // @ts-ignore: 防止后续 main() 执行
33
+ await new Promise(() => { });
32
34
  }
33
35
  // result === false → 继续作为 MCP Server
34
36
  }
@@ -158,6 +158,100 @@ export function startWebServer(webPort) {
158
158
  res.json({ projects: [] });
159
159
  }
160
160
  });
161
+ // GET /api/browse → 服务端目录浏览 (供前端目录选择器使用)
162
+ app.get('/api/browse', (req, res) => {
163
+ const dir = req.query.dir || '';
164
+ try {
165
+ // 无参数时返回根级入口
166
+ if (!dir) {
167
+ const home = os.homedir();
168
+ // 常用快捷入口
169
+ const shortcuts = [];
170
+ const desktop = path.join(home, 'Desktop');
171
+ if (fs.existsSync(desktop))
172
+ shortcuts.push({ name: '🖥️ 桌面 (Desktop)', path: desktop });
173
+ shortcuts.push({ name: '🏠 用户目录', path: home });
174
+ if (process.platform === 'darwin') {
175
+ const dev = path.join(home, 'Developer');
176
+ const docs = path.join(home, 'Documents');
177
+ if (fs.existsSync(dev))
178
+ shortcuts.push({ name: '💻 Developer', path: dev });
179
+ if (fs.existsSync(docs))
180
+ shortcuts.push({ name: '📄 Documents', path: docs });
181
+ }
182
+ if (process.platform === 'win32') {
183
+ // Windows: 列出盘符
184
+ const drives = [];
185
+ for (let i = 65; i <= 90; i++) {
186
+ const d = String.fromCharCode(i) + ':\\';
187
+ try {
188
+ fs.accessSync(d);
189
+ drives.push(d);
190
+ }
191
+ catch { /* skip */ }
192
+ }
193
+ res.json({ ok: true, dir: '', dirs: drives, shortcuts, platform: 'win32' });
194
+ }
195
+ else {
196
+ // Mac/Linux: 列出 home 下的子目录
197
+ try {
198
+ const homeDirs = fs.readdirSync(home, { withFileTypes: true })
199
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
200
+ .slice(0, 20)
201
+ .map(e => path.join(home, e.name));
202
+ res.json({ ok: true, dir: home, dirs: homeDirs, shortcuts, platform: process.platform });
203
+ }
204
+ catch {
205
+ res.json({ ok: true, dir: '', dirs: [home], shortcuts, platform: process.platform });
206
+ }
207
+ }
208
+ return;
209
+ }
210
+ // 列出指定目录的子目录
211
+ if (!fs.existsSync(dir)) {
212
+ res.json({ ok: false, error: '目录不存在' });
213
+ return;
214
+ }
215
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
216
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
217
+ .sort((a, b) => a.name.localeCompare(b.name))
218
+ .slice(0, 100)
219
+ .map(e => path.join(dir, e.name));
220
+ res.json({ ok: true, dir, dirs: entries, platform: process.platform });
221
+ }
222
+ catch (e) {
223
+ res.json({ ok: false, error: String(e).slice(0, 100) });
224
+ }
225
+ });
226
+ // GET /api/pick-dir → 调用系统原生目录选择器
227
+ app.get('/api/pick-dir', async (_req, res) => {
228
+ try {
229
+ let cmd;
230
+ if (process.platform === 'win32') {
231
+ // Windows: PowerShell FolderBrowserDialog
232
+ cmd = `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $d=New-Object System.Windows.Forms.FolderBrowserDialog; $d.Description='选择项目目录'; $d.ShowNewFolderButton=$true; if($d.ShowDialog() -eq 'OK'){$d.SelectedPath}else{''}"`;
233
+ }
234
+ else if (process.platform === 'darwin') {
235
+ // Mac: osascript choose folder
236
+ cmd = `osascript -e 'try' -e 'POSIX path of (choose folder with prompt "选择项目目录")' -e 'on error' -e '""' -e 'end try'`;
237
+ }
238
+ else {
239
+ // Linux: zenity
240
+ cmd = `zenity --file-selection --directory --title="选择项目目录" 2>/dev/null || echo ""`;
241
+ }
242
+ const { execSync } = await import('child_process');
243
+ const result = execSync(cmd, { encoding: 'utf-8', timeout: 120000 }).trim();
244
+ if (result) {
245
+ res.json({ ok: true, dir: result });
246
+ }
247
+ else {
248
+ res.json({ ok: false, cancelled: true });
249
+ }
250
+ }
251
+ catch {
252
+ res.json({ ok: false, error: '目录选择器调用失败' });
253
+ }
254
+ });
161
255
  // ============ 服务端授权流程 ============
162
256
  // POST /api/auth/start → 发起授权请求 (代理转发到主机)
163
257
  app.post('/api/auth/start', async (req, res) => {
package/dist/web/ui.js CHANGED
@@ -91,6 +91,11 @@ label{font-size:12px;color:#64748b;font-weight:500;display:block;margin-bottom:4
91
91
  .tab{padding:8px 16px;font-size:13px;font-weight:600;color:#64748b;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s}
92
92
  .tab.active{color:#60a5fa;border-color:#60a5fa}
93
93
  @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
94
+ .dir-picker-list{max-height:50vh;overflow-y:auto}
95
+ .dir-item{padding:8px 12px;border-radius:6px;cursor:pointer;font-size:13px;color:#cbd5e1;display:flex;align-items:center;gap:8px;transition:background .15s}
96
+ .dir-item:hover{background:rgba(96,165,250,.12)}
97
+ .dir-item .icon{font-size:16px}
98
+ .dir-breadcrumb{font-size:12px;color:#64748b;padding:6px 10px;background:rgba(15,23,42,.6);border-radius:6px;margin-bottom:10px;word-break:break-all}
94
99
  </style>
95
100
  </head>
96
101
  <body>
@@ -210,12 +215,61 @@ function renderAddStep1(){
210
215
  return '<span class="back" onclick="cancelAuth();go(\\x27list\\x27)">← 返回项目列表</span>'+
211
216
  '<div class="card no-hover"><div class="section"><h2>➕ 添加项目 — Step 1/2</h2>'+
212
217
  '<p style="font-size:13px;color:#64748b;margin-bottom:12px">指定本地项目目录,以目录名作为项目名称</p>'+
213
- '<div class="fg"><label>本地项目目录 (绝对路径)</label><input id="iDir" value="'+S.addDir+'" placeholder="例: D:/projects/my-app" oninput="S.addDir=this.value;_render()"></div>'+
218
+ '<div class="fg"><label>本地项目目录 (绝对路径)</label><div style="display:flex;gap:8px"><input id="iDir" style="flex:1" value="'+S.addDir+'" placeholder="例: /Users/me/projects/my-app" oninput="S.addDir=this.value;_render()"><button class="btn btn-p" style="white-space:nowrap;padding:6px 14px" onclick="nativePickDir()">📁 选择目录</button></div></div>'+
214
219
  namePreview+
215
220
  '<div class="btn-row"><button class="btn btn-p" onclick="doAuthStart()">📡 发送授权请求</button></div>'+
216
221
  '</div></div>';
217
222
  }
218
223
 
224
+ function nativePickDir(){
225
+ toast('正在打开系统目录选择器...');
226
+ api('/api/pick-dir').then(function(r){
227
+ if(r.ok&&r.dir){S.addDir=r.dir;_render();toast('✅ 已选择: '+r.dir);}
228
+ else if(r.cancelled){toast('已取消');}
229
+ else{toast('系统选择器不可用,使用内置浏览器','err');openDirPicker();}
230
+ }).catch(function(){openDirPicker();});
231
+ }
232
+
233
+ function openDirPicker(){
234
+ var currentDir='';
235
+ function browse(d){
236
+ api('/api/browse'+(d?'?dir='+encodeURIComponent(d):'')).then(function(r){
237
+ if(!r.ok){toast(r.error||'浏览失败','err');return}
238
+ currentDir=r.dir||'';
239
+ var dirs=r.dirs||[];
240
+ var shortcuts=r.shortcuts||[];
241
+ var sep=r.platform==='win32'?'\\\\':'/';
242
+ var html='<div class="dir-breadcrumb">📍 '+(currentDir||'根目录')+'</div>';
243
+ if(!currentDir&&shortcuts.length>0){
244
+ for(var s=0;s<shortcuts.length;s++){var sp=shortcuts[s].path.replace(/\\\\/g,'\\\\\\\\').replace(/'/g,"\\\\'");html+='<div class="dir-item" style="color:#60a5fa" onclick="dirPickerNav(\\x27'+sp+'\\x27)"><span class="icon">⚡</span> '+esc(shortcuts[s].name)+'</div>';}
245
+ html+='<div style="margin:8px 0;border-top:1px solid rgba(100,116,139,.2)"></div>';
246
+ }
247
+ if(currentDir){
248
+ var parts=currentDir.replace(/\\\\/g,'/').split('/').filter(Boolean);
249
+ var parent=parts.length>1?parts.slice(0,-1).join(sep):(r.platform==='win32'?'':'/');
250
+ if(r.platform==='win32'&&parts.length===1)parent='';
251
+ html+='<div class="dir-item" onclick="dirPickerNav(\\x27'+parent.replace(/\\\\/g,'\\\\\\\\').replace(/'/g,"\\\\x27")+'\\x27)"><span class="icon">⬆️</span> .. 上级目录</div>';
252
+ html+='<div class="dir-item" style="background:rgba(96,165,250,.15);border:1px solid rgba(96,165,250,.3)" onclick="dirPickerSelect(\\x27'+currentDir.replace(/\\\\/g,'\\\\\\\\').replace(/'/g,"\\\\x27")+'\\x27)"><span class="icon">✅</span> <strong>选择当前目录</strong></div>';
253
+ }
254
+ for(var i=0;i<dirs.length;i++){
255
+ var dn=dirs[i].replace(/\\\\/g,'/').split('/').filter(Boolean).pop();
256
+ html+='<div class="dir-item" onclick="dirPickerNav(\\x27'+dirs[i].replace(/\\\\/g,'\\\\\\\\').replace(/'/g,"\\\\x27")+'\\x27)"><span class="icon">📁</span> '+esc(dn)+'</div>';
257
+ }
258
+ if(dirs.length===0&&currentDir){html+='<div style="padding:12px;color:#475569;font-size:13px">(空目录)</div>';}
259
+ var el=document.getElementById('dirPickerBody');
260
+ if(el)el.innerHTML=html;
261
+ });
262
+ }
263
+ window.dirPickerNav=function(d){browse(d)};
264
+ window.dirPickerSelect=function(d){
265
+ S.addDir=d;var bg=document.querySelector('.modal-bg');if(bg)bg.remove();_render();
266
+ };
267
+ var div=document.createElement('div');div.className='modal-bg';
268
+ div.innerHTML='<div class="modal" style="max-width:550px"><h3>📁 选择项目目录</h3><div class="dir-picker-list" id="dirPickerBody"><div style="padding:20px;color:#64748b">加载中...</div></div><div class="btn-row"><button class="btn btn-s" onclick="closeModal()">取消</button></div></div>';
269
+ document.body.appendChild(div);div.addEventListener('click',function(e){if(e.target===div)div.remove()});
270
+ browse('');
271
+ }
272
+
219
273
  function doAuthStart(){
220
274
  var dir=document.getElementById('iDir').value.trim();
221
275
  if(!dir){toast('请填写目录路径','err');return}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.2.14",
3
+ "version": "3.2.16",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",