@ppdocs/mcp 3.2.13 → 3.2.15
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 +3 -3
- package/dist/web/server.js +132 -1
- package/dist/web/ui.js +55 -1
- package/package.json +1 -1
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
|
-
|
|
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 === '
|
|
128
|
-
// 动态导入
|
|
127
|
+
if (cmd === 'webui') {
|
|
128
|
+
// 动态导入 WebUI 入口 (Web UI + 多项目管理) — 长驻进程,不退出
|
|
129
129
|
await import('./agent.js');
|
|
130
130
|
return 'keep-alive';
|
|
131
131
|
}
|
package/dist/web/server.js
CHANGED
|
@@ -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) => {
|
|
@@ -563,7 +657,13 @@ export function startWebServer(webPort) {
|
|
|
563
657
|
key: proj.remote.password,
|
|
564
658
|
user: `Agent@${os.hostname()}`,
|
|
565
659
|
}, null, 2));
|
|
566
|
-
|
|
660
|
+
let extra = '';
|
|
661
|
+
if (platform === 'antigravity') {
|
|
662
|
+
const cmdCount = installAntigravitySlashCommands();
|
|
663
|
+
if (cmdCount > 0)
|
|
664
|
+
extra = ` + ${cmdCount}个斜杠命令`;
|
|
665
|
+
}
|
|
666
|
+
res.json({ ok: true, message: `✅ ${platform} MCP 已安装${extra}` });
|
|
567
667
|
}
|
|
568
668
|
catch (e) {
|
|
569
669
|
res.json({ ok: false, error: String(e) });
|
|
@@ -667,6 +767,37 @@ function removeMcpFromConfig(filePath, serverName) {
|
|
|
667
767
|
}
|
|
668
768
|
catch { /* ignore */ }
|
|
669
769
|
}
|
|
770
|
+
/** 安装 Antigravity 斜杠命令到全局目录 ~/.gemini/antigravity/global_workflows/ */
|
|
771
|
+
function installAntigravitySlashCommands() {
|
|
772
|
+
const templatesDir = path.join(__dirname, '..', 'templates', 'commands', 'pp');
|
|
773
|
+
if (!fs.existsSync(templatesDir))
|
|
774
|
+
return 0;
|
|
775
|
+
const globalDir = path.join(os.homedir(), '.gemini', 'antigravity', 'global_workflows');
|
|
776
|
+
fs.mkdirSync(globalDir, { recursive: true });
|
|
777
|
+
const COMMAND_MAP = {
|
|
778
|
+
'init.md': { name: 'pp-init', desc: '初始化项目知识图谱' },
|
|
779
|
+
'sync.md': { name: 'pp-sync', desc: '同步代码与知识图谱' },
|
|
780
|
+
'review.md': { name: 'pp-shencha', desc: '审查任务成果' },
|
|
781
|
+
'DiagnosticProtocol.md': { name: 'pp-fenxi', desc: '深度分析错误' },
|
|
782
|
+
'Execution_Task.md': { name: 'pp-task', desc: '执行开发任务' },
|
|
783
|
+
'Sentinel_4.md': { name: 'pp-audit', desc: '多Agent代码审计' },
|
|
784
|
+
'SynchronizationProtocol.md': { name: 'pp-syncpro', desc: '知识图谱同步协议' },
|
|
785
|
+
'Zero_Defec_Genesis.md': { name: 'plan', desc: '零缺陷创生协议' },
|
|
786
|
+
};
|
|
787
|
+
let count = 0;
|
|
788
|
+
for (const [file, mapping] of Object.entries(COMMAND_MAP)) {
|
|
789
|
+
const dest = path.join(globalDir, `${mapping.name}.md`);
|
|
790
|
+
if (fs.existsSync(dest))
|
|
791
|
+
continue;
|
|
792
|
+
const src = path.join(templatesDir, file);
|
|
793
|
+
if (!fs.existsSync(src))
|
|
794
|
+
continue;
|
|
795
|
+
const content = fs.readFileSync(src, 'utf-8');
|
|
796
|
+
fs.writeFileSync(dest, `---\ndescription: ${mapping.desc}\n---\n\n${content}`);
|
|
797
|
+
count++;
|
|
798
|
+
}
|
|
799
|
+
return count;
|
|
800
|
+
}
|
|
670
801
|
/** 获取 Claude Desktop 全局配置路径 */
|
|
671
802
|
function getClaudeGlobalPath() {
|
|
672
803
|
if (process.platform === 'win32')
|
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="例:
|
|
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&¤tDir){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}
|