bluelamp 6.6.10 → 6.6.13
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/README.md +20 -0
- package/dist/auth/InteractiveLogin.js +1 -1
- package/dist/auth/PortalAuthClient.js +1 -1
- package/dist/auth/TokenManager.js +1 -1
- package/dist/bin/bluelamp-secret.mjs +2 -0
- package/dist/bin/bluelamp-vscode-base.mjs +1 -1
- package/dist/lib/connectResilience.d.ts +43 -0
- package/dist/lib/connectResilience.d.ts.map +1 -0
- package/dist/lib/connectResilience.js +1 -0
- package/dist/lib/connectResilience.js.map +1 -0
- package/dist/lib/headlessLaunch.d.ts +27 -0
- package/dist/lib/headlessLaunch.d.ts.map +1 -1
- package/dist/lib/headlessLaunch.js +1 -1
- package/dist/lib/headlessLaunch.js.map +1 -1
- package/dist/lib/mcp-server/index.cjs +1 -1
- package/dist/lib/mcp-server/install-package.cjs +1 -1
- package/dist/lib/mcp-server/portal-api-client.cjs +1 -1
- package/dist/lib/secretExec.d.ts +86 -0
- package/dist/lib/secretExec.d.ts.map +1 -0
- package/dist/lib/secretExec.js +1 -0
- package/dist/lib/secretExec.js.map +1 -0
- package/dist/lib/sysPromptHandoff.d.ts +9 -0
- package/dist/lib/sysPromptHandoff.d.ts.map +1 -0
- package/dist/lib/sysPromptHandoff.js +1 -0
- package/dist/lib/sysPromptHandoff.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 負荷下でも portal へつながるための、接続まわりの耐性(箱 n=1959)。
|
|
3
|
+
*
|
|
4
|
+
* 🩸 塞いでいる穴(2026-09-23 実測・ami機 node v24.5.0):
|
|
5
|
+
* node 20+ の既定は `autoSelectFamily=true`・`autoSelectFamilyAttemptTimeout=250ms`。
|
|
6
|
+
* api.motohi.jp は A と AAAA を返し、IPv6 の経路が無い機体では IPv4 の 1 本だけが頼りになる。
|
|
7
|
+
* CPU 負荷で event loop が 250ms 以上止まると、**その IPv4 の試行がタイマーで打ち切られ**、
|
|
8
|
+
* IPv6 は EHOSTUNREACH で落ちて `AggregateError ETIMEDOUT`(**message が空**)になる。
|
|
9
|
+
* ⇒ 起動子は slot 設定を取れず `process.exit(1)` し、tmux session ごと消えていた。
|
|
10
|
+
* gateway は目録を取れず「Starting with no services」で立ち、本体プロンプトも載らなかった。
|
|
11
|
+
* 🔬 再現: DNS 解決の直後に loop を 400ms 塞ぐと 0/5・塞がないと 5/5(tests/connect-resilience.test.mjs)。
|
|
12
|
+
*
|
|
13
|
+
* 🔑 直し方は「試行の打ち切りを負荷の揺れより長くする」。`autoSelectFamily` 自体は切らない
|
|
14
|
+
* ——切ると IPv6 が先に返る網で IPv4 へ落ちられなくなる(直したい障害の裏返しを作る)。
|
|
15
|
+
*
|
|
16
|
+
* 2 本目の防御として、起動の前に失敗すると致命な呼び出しだけ「通信の失敗」に限って撃ち直す
|
|
17
|
+
* (判断と間隔はここの純関数・撃つのは呼び出し側)。404 / 403 のような**答えが返った失敗**は撃ち直さない。
|
|
18
|
+
*/
|
|
19
|
+
/** 1 回の接続試行を打ち切るまでの時間(ms)。既定 250ms では負荷の揺れに負ける。 */
|
|
20
|
+
export declare const CONNECT_ATTEMPT_TIMEOUT_MS = 5000;
|
|
21
|
+
/** 撃ち直しを含めた最大の試行回数(初回を含む)。 */
|
|
22
|
+
export declare const NETWORK_RETRY_ATTEMPTS = 3;
|
|
23
|
+
/** n 回目の失敗のあとに待つ時間(ms)。長さ = NETWORK_RETRY_ATTEMPTS - 1。 */
|
|
24
|
+
export declare const NETWORK_RETRY_DELAYS_MS: readonly number[];
|
|
25
|
+
/** `node:net` のうち、ここで使う口だけ。 */
|
|
26
|
+
export interface NetDefaultsLike {
|
|
27
|
+
setDefaultAutoSelectFamilyAttemptTimeout?: (value: number) => void;
|
|
28
|
+
getDefaultAutoSelectFamilyAttemptTimeout?: () => number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* プロセス全体の接続試行の打ち切りを伸ばす。**最初の socket より前に**呼ぶこと。
|
|
32
|
+
* API が無い node(< 20)では何もしない(その版は happy eyeballs を持たない=この穴も無い)。
|
|
33
|
+
* @returns 適用後の値。適用できなかったときは null
|
|
34
|
+
*/
|
|
35
|
+
export declare function applyConnectResilience(net: NetDefaultsLike): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* 通信の失敗に限って撃ち直す。`isNetworkFailure` が偽の結果(成功・404 等)はそのまま返す。
|
|
38
|
+
* @param attempt 1 回撃つ関数
|
|
39
|
+
* @param isNetworkFailure 撃ち直すべき結果か
|
|
40
|
+
* @param sleep 待ち(テストから差し替える)
|
|
41
|
+
*/
|
|
42
|
+
export declare function retryOnNetworkFailure<T>(attempt: () => Promise<T>, isNetworkFailure: (result: T) => boolean, sleep?: (ms: number) => Promise<void>): Promise<T>;
|
|
43
|
+
//# sourceMappingURL=connectResilience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectResilience.d.ts","sourceRoot":"","sources":["../../src/lib/connectResilience.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,mDAAmD;AACnD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,8BAA8B;AAC9B,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,2DAA2D;AAC3D,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAiB,CAAC;AAEvE,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC9B,wCAAwC,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,wCAAwC,CAAC,EAAE,MAAM,MAAM,CAAC;CACzD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAa1E;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,EACxC,KAAK,GAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAiD,GACnF,OAAO,CAAC,CAAC,CAAC,CAOZ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function applyConnectResilience(e){return"function"!=typeof e.setDefaultAutoSelectFamilyAttemptTimeout?null:(("function"==typeof e.getDefaultAutoSelectFamilyAttemptTimeout?e.getDefaultAutoSelectFamilyAttemptTimeout():0)<exports.CONNECT_ATTEMPT_TIMEOUT_MS&&e.setDefaultAutoSelectFamilyAttemptTimeout(exports.CONNECT_ATTEMPT_TIMEOUT_MS),"function"==typeof e.getDefaultAutoSelectFamilyAttemptTimeout?e.getDefaultAutoSelectFamilyAttemptTimeout():exports.CONNECT_ATTEMPT_TIMEOUT_MS)}async function retryOnNetworkFailure(e,t,T=e=>new Promise(t=>setTimeout(t,e))){let o=await e();for(let E=1;E<exports.NETWORK_RETRY_ATTEMPTS&&t(o);E++)await T(exports.NETWORK_RETRY_DELAYS_MS[E-1]??exports.NETWORK_RETRY_DELAYS_MS[exports.NETWORK_RETRY_DELAYS_MS.length-1]),o=await e();return o}Object.defineProperty(exports,"__esModule",{value:!0}),exports.NETWORK_RETRY_DELAYS_MS=exports.NETWORK_RETRY_ATTEMPTS=exports.CONNECT_ATTEMPT_TIMEOUT_MS=void 0,exports.applyConnectResilience=applyConnectResilience,exports.retryOnNetworkFailure=retryOnNetworkFailure,exports.CONNECT_ATTEMPT_TIMEOUT_MS=5e3,exports.NETWORK_RETRY_ATTEMPTS=3,exports.NETWORK_RETRY_DELAYS_MS=[1e3,2e3];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectResilience.js","sourceRoot":"","sources":["../../src/lib/connectResilience.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAsBH,wDAaC;AAQD,sDAWC;AApDD,mDAAmD;AACtC,QAAA,0BAA0B,GAAG,IAAI,CAAC;AAE/C,8BAA8B;AACjB,QAAA,sBAAsB,GAAG,CAAC,CAAC;AAExC,2DAA2D;AAC9C,QAAA,uBAAuB,GAAsB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAQvE;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,GAAoB;IACzD,IAAI,OAAO,GAAG,CAAC,wCAAwC,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACpF,MAAM,OAAO,GACX,OAAO,GAAG,CAAC,wCAAwC,KAAK,UAAU;QAChE,CAAC,CAAC,GAAG,CAAC,wCAAwC,EAAE;QAChD,CAAC,CAAC,CAAC,CAAC;IACR,wBAAwB;IACxB,IAAI,OAAO,GAAG,kCAA0B,EAAE,CAAC;QACzC,GAAG,CAAC,wCAAwC,CAAC,kCAA0B,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,OAAO,GAAG,CAAC,wCAAwC,KAAK,UAAU;QACvE,CAAC,CAAC,GAAG,CAAC,wCAAwC,EAAE;QAChD,CAAC,CAAC,kCAA0B,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CACzC,OAAyB,EACzB,gBAAwC,EACxC,QAAuC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEpF,IAAI,MAAM,GAAG,MAAM,OAAO,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,8BAAsB,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5E,MAAM,KAAK,CAAC,+BAAuB,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,+BAAuB,CAAC,+BAAuB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3G,MAAM,GAAG,MAAM,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
* launcher が読んだうえで**自前の一時 config を組み直して**渡している。素通ししてしまうと
|
|
26
26
|
* claude 側で bluelamp サーバー定義が二重になり、#432 で直したテナント引き継ぎを壊す。
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* 箱 n=1984: 無人の起動元(scheduler)が、席の最初の prompt そのものとして task を渡す flag。
|
|
30
|
+
* 背景(2026-09-20 実測): scheduler は席が立ってから tmux で task を打ち込んでいたが、その前に席が
|
|
31
|
+
* 「本体の定義に従い開始してください」で自走し、別の便の手順を撃って閉じた(task は 1 度も届かなかった)。
|
|
32
|
+
* ⇒ task を起動引数にして、最初の prompt を task にする=届く前に動く隙間を無くす。
|
|
33
|
+
*/
|
|
34
|
+
export declare const TASK_FILE_FLAG = "--task-file";
|
|
28
35
|
/**
|
|
29
36
|
* 利用者がヘッドレス起動を要求しているか。
|
|
30
37
|
* ⚠️ 判定は `-p` / `--print` の**現れ方**だけで行い、値は見ない
|
|
@@ -60,4 +67,24 @@ export interface BuildClaudeArgsResult {
|
|
|
60
67
|
* ⇒ 挨拶定型が無いので、利用者の `-p "<指示>"` が唯一の prompt になる。
|
|
61
68
|
*/
|
|
62
69
|
export declare function buildClaudeArgs(input: BuildClaudeArgsInput): BuildClaudeArgsResult;
|
|
70
|
+
export type LauncherTask =
|
|
71
|
+
/** `--task-file` 無し=従来どおり(挨拶定型で開始) */
|
|
72
|
+
{
|
|
73
|
+
kind: 'none';
|
|
74
|
+
}
|
|
75
|
+
/** task を最初の prompt にする */
|
|
76
|
+
| {
|
|
77
|
+
kind: 'task';
|
|
78
|
+
text: string;
|
|
79
|
+
}
|
|
80
|
+
/** 指定されたのに読めない/空=**起動しない**(task の無い席を自走させない) */
|
|
81
|
+
| {
|
|
82
|
+
kind: 'invalid';
|
|
83
|
+
error: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* `--task-file <path>` / `--task-file=<path>` を読む(純関数・読み込みは注入)。
|
|
87
|
+
* ⛔ 指定があるのに読めない・空なら invalid。呼び出し側は claude を起動せずに止める。
|
|
88
|
+
*/
|
|
89
|
+
export declare function readLauncherTask(argv: readonly string[], readFile: (p: string) => string): LauncherTask;
|
|
63
90
|
//# sourceMappingURL=headlessLaunch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/headlessLaunch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"headlessLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/headlessLaunch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAS5C;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAwBzE;AAED,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAclF;AAED,MAAM,MAAM,YAAY;AACtB,sCAAsC;AACpC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,2BAA2B;GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAChC,gDAAgD;GAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,YAAY,CAwBvG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isHeadlessArgv=isHeadlessArgv,exports.stripLauncherOwnedFlags=stripLauncherOwnedFlags,exports.buildClaudeArgs=buildClaudeArgs;const LAUNCHER_OWNED_VALUE_FLAGS=["--mcp-config"],LAUNCHER_OWNED_BOOLEAN_FLAGS=["--debug"],HEADLESS_FLAGS=["-p","--print"];function isHeadlessArgv(
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.TASK_FILE_FLAG=void 0,exports.isHeadlessArgv=isHeadlessArgv,exports.stripLauncherOwnedFlags=stripLauncherOwnedFlags,exports.buildClaudeArgs=buildClaudeArgs,exports.readLauncherTask=readLauncherTask,exports.TASK_FILE_FLAG="--task-file";const LAUNCHER_OWNED_VALUE_FLAGS=["--mcp-config",exports.TASK_FILE_FLAG],LAUNCHER_OWNED_BOOLEAN_FLAGS=["--debug"],HEADLESS_FLAGS=["-p","--print"];function isHeadlessArgv(e){return e.some(e=>"string"==typeof e&&HEADLESS_FLAGS.includes(e))}function stripLauncherOwnedFlags(e){const s=[];for(let r=0;r<e.length;r+=1){const t=e[r];if("string"!=typeof t)continue;if(LAUNCHER_OWNED_BOOLEAN_FLAGS.includes(t))continue;const n=LAUNCHER_OWNED_VALUE_FLAGS.find(e=>t===e||t.startsWith(`${e}=`));if(n){if(t===n){const s=e[r+1];"string"!=typeof s||s.startsWith("-")||(r+=1)}}else s.push(t)}return s}function buildClaudeArgs(e){const s=isHeadlessArgv(e.userArgs),r=stripLauncherOwnedFlags(e.userArgs);return{args:[...s?[]:[e.initialPrompt],"--dangerously-skip-permissions",...e.sysArgs,"--mcp-config",e.configPath,...r],headless:s}}function readLauncherTask(e,s){let r,t,n=!1;for(let s=0;s<e.length;s+=1){const t=e[s];if(t===exports.TASK_FILE_FLAG){n=!0;const t=e[s+1];r="string"!=typeof t||t.startsWith("-")?void 0:t}else"string"==typeof t&&t.startsWith(`${exports.TASK_FILE_FLAG}=`)&&(n=!0,r=t.slice(exports.TASK_FILE_FLAG.length+1)||void 0)}if(!n)return{kind:"none"};if(void 0===r)return{kind:"invalid",error:`${exports.TASK_FILE_FLAG} にファイルの path がありません`};try{t=s(r)}catch(e){return{kind:"invalid",error:`${exports.TASK_FILE_FLAG} ${r} を読めません: ${e instanceof Error?e.message:String(e)}`}}return 0===t.trim().length?{kind:"invalid",error:`${exports.TASK_FILE_FLAG} ${r} が空です`}:{kind:"task",text:t.trim()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessLaunch.js","sourceRoot":"","sources":["../../src/lib/headlessLaunch.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG
|
|
1
|
+
{"version":3,"file":"headlessLaunch.js","sourceRoot":"","sources":["../../src/lib/headlessLaunch.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AAsBH,wCAEC;AAMD,0DAwBC;AA2BD,0CAcC;AAcD,4CAwBC;AAnID;;;;;GAKG;AACU,QAAA,cAAc,GAAG,aAAa,CAAC;AAE5C,+CAA+C;AAC/C,MAAM,0BAA0B,GAAG,CAAC,cAAc,EAAE,sBAAc,CAAU,CAAC;AAC7E,MAAM,4BAA4B,GAAG,CAAC,SAAS,CAAU,CAAC;AAE1D,2CAA2C;AAC3C,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,SAAS,CAAU,CAAC;AAElD;;;;GAIG;AACH,SAAgB,cAAc,CAAC,IAAuB;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAK,cAAoC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5G,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CAAC,IAAuB;IAC7D,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAEtC,IAAK,4BAAkD,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QAEhF,MAAM,SAAS,GAAG,0BAA0B,CAAC,IAAI,CAC/C,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAC5C,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,wDAAwD;YACxD,uEAAuE;YACvE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACzB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,CAAC,IAAI,CAAC,CAAC;YAChE,CAAC;YACD,SAAS;QACX,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAoBD;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,KAA2B;IACzD,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,uBAAuB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE1D,MAAM,IAAI,GAAG;QACX,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC1C,gCAAgC;QAChC,GAAG,KAAK,CAAC,OAAO;QAChB,cAAc;QACd,KAAK,CAAC,UAAU;QAChB,GAAG,SAAS;KACb,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAUD;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,IAAuB,EAAE,QAA+B;IACvF,IAAI,IAAwB,CAAC;IAC7B,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,sBAAc,EAAE,CAAC;YAC3B,KAAK,GAAG,IAAI,CAAC;YACb,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,CAAC;aAAM,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,sBAAc,GAAG,CAAC,EAAE,CAAC;YAC3E,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,sBAAc,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC;QAC3D,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,sBAAc,qBAAqB,EAAE,CAAC;IAClG,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,sBAAc,IAAI,IAAI,YAAY,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACvH,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,sBAAc,IAAI,IAAI,OAAO,EAAE,CAAC;IAClG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal,listAvailableMcps:listAvailableMcps,downloadMcpPackage:downloadMcpPackage,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy}=require("./portal-api-client.cjs"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t,setLanguage:setLanguage}=requireFromPackage(require,"../../i18n"),{describeTokenForDebug:describeTokenForDebug}=requireFromPackage(require,"../../lib/debugRedaction"),{collectUpdateNotices:collectUpdateNotices,buildUpdateNoticeText:buildUpdateNoticeText}=require("./mcp-version-advisor.cjs"),{installPackageFiles:installPackageFiles,formatInstallFailure:formatInstallFailure,runProductionNpmInstall:runProductionNpmInstall}=require("./install-package.cjs"),spawn=require("cross-spawn"),{StringDecoder:StringDecoder}=require("string_decoder"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.MUSUHI_KEYWORD||"BL-要件定義",{TokenManager:TokenManager}=requireFromPackage(require,"../../auth/TokenManager");function resolveToken(){return TokenManager.loadTokenStringSync()}const PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnv=PORTAL_URL_ENV.includes("localhost"),PORTAL_URL=PORTAL_URL_ENV||(isDevEnv?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LANGUAGE=process.env.MUSUHI_LANGUAGE||"ja",CATALOG_CACHE_FILE=path.join(os.homedir(),".bluelamp-vscode","gateway-catalog.json"),CATALOG_CACHE_VERSION=1,CHILD_READY_TIMEOUT_MS=3e4,CHILD_NOT_READY_TEXT="起動中です。数秒後に再試行してください";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(` TOKEN (起動時スナップショット): ${describeTokenForDebug(resolveToken())}`),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`);const LEGACY_REGISTRY=path.join(os.homedir(),".bluelamp","registry.json");if(fs.existsSync(LEGACY_REGISTRY))try{fs.unlinkSync(LEGACY_REGISTRY),log(`[startup] Legacy registry.json removed: ${LEGACY_REGISTRY}`)}catch(e){log(`[startup] Failed to remove legacy registry.json: ${e.message}`)}class ChildMcpProcess{constructor(e,t,r,o){this.serviceId=e,this.command=t,this.args=(r||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...o},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer="",this.stdoutDecoder=new StringDecoder("utf8"),this.stderrDecoder=new StringDecoder("utf8"),this.stderrTail="",this._decodersEnded=!1}async start(){return new Promise((e,t)=>{let r=!1;const o=(e,t)=>{r||(r=!0,clearTimeout(s),e(t))},s=setTimeout(()=>{o(t,new Error(`Timeout: "${this.serviceId}" failed to start within 90s`)),this.stop()},9e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{o(t,new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.stdin.on("error",e=>{log(`[ChildMcp:${this.serviceId}:stdin] ${e.message}`)}),this.process.on("close",()=>{this._flushDecoders("close")}),this.process.on("exit",(e,r)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${r})`);const s=this.stderrTail.trim();o(t,new Error(`"${this.serviceId}" exited before ready (code=${e}, signal=${r})${s?` | stderr: ${s}`:""}`)),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()}),this.process.on("close",()=>{if(this._decodersEnded)return;this._decodersEnded=!0;const e=this.stdoutDecoder.end(),t=this.stderrDecoder.end();e&&(this.buffer+=e,log(`[ChildMcp] "${this.serviceId}" stdout に半端なバイトが ${Buffer.byteLength(e)} バイト残っていた(子が多バイト文字の途中で終了)⇒ buffer へ流した。⛔ これは「壊れた文字」であって「失われた文字」ではない`)),t&&log(`[ChildMcp] "${this.serviceId}" stderr に半端なバイトが ${Buffer.byteLength(t)} バイト残っていた ⇒ ${JSON.stringify(t)}`)}),this.process.stderr.on("data",e=>{const t=this.stderrDecoder.write(e);this.stderrTail=sliceTailPreservingSurrogates(this.stderrTail+t,500),log(`[ChildMcp:${this.serviceId}:stderr] ${t.trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=this.stdoutDecoder.write(e),this._processBuffer()}),this._sendRequest("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"bluelamp-gateway",version:"1.0.0"}}).then(e=>(log(`[ChildMcp] "${this.serviceId}" initialized: ${JSON.stringify(e.serverInfo||{})}`),this._sendNotification("notifications/initialized",{}),this._sendRequest("tools/list",{}))).then(t=>{const r=t.tools||[];this.tools=r.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),o(e,this.tools)}).catch(e=>{o(t,e)})}catch(e){o(t,e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}_flushDecoders(e){for(const[t,r]of[["stdout",this.stdoutDecoder],["stderr",this.stderrDecoder]]){let o="";try{o=r.end()}catch(e){log(`[ChildMcp:${this.serviceId}:${t}] decoder.end() が落ちた: ${e.message}`);continue}o&&log(`[ChildMcp:${this.serviceId}:${t}] 🚨 未完の多バイト列を ${e} 時に検出: ${JSON.stringify(o)}(${o.length} 文字・⛔ 黙って捨てていない)`)}}stop(){this.process&&(this.process.kill(),this.process=null),this.pending.forEach(e=>e.reject(new Error("Process stopped"))),this.pending.clear()}_sendRequest(e,t){return new Promise((r,o)=>{const s=++this.requestId,i=JSON.stringify({jsonrpc:"2.0",id:s,method:e,params:t})+"\n";this.pending.set(s,{resolve:r,reject:o});try{this.process.stdin.write(i)}catch(e){this.pending.delete(s),o(new Error(`Write error: ${e.message}`))}})}_sendNotification(e,t){const r=JSON.stringify({jsonrpc:"2.0",method:e,params:t})+"\n";try{this.process.stdin.write(r)}catch(e){log(`[ChildMcp] Notification write error: ${e.message}`)}}_processBuffer(){const e=this.buffer.split("\n");this.buffer=e.pop();for(const t of e){const e=t.trim();if(e)try{const t=JSON.parse(e);if(t.id&&this.pending.has(t.id)){const e=this.pending.get(t.id);this.pending.delete(t.id),t.error?e.reject(new Error(`JSON-RPC error: ${JSON.stringify(t.error)}`)):e.resolve(t.result)}}catch(t){log(`[ChildMcp:${this.serviceId}] Parse error: ${t.message} | line: ${e.substring(0,200)}`)}}}}class ChildMcpManager{constructor(){this.children=new Map,this.starting=new Map}async activateExternal(e,t){const r=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),o=await r.start();return this.children.set(e,{type:"external",process:r,tools:o,service:t}),o}beginActivateExternal(e,t){const r=this.children.get(e);if(r)return Promise.resolve({ok:!0,tools:r.tools||[]});const o=this.starting.get(e);if(o)return o;const s=this.activateExternal(e,t).then(e=>({ok:!0,tools:e}),e=>({ok:!1,error:e})).then(t=>(this.starting.delete(e),t));return this.starting.set(e,s),s}isStarting(e){return this.starting.has(e)}async waitForReady(e,t){if(this.children.has(e))return{ready:!0};const r=this.starting.get(e);if(!r)return{ready:!1,reason:"unknown"};let o=null;const s=new Promise(e=>{o=setTimeout(()=>e({timedOut:!0}),t),"function"==typeof o.unref&&o.unref()});try{const e=await Promise.race([r,s]);return e.timedOut?{ready:!1,reason:"timeout"}:e.ok?{ready:!0}:{ready:!1,reason:"failed",error:e.error}}finally{o&&clearTimeout(o)}}deactivate(e){const t=this.children.get(e);t&&("external"===t.type&&t.process&&t.process.stop(),this.children.delete(e))}isActive(e){return this.children.has(e)}getActiveTools(){const e=[];for(const t of this.children.values())e.push(...t.tools||[]);return e}async callTool(e,t){for(const[r,o]of this.children)if("external"===o.type){const r=o.tools.find(t=>t.name===e);if(r)return{found:!0,result:await o.process.callTool(r._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.updateNoticeText=null,this._catalogTools=new Map,this._cloudProxyTools=new Map,this._catalogServices=new Map,this._needChildForSchema=new Set,this._catalogFromCache=!1,this._configRefresh=null,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return resolveToken()?{ok:!0}:{ok:!1,error:"BlueLamp にログインしてください。ターミナルで `bluelamp1` を一度実行すると ~/.musuhi/portal-token.enc が作成されます。"}}buildToolList(){return buildGatewayToolList([{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]}},{name:"search_mcp",description:'キーワードでインストール済みMCPサービスを検索します。使いたい機能を自然言語で説明してください。(例: "ブラウザ操作", "メール送信", "画像生成")',inputSchema:{type:"object",properties:{query:{type:"string",description:"検索キーワード(自然言語OK)"}},required:["query"]}},{name:"activate_service",description:"deactivate_service で一覧から外した MCP を、道具一覧に戻します(deactivate を戻すだけ)。権限のある MCP は最初から一覧に出ているので、通常このツールを撃つ必要はありません。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"後方互換のため受け付けますが、一覧の可否には使いません(無視されます)"}},required:["service_id"]}},{name:"deactivate_service",description:"指定した MCP を道具一覧から外します(deactivated=true)。次の接続でも一覧に出なくなります。権限は変わらないので activate_service でいつでも戻せます。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"restart_service",description:"MCPサービスのプロセスを再起動します。コード更新や設定変更の反映に使用します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"unregister_service",description:"MCPサービスをレジストリから完全に削除します。プロセスも停止します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"削除するサービスID"}},required:["service_id"]}},{name:"register_service",description:"新しいMCPサービスをゲートウェイに登録します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:'サービスID(英数字、例: "microcms")'},name:{type:"string",description:"サービス表示名"},description:{type:"string",description:"サービスの説明"},tags:{type:"array",items:{type:"string"},description:"検索用タグ"},command:{type:"string",description:'実行コマンド("npx" or "node")'},args:{type:"array",items:{type:"string"},description:"コマンド引数"},env:{type:"object",description:"環境変数"}},required:["service_id","name","description","tags"]}},{name:"browse_mcp_store",description:"MCPストア(Portal)からインストール可能なMCP一覧を取得します。",inputSchema:{type:"object",properties:{},required:[]}},{name:"install_from_store",description:"MCP ストアから MCP を取得してこの機体に展開し、env と導入版をサーバに記録します。browse_mcp_store で見つけた mcp_id を指定してください。道具一覧に出るかどうかは権限で決まるので、権限の無い MCP をこれで一覧に出すことはできません(自分で作った MCP・共有された MCP は権限に含まれるため、何もしなくても一覧に出ます)。",inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:"インストールするMCPのID"}},required:["mcp_id"]}},{name:"save_mcp_env",description:"MCPの環境変数(APIキー等)をサーバーに安全に保存します。ローカルには保存されません。次回起動時にサーバーから自動取得されます。解約時はアクセスできなくなります。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"MCP サービスID(例: heygen, gemini-image)"},env:{type:"object",description:'環境変数のキーと値(例: {"HEYGEN_API_KEY": "sk_xxx"})',additionalProperties:{type:"string"}}},required:["service_id","env"]}}],this._catalogTools,this.childManager.getActiveTools())}applyCatalog(e,t={}){const{catalog:r,cloudProxy:o,warnings:s,needChildForSchema:i}=buildCatalogRegistry(e);this._catalogTools=r,this._cloudProxyTools=o,this._catalogServices=new Map,this._needChildForSchema=new Set(i),this._catalogFromCache=!0===t.fromCache;for(const t of Array.isArray(e)?e:[])t&&t.mcpId&&this._catalogServices.set(t.mcpId,t);let n=0;for(const e of s)"no-input-schema"===e.reason?(n+=e.count,log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}): ${e.count}/${e.total} tools have no inputSchema — 一覧には出す(空 schema)。子を起こして実 schema で上書きする`)):log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return n>0&&log(`[catalog] inputSchema 欠損の合計: ${n} 本 / ${this._needChildForSchema.size} service(一覧には出す。子を起こして実 schema で上書きする)`),log(`[catalog] ${r.size} tools registered from ${this._catalogServices.size} services (cloud_proxy: ${o.size})`),r}registerServiceInCatalog(e){const{catalog:t,cloudProxy:r,warnings:o}=buildCatalogRegistry([e]);for(const[e,r]of t)this._catalogTools.set(e,r);for(const[e,t]of r)this._cloudProxyTools.set(e,t);for(const e of o)log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return e&&e.mcpId&&this._catalogServices.set(e.mcpId,e),t.size}unregisterServiceFromCatalog(e){const t=`${e}__`;let r=0;for(const e of[...this._catalogTools.keys()])e.startsWith(t)&&(this._catalogTools.delete(e),this._cloudProxyTools.delete(e),r++);return r}buildChildService(e,t){const r=path.join(os.homedir(),".claude","mcp-servers",e),o=t.entryPoint||"dist/index.js";return{name:t.name,description:t.description,type:"external",command:t.command||"node",args:t.args&&t.args.length?t.args:[path.join(r,o)],env:t.env||{}}}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{const e=this.buildToolList(),t=this.childManager.getActiveServiceIds();return log(`[ListTools] ${e.length} tools (active: ${t.join(", ")||"none"})`),{tools:e}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r,arguments:o}=e.params;log(`[CallTool] ${r}`);const s=resolveToken();if("load_knowledge"===r){const{keyword:e}=o;log(`[load_knowledge] keyword: ${e}`);try{const r=await getPromptFromPortal(e,s,LANGUAGE);if(!r||!r.success){const o=r?.error||t("mcp.knowledgeNotFound",{keyword:e});return{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"",i=this.updateNoticeText?`\n\n${this.updateNoticeText}`:"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${r.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}${i}`}]}}catch(e){return log(`[load_knowledge] Exception: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("search_mcp"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};if(!s)return{content:[{type:"text",text:"❌ 未認証です。MCPカタログの検索にはログインが必要です。"}],isError:!0};let t=[];try{const e=await searchMcpServices(o.query,s);if(!e.success)return{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.error||"Unknown"}`}],isError:!0};t=(e.data||[]).map(e=>({id:e.mcp_id,service:{name:e.name,description:e.description,tags:[],alwaysOn:e.alwaysOn||!1},isInstalled:e.isInstalled||!1}))}catch(e){return log(`[search_mcp] Server search failed: ${e.message}`),{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.message}`}],isError:!0}}if(0===t.length)return{content:[{type:"text",text:`🔍 「${o.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、browse_mcp_storeでカタログを確認してください。`}]};let r=`🔍 「${o.query}」の検索結果(${t.length}件)\n\n`;for(const e of t.slice(0,10)){const t=e.id,o=e.service,s=this.childManager.isActive(t),i=!0===o.deactivated,n=e.isInstalled?"":" [未インストール]",a=i?" [一覧から外し中]":"";r+=`${i?"🚫":s?"🟢":"⚪"} **${o.name}**${a}${n} (id: \`${t}\`)\n`,r+=` ${o.description}\n\n`}return r+="一覧に戻す: activate_service | ストアから導入: install_from_store",{content:[{type:"text",text:r}]}}if("activate_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t}=o,r=await getGatewayConfig(s);if(!r.success)return{content:[{type:"text",text:`❌ サーバー設定の取得に失敗: ${r.error||"Unknown"}`}],isError:!0};const i=(r.data?.services||[]).find(e=>e.mcpId===t);if(!i)return{content:[{type:"text",text:`❌ サービス "${t}" はあなたの権限の中にありません(権限のある MCP は何もしなくても一覧に出ます)。`}],isError:!0};const n=await setServiceDeactivated(t,!1,s);if(!n.success)return{content:[{type:"text",text:`❌ "${t}" を一覧に戻せませんでした: ${n.error||"Unknown"}`}],isError:!0};log(`[activate] "${t}" deactivated=false (一覧に戻した)`);const a=this.registerServiceInCatalog({...i,deactivated:!1});if(0===a)return{content:[{type:"text",text:`❌ "${t}" には目録にツール定義がありません(tools が空)。`}],isError:!0};if("cloud_proxy"===i.hostingType){log(`[activate] "${t}" (cloud_proxy) → ${a} proxy tools registered`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${i.name}" を一覧に戻しました(cloud_proxy ツール${a}個)`}]}}if(this.childManager.isActive(t)){try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${t}" を一覧に戻しました(子プロセスは既に起動済み)。`}]}}log(`[activate] "${t}" (${i.hostingType}) → ${a} catalog tools restored. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${i.name}" を一覧に戻しました(ツール${a}個)。最初に呼ばれた時に自動で起動します。`}]}}if("deactivate_service"===r){const{service_id:e}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const t=await setServiceDeactivated(e,!0,s);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" を一覧から外せませんでした: ${t.error||"Unknown"}`}],isError:!0};log(`[deactivate] "${e}" deactivated=true (一覧から外した)`);const r=this.unregisterServiceFromCatalog(e);r>0&&log(`[deactivate] "${e}" → ${r} catalog tools removed`),this._catalogServices.delete(e);let i=!1;if(this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[deactivate] "${e}" child process stopped`),i=!0),r>0||i)try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[deactivate] notification error: ${e.message}`)}let n=`✅ "${e}" を道具一覧から外しました(deactivated=true)。`;return r>0&&(n+=` ツール${r}個を一覧から削除しました。`),i&&(n+=" 子プロセスを停止しました。"),n+=" 次の接続でも一覧に出ません。権限は変わっていないので activate_service で戻せます。",{content:[{type:"text",text:n}]}}if("restart_service"===r){const{service_id:e}=o,t=this.childManager.children.get(e),r=t?.service;if(!r)return isCloudProxyService(this._cloudProxyTools,e)?{content:[{type:"text",text:`ℹ️ "${e}" は cloud_proxy 方式のため、ローカルの再起動対象ではありません(子プロセスを持ちません)。\nサーバ側のツール更新は deploy で全利用者に反映されるので、再起動は不要です。\nツール一覧を取り直したいだけなら deactivate_service → activate_service を実行してください。`}]}:{content:[{type:"text",text:`❌ "${e}" の子プロセスは現在動いていません。道具を呼べば自動で起動します(restart は起動済みの子にだけ効きます)。`}],isError:!0};let i,n=r;if(s)try{const t=await getGatewayConfig(s);if(t.success){const o=(t.data?.services||[]).find(t=>t.mcpId===e);o&&o.env&&(n={...r,env:o.env},log(`[restart] "${e}" env refreshed from server (keys=${Object.keys(o.env).join(",")||"none"})`))}}catch(e){log(`[restart] env refresh failed (using cached): ${e.message}`)}this.childManager.deactivate(e),log(`[restart] "${e}" stopped`);try{i=await this.childManager.activateExternal(e,n)}catch(t){return log(`[restart] Failed "${e}": ${t.message}`),{content:[{type:"text",text:`❌ "${e}" の再起動に失敗しました: ${t.message}`}],isError:!0}}log(`[restart] "${e}" → ${i.length} tools. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[restart] notification error: ${e.message}`)}const a=i.map(e=>e.name).join(", ");return{content:[{type:"text",text:`🔄 "${n.name}" を再起動しました(ツール${i.length}個)\n\nツール: ${a||"(なし)"}`}]}}if("unregister_service"===r){const{service_id:e}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[unregister] "${e}" stopped`));const t=await uninstallMcpService(e,s);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" のサーバー削除に失敗: ${t.error||"Unknown"}`}],isError:!0};log(`[unregister] "${e}" uninstalled from server`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[unregister] notification error: ${e.message}`)}return{content:[{type:"text",text:`🗑️ "${e}" を削除しました。プロセスも停止済みです。`}]}}if("register_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const i=r||{},n=await installMcpService(t,!1,s,i);return n.success?(log(`[register] "${t}" installed on server`),{content:[{type:"text",text:`✅ サービス "${t}" をサーバーに登録しました。\n\n次のステップ:\n- save_mcp_env で APIキー等を設定\n- 次の接続で道具一覧に出ます(activate は不要です)`}]}):{content:[{type:"text",text:`❌ "${t}" の登録に失敗しました: ${n.error||"Unknown"}\n\n※ MCP はサーバーカタログに登録されている必要があります。browse_mcp_store で確認してください。`}],isError:!0}}if("save_mcp_env"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=o;if(!t||!r||0===Object.keys(r).length)return{content:[{type:"text",text:"❌ service_id と env(キー/値のペア)が必要です"}],isError:!0};log(`[save_mcp_env] ${t}: ${Object.keys(r).join(", ")}`);try{const e=await updateMcpEnv(t,r,s);if(!e.success)return{content:[{type:"text",text:`❌ サーバー保存失敗: ${e.error}`}],isError:!0};const o=Object.keys(r).join(", ");return e.warning?{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n${e.warning}`}]}:{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n※ ローカルには保存されません。次回起動時にサーバーから自動取得されます。`}]}}catch(e){return log(`[save_mcp_env] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("browse_mcp_store"===r){log("[browse_mcp_store]");try{const e=await listAvailableMcps({},s,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${e?.error||"Unknown"}`}],isError:!0};const t=e.data||[];if(0===t.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let r=`📦 MCPストア一覧 (${t.length}件)\n\n`;return t.forEach(e=>{const t=e.isOfficial?" [公式]":"";r+=`**${e.name}**${t}\n`,r+=` ID: ${e.mcpId}\n`,r+=` ${e.description}\n`,r+=` ツール数: ${e.toolCount}\n\n`}),r+='\nインストールするには: install_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:r}]}}catch(e){return log(`[browse_mcp_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_from_store"===r){const{mcp_id:e}=o;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,s);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const r=t.data,o=r.packageSource||{},i=r.files||[];let n=o.hostingType||o.hosting&&o.hosting.type||null;if(s){let t=!1;try{const r=await getGatewayConfig(s);if(r.success){const o=(r.data?.services||[]).find(t=>t.mcpId===e);o&&(t=!0===o.alwaysOn,n||(n=o.hostingType))}}catch(e){}const r=await installMcpService(e,t,s);if(!r.success)return{content:[{type:"text",text:`❌ サーバー登録失敗: ${r.error||"Unknown"}`}],isError:!0};log(`[install_from_store] Server sync OK: ${e} (alwaysOn=${t})`)}if("cloud_proxy"===n){log(`[install_from_store] "${e}" is cloud_proxy, skipping local extract`);let t=`✅ MCP「${o.name||e}」をインストールしました(cloud_proxy)\n\n`;return t+="☁ クラウド実行のためローカル展開は不要です\n",o.installNotes&&(t+=`\n📝 ${o.installNotes}\n`),t+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:t}]}}if(0===i.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const a=path.join(os.homedir(),".claude","mcp-servers",e),c=installPackageFiles({installDir:a,files:i,needsNpmInstall:!!o.needsNpmInstall,runNpmInstall:e=>runProductionNpmInstall(e)});if(!c.ok)return log(`[install_from_store] ${e} failed at ${c.stage}: ${c.error.message}`),{content:[{type:"text",text:formatInstallFailure(o.name||e,c)}],isError:!0};log(`[install_from_store] ${i.length} files written to ${a}`);let l=`✅ MCP「${o.name||e}」をインストールしました\n\n`;l+=`📁 インストール先: ${a}\n`,l+=`📄 ファイル数: ${i.length}\n`,o.installNotes&&(l+=`\n📝 ${o.installNotes}\n`);const d=(o.requiredEnvVars||[]).filter(e=>!process.env[e]);return d.length>0&&(l+="\n⚠️ 以下の環境変数を save_mcp_env で設定してください:\n",d.forEach(e=>{l+=` - ${e}\n`})),l+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:l}]}}catch(e){return log(`[install_from_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if(this._cloudProxyTools&&this._cloudProxyTools.has(r)){const e=this._cloudProxyTools.get(r);log(`[callTool] Cloud proxy: ${r} → ${e.mcpId}/${e.toolName}`);try{const t=await callMcpToolProxy(e.mcpId,e.toolName,o,s);return t.success&&t.data?t.data.content?t.data:{content:[{type:"text",text:JSON.stringify(t.data,null,2)}]}:{content:[{type:"text",text:`❌ プロキシエラー: ${t.error||"Unknown"}`}],isError:!0}}catch(e){return log(`[callTool] Cloud proxy error "${r}": ${e.message}`),{content:[{type:"text",text:`❌ クラウドプロキシエラー: ${e.message}`}],isError:!0}}}try{const{found:e,result:t}=await this.childManager.callTool(r,o);if(e)return t&&t.content?t:{content:[{type:"text",text:String(t)}]}}catch(e){return log(`[callTool] Error proxying "${r}": ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}const i=this._catalogTools.get(r);if(i&&"cloud_proxy"!==i.hostingType){const e=i.mcpId;this._catalogFromCache&&this._configRefresh&&(log(`[callTool] "${e}" は目録キャッシュ由来。起動仕様の到着を待つ。`),await this._configRefresh),this.startChildOnDemand(e);const t=await this.childManager.waitForReady(e,3e4);if(!t.ready){if("failed"===t.reason){const r=t.error?.message||String(t.error||"Unknown");return log(`[callTool] "${e}" failed to start: ${r}`),{content:[{type:"text",text:`❌ "${e}" の起動に失敗しました: ${r}`}],isError:!0}}return"timeout"===t.reason?(log(`[callTool] "${e}" not ready within 30000ms for "${r}".`),{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}):(log(`[callTool] "${e}" could not be started for "${r}" (reason=${t.reason}).`),{content:[{type:"text",text:`❌ "${e}" を起動できませんでした(設定が見つかりません)。`}],isError:!0})}try{const{found:t,result:s}=await this.childManager.callTool(r,o);if(t)return s&&s.content?s:{content:[{type:"text",text:String(s)}]};log(`[callTool] "${r}" is in catalog but child "${e}" does not have it.`)}catch(e){return log(`[callTool] Error proxying "${r}" after ready: ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`❌ Unknown tool: ${r}`}],isError:!0}})}startChildrenForMissingSchema(){let e=0;for(const t of this._needChildForSchema){const r=this._catalogServices.get(t);if(r)if("cloud_proxy"!==r.hostingType){if(!this.childManager.isActive(t)&&!this.childManager.isStarting(t)){log(`[schemaChild] "${t}" (${r.hostingType}) を起こして実 schema を取りに行く`);try{this.startChildOnDemand(t),e++}catch(e){log(`[schemaChild] "${t}" の起動に失敗: ${e.message}(⛔ 席は落とさない)`)}}}else log(`[schemaChild] "${t}" は cloud_proxy のため子を起こせない。schema 欠損の道具は一覧に出ない。`)}return log(`[schemaChild] ${e} service(=目録に schema が無い道具を持つ service の数)`),e}async loadCatalog(e){if(!e)return log("[catalog] No token, starting with no catalog tools."),[];let t=[];try{const r=await getGatewayConfig(e);if(!r.success)return log(`[catalog] Server config failed: ${r.error||"Unknown"}. Starting with no services.`),[];t=r.data?.services||[],log(`[catalog] Server config: ${t.length} services`),this.applyCatalog(t),writeCatalogCache(CATALOG_CACHE_FILE,t,catalogCacheFingerprint(e),log)}catch(e){return log(`[catalog] Server config error: ${e.message}. Starting with no services.`),[]}try{const e=collectUpdateNotices(t);this.updateNoticeText=buildUpdateNoticeText(e);for(const t of e)"not-applicable"!==t.status&&log(`[versionCheck] ${t.mcpId}: ${t.status} (${t.reason}) catalog=${t.catalogVersion} local=${t.localVersion}`)}catch(e){log(`[versionCheck] skipped: ${e.message}`)}return t}startChildOnDemand(e){const t=this._catalogServices.get(e);if(!t||!0===t.deactivated)return null;if("cloud_proxy"===t.hostingType)return null;if(this.childManager.isActive(e))return null;const r=this.childManager.isStarting(e),o=this.childManager.beginActivateExternal(e,this.buildChildService(e,t));return r||log(`[startChild] "${e}" (${t.hostingType}) starting on demand...`),o.then(t=>this.reconcileChildWithCatalog(e,t)).catch(t=>log(`[startChild] "${e}" post-start error: ${t.message}`)),o}async reconcileChildWithCatalog(e,t){const r=decideCatalogReconciliation(this._catalogTools,e,t);if(!r.ready){const o=t&&t.error?`: ${t.error.message||t.error}`:"";return log(`[versionDrift] "${e}" not ready (${r.reason})${o} → 剪定も通知もしない`),r}if(!r.notify)return log(`[versionDrift] "${e}" matches catalog (${t.tools.length} tools) → no list_changed`),r;for(const e of r.prune)this._catalogTools.delete(e),this._cloudProxyTools.delete(e);const o=r.diff;log(`[versionDrift] "${e}" differs from catalog: +${o.added.length} -${o.removed.length} ~${o.changed.length} → list_changed`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[versionDrift] notification error: ${e.message}`)}return r}toolListSignature(){return stableJson(this.buildToolList().map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})))}async refreshCatalogInBackground(e){const t=this.toolListSignature();try{await this.loadCatalog(e)}catch(e){return log(`[catalogRefresh] failed, keeping cached catalog: ${e.message}`),{notified:!1,reason:"failed"}}if(t===this.toolListSignature())return log("[catalogRefresh] server catalog matches cache → no list_changed"),{notified:!1,reason:"same"};log("[catalogRefresh] server catalog differs from cache → list_changed");try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[catalogRefresh] notification error: ${e.message}`)}return{notified:!0,reason:"diff"}}async run(){const e=resolveToken(),t=catalogCacheFingerprint(e),r=readCatalogCache(CATALOG_CACHE_FILE,t,log);r?(this.applyCatalog(r,{fromCache:!0}),log(`[catalogCache] ${this._catalogTools.size} tools from cache (HTTP は待たない)`)):(log("[catalogCache] no usable cache, waiting for server config"),await this.loadCatalog(e));const o=new StdioServerTransport;await this.server.connect(o),log(`[Gateway] BlueLamp Gateway v2.0.0 started. (${this._catalogTools.size} catalog tools available at connect)`),r?(this._configRefresh=this.refreshCatalogInBackground(e).catch(e=>(log(`[catalogRefresh] unexpected: ${e.message}`),{notified:!1,reason:"failed"})),this._configRefresh.then(()=>this.startChildrenForMissingSchema()).catch(e=>log(`[schemaChild] deferred start failed: ${e.message}`))):this.startChildrenForMissingSchema();const s=resolveToken();if(KEYWORD&&s){log(`[Gateway] Auto-loading knowledge for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,s,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}function buildCatalogRegistry(e){const t=new Map,r=new Map,o=[],s=[];for(const i of Array.isArray(e)?e:[]){if(!i||"string"!=typeof i.mcpId||!i.mcpId)continue;const e=i.hostingType||null;if(!0===i.deactivated)continue;const n=Array.isArray(i.tools)?i.tools:[];if(0===n.length){o.push({mcpId:i.mcpId,hostingType:e,reason:"no-tools"});continue}const a=[];for(const s of n){if(!s||"string"!=typeof s.name||!s.name){o.push({mcpId:i.mcpId,hostingType:e,reason:"unnamed-tool"});continue}s.inputSchema&&"object"==typeof s.inputSchema||a.push(s.name);const n=`${i.mcpId}__${s.name}`,c={name:n,mcpId:i.mcpId,toolName:s.name,hostingType:e,description:s.description,inputSchema:s.inputSchema};t.set(n,c),"cloud_proxy"===e&&r.set(n,{mcpId:i.mcpId,toolName:s.name,description:s.description,inputSchema:s.inputSchema})}a.length>0&&(o.push({mcpId:i.mcpId,hostingType:e,reason:"no-input-schema",count:a.length,total:n.length,tools:a}),s.push(i.mcpId))}return{catalog:t,cloudProxy:r,warnings:o,needChildForSchema:s}}function catalogEntryToTool(e){return{name:e.name,description:e.description||`[${e.mcpId}] ${e.toolName}`,inputSchema:e.inputSchema||{type:"object",properties:{}}}}function buildGatewayToolList(e,t,r){const o=new Map;for(const t of Array.isArray(e)?e:[])t&&t.name&&o.set(t.name,t);if(t&&"function"==typeof t.values)for(const e of t.values())o.has(e.name)||o.set(e.name,catalogEntryToTool(e));for(const e of Array.isArray(r)?r:[])e&&e.name&&o.set(e.name,e);return[...o.values()]}function sanitizeServicesForCache(e){return(Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.mcpId&&e.mcpId).map(e=>({mcpId:e.mcpId,name:e.name,description:e.description,hostingType:e.hostingType||null,deactivated:!0===e.deactivated,tools:(Array.isArray(e.tools)?e.tools:[]).filter(e=>e&&"string"==typeof e.name&&e.name).map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))}))}function catalogCacheFingerprint(e){return e?require("crypto").createHash("sha256").update(String(e)).digest("hex").slice(0,16):null}function sliceTailPreservingSurrogates(e,t){const r=String(null==e?"":e);if(r.length<=t)return r;const o=r.slice(-t),s=o.charCodeAt(0);return s>=56320&&s<=57343?o.slice(1):o}function stableJson(e){return void 0===e?"null":null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(stableJson).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${stableJson(e[t])}`).join(",")}}`}function diffChildToolsAgainstCatalog(e,t,r){const o=`${t}__`,s=new Set;if(e&&"function"==typeof e.keys)for(const t of e.keys())"string"==typeof t&&t.startsWith(o)&&s.add(t);const i=[],n=[],a=new Set;for(const t of Array.isArray(r)?r:[]){if(!t||!t.name)continue;a.add(t.name);const r=e&&"function"==typeof e.get?e.get(t.name):void 0;r?stableJson(r.description)===stableJson(t.description)&&stableJson(r.inputSchema)===stableJson(t.inputSchema)||n.push(t.name):i.push(t.name)}const c=[...s].filter(e=>!a.has(e));return{added:i,removed:c,changed:n,hasDiff:i.length+c.length+n.length>0}}function decideCatalogReconciliation(e,t,r){if(!r||!0!==r.ok||!Array.isArray(r.tools))return{ready:!1,prune:[],notify:!1,reason:r&&r.reason||"not-ready",diff:null};const o=diffChildToolsAgainstCatalog(e,t,r.tools);return{ready:!0,prune:o.removed,notify:o.hasDiff,reason:"ready",diff:o}}function readCatalogCache(e,t,r){const o=r||(()=>{});if(!t)return null;let s,i;try{s=fs.readFileSync(e,"utf8")}catch(e){return"ENOENT"!==e.code&&o(`[catalogCache] read failed: ${e.message}`),null}try{i=JSON.parse(s)}catch(e){return o(`[catalogCache] broken JSON, ignoring: ${e.message}`),null}return i&&1===i.version?i.tokenFingerprint!==t?(o("[catalogCache] fingerprint mismatch (口座が違う), ignoring"),null):Array.isArray(i.services)?i.services:(o("[catalogCache] services が配列でない, ignoring"),null):(o(`[catalogCache] version mismatch (${i&&i.version}), ignoring`),null)}function writeCatalogCache(e,t,r,o){const s=o||(()=>{});if(!r)return!1;try{fs.mkdirSync(path.dirname(e),{recursive:!0});const o={version:1,tokenFingerprint:r,savedAt:(new Date).toISOString(),services:sanitizeServicesForCache(t)},s=`${e}.${process.pid}.tmp`;return fs.writeFileSync(s,JSON.stringify(o),{mode:384}),fs.renameSync(s,e),!0}catch(e){return s(`[catalogCache] write failed: ${e.message}`),!1}}function isCloudProxyService(e,t){if(!e||"function"!=typeof e.keys)return!1;const r=`${t}__`;for(const t of e.keys())if("string"==typeof t&&t.startsWith(r))return!0;return!1}if(module.exports={ChildMcpProcess:ChildMcpProcess,ChildMcpManager:ChildMcpManager,BlueLampServer:BlueLampServer,isCloudProxyService:isCloudProxyService,buildCatalogRegistry:buildCatalogRegistry,buildGatewayToolList:buildGatewayToolList,catalogEntryToTool:catalogEntryToTool,diffChildToolsAgainstCatalog:diffChildToolsAgainstCatalog,decideCatalogReconciliation:decideCatalogReconciliation,sanitizeServicesForCache:sanitizeServicesForCache,catalogCacheFingerprint:catalogCacheFingerprint,readCatalogCache:readCatalogCache,writeCatalogCache:writeCatalogCache,CATALOG_CACHE_FILE:CATALOG_CACHE_FILE,CATALOG_CACHE_VERSION:1,stableJson:stableJson,sliceTailPreservingSurrogates:sliceTailPreservingSurrogates,CHILD_READY_TIMEOUT_MS:3e4,CHILD_NOT_READY_TEXT:CHILD_NOT_READY_TEXT},require.main===module){const e=new BlueLampServer;let t=!1;const r=r=>{if(!t){t=!0;try{log(`[Gateway] Shutdown (${r}) - cleaning up children...`)}catch(e){}if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{try{log(`[Gateway] Stopping child: ${t}`)}catch(e){}r&&"external"===r.type&&r.process&&"function"==typeof r.process.stop&&r.process.stop()}catch(e){}setTimeout(()=>process.exit(0),200).unref()}};process.on("SIGTERM",()=>r("SIGTERM")),process.on("SIGINT",()=>r("SIGINT")),process.on("SIGHUP",()=>r("SIGHUP")),process.stdin.on("end",()=>r("stdin-end")),process.stdin.on("close",()=>r("stdin-close")),process.on("exit",()=>{if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{r&&r.process&&r.process.process&&"function"==typeof r.process.process.kill&&r.process.process.kill("SIGKILL")}catch(e){}}),e.run().catch(e=>{log(`[Gateway] Fatal: ${e.message}`),process.exit(1)})}
|
|
2
|
+
const{Server:Server}=require("@modelcontextprotocol/sdk/server/index.js"),{StdioServerTransport:StdioServerTransport}=require("@modelcontextprotocol/sdk/server/stdio.js"),{CallToolRequestSchema:CallToolRequestSchema,ListToolsRequestSchema:ListToolsRequestSchema}=require("@modelcontextprotocol/sdk/types.js"),{getPromptFromPortal:getPromptFromPortal,listAvailableMcps:listAvailableMcps,downloadMcpPackage:downloadMcpPackage,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy}=require("./portal-api-client.cjs"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t,setLanguage:setLanguage}=requireFromPackage(require,"../../i18n");requireFromPackage(require,"../../lib/connectResilience").applyConnectResilience(require("node:net"));{const{SYS_PROMPT_ENV:e,isOwnSysPromptPath:t}=requireFromPackage(require,"../../lib/sysPromptHandoff"),r=process.env[e];if(t(r,require("node:os").tmpdir()))try{require("node:fs").unlinkSync(r)}catch{}}const{describeTokenForDebug:describeTokenForDebug}=requireFromPackage(require,"../../lib/debugRedaction"),{collectUpdateNotices:collectUpdateNotices,buildUpdateNoticeText:buildUpdateNoticeText}=require("./mcp-version-advisor.cjs"),{installPackageFiles:installPackageFiles,formatInstallFailure:formatInstallFailure,runProductionNpmInstall:runProductionNpmInstall}=require("./install-package.cjs"),spawn=require("cross-spawn"),{StringDecoder:StringDecoder}=require("string_decoder"),path=require("path"),fs=require("fs"),os=require("os"),dotenvLocalPath=path.join(__dirname,"../../.env.local"),dotenvPath=path.join(__dirname,"../../.env");fs.existsSync(dotenvLocalPath)?(require("dotenv").config({path:dotenvLocalPath}),logger.debug("[MCP Server] Loaded .env.local")):fs.existsSync(dotenvPath)&&(require("dotenv").config({path:dotenvPath}),logger.debug("[MCP Server] Loaded .env"));const KEYWORD=process.env.MUSUHI_KEYWORD||"BL-要件定義",{TokenManager:TokenManager}=requireFromPackage(require,"../../auth/TokenManager");function resolveToken(){return TokenManager.loadTokenStringSync()}const PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnv=PORTAL_URL_ENV.includes("localhost"),PORTAL_URL=PORTAL_URL_ENV||(isDevEnv?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LANGUAGE=process.env.MUSUHI_LANGUAGE||"ja",CATALOG_CACHE_FILE=path.join(os.homedir(),".bluelamp-vscode","gateway-catalog.json"),CATALOG_CACHE_VERSION=1,CHILD_READY_TIMEOUT_MS=3e4,CHILD_NOT_READY_TEXT="起動中です。数秒後に再試行してください";setLanguage(LANGUAGE);const LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(e){const t=`[${(new Date).toISOString()}] ${e}\n`;fs.appendFileSync(LOG_FILE,t),logger.info(`[MCP Server] ${e}`)}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),log(`MCP Server started - Log file: ${LOG_FILE}`),log("Environment variables:"),log(` KEYWORD: ${KEYWORD}`),log(` TOKEN (起動時スナップショット): ${describeTokenForDebug(resolveToken())}`),log(` PORTAL_URL: ${PORTAL_URL}`),log(` LANGUAGE: ${LANGUAGE}`);const LEGACY_REGISTRY=path.join(os.homedir(),".bluelamp","registry.json");if(fs.existsSync(LEGACY_REGISTRY))try{fs.unlinkSync(LEGACY_REGISTRY),log(`[startup] Legacy registry.json removed: ${LEGACY_REGISTRY}`)}catch(e){log(`[startup] Failed to remove legacy registry.json: ${e.message}`)}class ChildMcpProcess{constructor(e,t,r,o){this.serviceId=e,this.command=t,this.args=(r||[]).map(e=>"string"==typeof e?e.replace(/^~\//,os.homedir()+"/"):e),this.env={...process.env,...o},this.process=null,this.tools=[],this.requestId=0,this.pending=new Map,this.buffer="",this.stdoutDecoder=new StringDecoder("utf8"),this.stderrDecoder=new StringDecoder("utf8"),this.stderrTail="",this._decodersEnded=!1}async start(){return new Promise((e,t)=>{let r=!1;const o=(e,t)=>{r||(r=!0,clearTimeout(s),e(t))},s=setTimeout(()=>{o(t,new Error(`Timeout: "${this.serviceId}" failed to start within 90s`)),this.stop()},9e4);try{this.process=spawn(this.command,this.args,{env:this.env,stdio:["pipe","pipe","pipe"]}),this.process.on("error",e=>{o(t,new Error(`Spawn error for "${this.serviceId}": ${e.message}`))}),this.process.stdin.on("error",e=>{log(`[ChildMcp:${this.serviceId}:stdin] ${e.message}`)}),this.process.on("close",()=>{this._flushDecoders("close")}),this.process.on("exit",(e,r)=>{log(`[ChildMcp] "${this.serviceId}" exited (code=${e}, signal=${r})`);const s=this.stderrTail.trim();o(t,new Error(`"${this.serviceId}" exited before ready (code=${e}, signal=${r})${s?` | stderr: ${s}`:""}`)),this.pending.forEach(e=>e.reject(new Error("Process exited"))),this.pending.clear()}),this.process.on("close",()=>{if(this._decodersEnded)return;this._decodersEnded=!0;const e=this.stdoutDecoder.end(),t=this.stderrDecoder.end();e&&(this.buffer+=e,log(`[ChildMcp] "${this.serviceId}" stdout に半端なバイトが ${Buffer.byteLength(e)} バイト残っていた(子が多バイト文字の途中で終了)⇒ buffer へ流した。⛔ これは「壊れた文字」であって「失われた文字」ではない`)),t&&log(`[ChildMcp] "${this.serviceId}" stderr に半端なバイトが ${Buffer.byteLength(t)} バイト残っていた ⇒ ${JSON.stringify(t)}`)}),this.process.stderr.on("data",e=>{const t=this.stderrDecoder.write(e);this.stderrTail=sliceTailPreservingSurrogates(this.stderrTail+t,500),log(`[ChildMcp:${this.serviceId}:stderr] ${t.trim()}`)}),this.process.stdout.on("data",e=>{this.buffer+=this.stdoutDecoder.write(e),this._processBuffer()}),this._sendRequest("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"bluelamp-gateway",version:"1.0.0"}}).then(e=>(log(`[ChildMcp] "${this.serviceId}" initialized: ${JSON.stringify(e.serverInfo||{})}`),this._sendNotification("notifications/initialized",{}),this._sendRequest("tools/list",{}))).then(t=>{const r=t.tools||[];this.tools=r.map(e=>({...e,name:`${this.serviceId}__${e.name}`,_originalName:e.name})),log(`[ChildMcp] "${this.serviceId}" ready: ${this.tools.length} tools`),o(e,this.tools)}).catch(e=>{o(t,e)})}catch(e){o(t,e)}})}async callTool(e,t){return await this._sendRequest("tools/call",{name:e,arguments:t})}_flushDecoders(e){for(const[t,r]of[["stdout",this.stdoutDecoder],["stderr",this.stderrDecoder]]){let o="";try{o=r.end()}catch(e){log(`[ChildMcp:${this.serviceId}:${t}] decoder.end() が落ちた: ${e.message}`);continue}o&&log(`[ChildMcp:${this.serviceId}:${t}] 🚨 未完の多バイト列を ${e} 時に検出: ${JSON.stringify(o)}(${o.length} 文字・⛔ 黙って捨てていない)`)}}stop(){this.process&&(this.process.kill(),this.process=null),this.pending.forEach(e=>e.reject(new Error("Process stopped"))),this.pending.clear()}_sendRequest(e,t){return new Promise((r,o)=>{const s=++this.requestId,i=JSON.stringify({jsonrpc:"2.0",id:s,method:e,params:t})+"\n";this.pending.set(s,{resolve:r,reject:o});try{this.process.stdin.write(i)}catch(e){this.pending.delete(s),o(new Error(`Write error: ${e.message}`))}})}_sendNotification(e,t){const r=JSON.stringify({jsonrpc:"2.0",method:e,params:t})+"\n";try{this.process.stdin.write(r)}catch(e){log(`[ChildMcp] Notification write error: ${e.message}`)}}_processBuffer(){const e=this.buffer.split("\n");this.buffer=e.pop();for(const t of e){const e=t.trim();if(e)try{const t=JSON.parse(e);if(t.id&&this.pending.has(t.id)){const e=this.pending.get(t.id);this.pending.delete(t.id),t.error?e.reject(new Error(`JSON-RPC error: ${JSON.stringify(t.error)}`)):e.resolve(t.result)}}catch(t){log(`[ChildMcp:${this.serviceId}] Parse error: ${t.message} | line: ${e.substring(0,200)}`)}}}}class ChildMcpManager{constructor(){this.children=new Map,this.starting=new Map}async activateExternal(e,t){const r=new ChildMcpProcess(e,t.command,t.args||[],t.env||{}),o=await r.start();return this.children.set(e,{type:"external",process:r,tools:o,service:t}),o}beginActivateExternal(e,t){const r=this.children.get(e);if(r)return Promise.resolve({ok:!0,tools:r.tools||[]});const o=this.starting.get(e);if(o)return o;const s=this.activateExternal(e,t).then(e=>({ok:!0,tools:e}),e=>({ok:!1,error:e})).then(t=>(this.starting.delete(e),t));return this.starting.set(e,s),s}isStarting(e){return this.starting.has(e)}async waitForReady(e,t){if(this.children.has(e))return{ready:!0};const r=this.starting.get(e);if(!r)return{ready:!1,reason:"unknown"};let o=null;const s=new Promise(e=>{o=setTimeout(()=>e({timedOut:!0}),t),"function"==typeof o.unref&&o.unref()});try{const e=await Promise.race([r,s]);return e.timedOut?{ready:!1,reason:"timeout"}:e.ok?{ready:!0}:{ready:!1,reason:"failed",error:e.error}}finally{o&&clearTimeout(o)}}deactivate(e){const t=this.children.get(e);t&&("external"===t.type&&t.process&&t.process.stop(),this.children.delete(e))}isActive(e){return this.children.has(e)}getActiveTools(){const e=[];for(const t of this.children.values())e.push(...t.tools||[]);return e}async callTool(e,t){for(const[r,o]of this.children)if("external"===o.type){const r=o.tools.find(t=>t.name===e);if(r)return{found:!0,result:await o.process.callTool(r._originalName,t)}}return{found:!1}}getActiveServiceIds(){return[...this.children.keys()]}}class BlueLampServer{constructor(){this.childManager=new ChildMcpManager,this.updateNoticeText=null,this._catalogTools=new Map,this._cloudProxyTools=new Map,this._catalogServices=new Map,this._needChildForSchema=new Set,this._catalogFromCache=!1,this._configRefresh=null,this.server=new Server({name:"bluelamp",version:"2.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.setupHandlers()}checkAuth(){return resolveToken()?{ok:!0}:{ok:!1,error:"BlueLamp にログインしてください。ターミナルで `bluelamp1` を一度実行すると ~/.musuhi/portal-token.enc が作成されます。"}}buildToolList(){return buildGatewayToolList([{name:"load_knowledge",description:t("mcp.toolDescriptionLoadKnowledge"),annotations:{readOnlyHint:!0,idempotentHint:!0},inputSchema:{type:"object",properties:{keyword:{type:"string",description:t("mcp.keywordDescription")}},required:["keyword"]}},{name:"search_mcp",description:'キーワードでインストール済みMCPサービスを検索します。使いたい機能を自然言語で説明してください。(例: "ブラウザ操作", "メール送信", "画像生成")',inputSchema:{type:"object",properties:{query:{type:"string",description:"検索キーワード(自然言語OK)"}},required:["query"]}},{name:"activate_service",description:"deactivate_service で一覧から外した MCP を、道具一覧に戻します(deactivate を戻すだけ)。権限のある MCP は最初から一覧に出ているので、通常このツールを撃つ必要はありません。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"},always_on:{type:"boolean",description:"後方互換のため受け付けますが、一覧の可否には使いません(無視されます)"}},required:["service_id"]}},{name:"deactivate_service",description:"指定した MCP を道具一覧から外します(deactivated=true)。次の接続でも一覧に出なくなります。権限は変わらないので activate_service でいつでも戻せます。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"restart_service",description:"MCPサービスのプロセスを再起動します。コード更新や設定変更の反映に使用します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"サービスID"}},required:["service_id"]}},{name:"unregister_service",description:"MCPサービスをレジストリから完全に削除します。プロセスも停止します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"削除するサービスID"}},required:["service_id"]}},{name:"register_service",description:"新しいMCPサービスをゲートウェイに登録します。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:'サービスID(英数字、例: "microcms")'},name:{type:"string",description:"サービス表示名"},description:{type:"string",description:"サービスの説明"},tags:{type:"array",items:{type:"string"},description:"検索用タグ"},command:{type:"string",description:'実行コマンド("npx" or "node")'},args:{type:"array",items:{type:"string"},description:"コマンド引数"},env:{type:"object",description:"環境変数"}},required:["service_id","name","description","tags"]}},{name:"browse_mcp_store",description:"MCPストア(Portal)からインストール可能なMCP一覧を取得します。",inputSchema:{type:"object",properties:{},required:[]}},{name:"install_from_store",description:"MCP ストアから MCP を取得してこの機体に展開し、env と導入版をサーバに記録します。browse_mcp_store で見つけた mcp_id を指定してください。道具一覧に出るかどうかは権限で決まるので、権限の無い MCP をこれで一覧に出すことはできません(自分で作った MCP・共有された MCP は権限に含まれるため、何もしなくても一覧に出ます)。",inputSchema:{type:"object",properties:{mcp_id:{type:"string",description:"インストールするMCPのID"}},required:["mcp_id"]}},{name:"save_mcp_env",description:"MCPの環境変数(APIキー等)をサーバーに安全に保存します。ローカルには保存されません。次回起動時にサーバーから自動取得されます。解約時はアクセスできなくなります。",inputSchema:{type:"object",properties:{service_id:{type:"string",description:"MCP サービスID(例: heygen, gemini-image)"},env:{type:"object",description:'環境変数のキーと値(例: {"HEYGEN_API_KEY": "sk_xxx"})',additionalProperties:{type:"string"}}},required:["service_id","env"]}}],this._catalogTools,this.childManager.getActiveTools())}applyCatalog(e,t={}){const{catalog:r,cloudProxy:o,warnings:s,needChildForSchema:i}=buildCatalogRegistry(e);this._catalogTools=r,this._cloudProxyTools=o,this._catalogServices=new Map,this._needChildForSchema=new Set(i),this._catalogFromCache=!0===t.fromCache;for(const t of Array.isArray(e)?e:[])t&&t.mcpId&&this._catalogServices.set(t.mcpId,t);let n=0;for(const e of s)"no-input-schema"===e.reason?(n+=e.count,log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}): ${e.count}/${e.total} tools have no inputSchema — 一覧には出す(空 schema)。子を起こして実 schema で上書きする`)):log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return n>0&&log(`[catalog] inputSchema 欠損の合計: ${n} 本 / ${this._needChildForSchema.size} service(一覧には出す。子を起こして実 schema で上書きする)`),log(`[catalog] ${r.size} tools registered from ${this._catalogServices.size} services (cloud_proxy: ${o.size})`),r}registerServiceInCatalog(e){const{catalog:t,cloudProxy:r,warnings:o}=buildCatalogRegistry([e]);for(const[e,r]of t)this._catalogTools.set(e,r);for(const[e,t]of r)this._cloudProxyTools.set(e,t);for(const e of o)log(`[catalog] "${e.mcpId}" (${e.hostingType||"unknown"}) skipped: ${e.reason}`);return e&&e.mcpId&&this._catalogServices.set(e.mcpId,e),t.size}unregisterServiceFromCatalog(e){const t=`${e}__`;let r=0;for(const e of[...this._catalogTools.keys()])e.startsWith(t)&&(this._catalogTools.delete(e),this._cloudProxyTools.delete(e),r++);return r}buildChildService(e,t){const r=path.join(os.homedir(),".claude","mcp-servers",e),o=t.entryPoint||"dist/index.js";return{name:t.name,description:t.description,type:"external",command:t.command||"node",args:t.args&&t.args.length?t.args:[path.join(r,o)],env:t.env||{}}}setupHandlers(){this.server.setRequestHandler(ListToolsRequestSchema,async()=>{const e=this.buildToolList(),t=this.childManager.getActiveServiceIds();return log(`[ListTools] ${e.length} tools (active: ${t.join(", ")||"none"})`),{tools:e}}),this.server.setRequestHandler(CallToolRequestSchema,async e=>{const{name:r,arguments:o}=e.params;log(`[CallTool] ${r}`);const s=resolveToken();if("load_knowledge"===r){const{keyword:e}=o;log(`[load_knowledge] keyword: ${e}`);try{const r=await getPromptFromPortal(e,s,LANGUAGE);if(!r||!r.success){const o=r?.error||t("mcp.knowledgeNotFound",{keyword:e});return{content:[{type:"text",text:`❌ ${t("mcp.portalApiFailed",{error:o})}`}],isError:!0}}const o="ja"!==LANGUAGE?"\n\n[Note] The user's preferred language is English.":"",i=this.updateNoticeText?`\n\n${this.updateNoticeText}`:"";return{content:[{type:"text",text:`✅ ${t("mcp.knowledgeLoadSuccess")}\n\n${r.knowledge}${o}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${t("mcp.knowledgeApplied")}${i}`}]}}catch(e){return log(`[load_knowledge] Exception: ${e.message}`),{content:[{type:"text",text:`❌ ${t("mcp.knowledgeLoadError",{error:e.message})}`}],isError:!0}}}if("search_mcp"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};if(!s)return{content:[{type:"text",text:"❌ 未認証です。MCPカタログの検索にはログインが必要です。"}],isError:!0};let t=[];try{const e=await searchMcpServices(o.query,s);if(!e.success)return{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.error||"Unknown"}`}],isError:!0};t=(e.data||[]).map(e=>({id:e.mcp_id,service:{name:e.name,description:e.description,tags:[],alwaysOn:e.alwaysOn||!1},isInstalled:e.isInstalled||!1}))}catch(e){return log(`[search_mcp] Server search failed: ${e.message}`),{content:[{type:"text",text:`❌ MCPカタログ検索に失敗: ${e.message}`}],isError:!0}}if(0===t.length)return{content:[{type:"text",text:`🔍 「${o.query}」に一致するサービスはありません。\n\n別のキーワードで試すか、browse_mcp_storeでカタログを確認してください。`}]};let r=`🔍 「${o.query}」の検索結果(${t.length}件)\n\n`;for(const e of t.slice(0,10)){const t=e.id,o=e.service,s=this.childManager.isActive(t),i=!0===o.deactivated,n=e.isInstalled?"":" [未インストール]",a=i?" [一覧から外し中]":"";r+=`${i?"🚫":s?"🟢":"⚪"} **${o.name}**${a}${n} (id: \`${t}\`)\n`,r+=` ${o.description}\n\n`}return r+="一覧に戻す: activate_service | ストアから導入: install_from_store",{content:[{type:"text",text:r}]}}if("activate_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t}=o,r=await getGatewayConfig(s);if(!r.success)return{content:[{type:"text",text:`❌ サーバー設定の取得に失敗: ${r.error||"Unknown"}`}],isError:!0};const i=(r.data?.services||[]).find(e=>e.mcpId===t);if(!i)return{content:[{type:"text",text:`❌ サービス "${t}" はあなたの権限の中にありません(権限のある MCP は何もしなくても一覧に出ます)。`}],isError:!0};const n=await setServiceDeactivated(t,!1,s);if(!n.success)return{content:[{type:"text",text:`❌ "${t}" を一覧に戻せませんでした: ${n.error||"Unknown"}`}],isError:!0};log(`[activate] "${t}" deactivated=false (一覧に戻した)`);const a=this.registerServiceInCatalog({...i,deactivated:!1});if(0===a)return{content:[{type:"text",text:`❌ "${t}" には目録にツール定義がありません(tools が空)。`}],isError:!0};if("cloud_proxy"===i.hostingType){log(`[activate] "${t}" (cloud_proxy) → ${a} proxy tools registered`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${i.name}" を一覧に戻しました(cloud_proxy ツール${a}個)`}]}}if(this.childManager.isActive(t)){try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${t}" を一覧に戻しました(子プロセスは既に起動済み)。`}]}}log(`[activate] "${t}" (${i.hostingType}) → ${a} catalog tools restored. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[activate] notification error: ${e.message}`)}return{content:[{type:"text",text:`✅ "${i.name}" を一覧に戻しました(ツール${a}個)。最初に呼ばれた時に自動で起動します。`}]}}if("deactivate_service"===r){const{service_id:e}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const t=await setServiceDeactivated(e,!0,s);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" を一覧から外せませんでした: ${t.error||"Unknown"}`}],isError:!0};log(`[deactivate] "${e}" deactivated=true (一覧から外した)`);const r=this.unregisterServiceFromCatalog(e);r>0&&log(`[deactivate] "${e}" → ${r} catalog tools removed`),this._catalogServices.delete(e);let i=!1;if(this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[deactivate] "${e}" child process stopped`),i=!0),r>0||i)try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[deactivate] notification error: ${e.message}`)}let n=`✅ "${e}" を道具一覧から外しました(deactivated=true)。`;return r>0&&(n+=` ツール${r}個を一覧から削除しました。`),i&&(n+=" 子プロセスを停止しました。"),n+=" 次の接続でも一覧に出ません。権限は変わっていないので activate_service で戻せます。",{content:[{type:"text",text:n}]}}if("restart_service"===r){const{service_id:e}=o,t=this.childManager.children.get(e),r=t?.service;if(!r)return isCloudProxyService(this._cloudProxyTools,e)?{content:[{type:"text",text:`ℹ️ "${e}" は cloud_proxy 方式のため、ローカルの再起動対象ではありません(子プロセスを持ちません)。\nサーバ側のツール更新は deploy で全利用者に反映されるので、再起動は不要です。\nツール一覧を取り直したいだけなら deactivate_service → activate_service を実行してください。`}]}:{content:[{type:"text",text:`❌ "${e}" の子プロセスは現在動いていません。道具を呼べば自動で起動します(restart は起動済みの子にだけ効きます)。`}],isError:!0};let i,n=r;if(s)try{const t=await getGatewayConfig(s);if(t.success){const o=(t.data?.services||[]).find(t=>t.mcpId===e);o&&o.env&&(n={...r,env:o.env},log(`[restart] "${e}" env refreshed from server (keys=${Object.keys(o.env).join(",")||"none"})`))}}catch(e){log(`[restart] env refresh failed (using cached): ${e.message}`)}this.childManager.deactivate(e),log(`[restart] "${e}" stopped`);try{i=await this.childManager.activateExternal(e,n)}catch(t){return log(`[restart] Failed "${e}": ${t.message}`),{content:[{type:"text",text:`❌ "${e}" の再起動に失敗しました: ${t.message}`}],isError:!0}}log(`[restart] "${e}" → ${i.length} tools. Sending list_changed.`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[restart] notification error: ${e.message}`)}const a=i.map(e=>e.name).join(", ");return{content:[{type:"text",text:`🔄 "${n.name}" を再起動しました(ツール${i.length}個)\n\nツール: ${a||"(なし)"}`}]}}if("unregister_service"===r){const{service_id:e}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};this.childManager.isActive(e)&&(this.childManager.deactivate(e),log(`[unregister] "${e}" stopped`));const t=await uninstallMcpService(e,s);if(!t.success)return{content:[{type:"text",text:`❌ "${e}" のサーバー削除に失敗: ${t.error||"Unknown"}`}],isError:!0};log(`[unregister] "${e}" uninstalled from server`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[unregister] notification error: ${e.message}`)}return{content:[{type:"text",text:`🗑️ "${e}" を削除しました。プロセスも停止済みです。`}]}}if("register_service"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=o;if(!s)return{content:[{type:"text",text:"🔒 未認証です。ログインが必要です。"}],isError:!0};const i=r||{},n=await installMcpService(t,!1,s,i);return n.success?(log(`[register] "${t}" installed on server`),{content:[{type:"text",text:`✅ サービス "${t}" をサーバーに登録しました。\n\n次のステップ:\n- save_mcp_env で APIキー等を設定\n- 次の接続で道具一覧に出ます(activate は不要です)`}]}):{content:[{type:"text",text:`❌ "${t}" の登録に失敗しました: ${n.error||"Unknown"}\n\n※ MCP はサーバーカタログに登録されている必要があります。browse_mcp_store で確認してください。`}],isError:!0}}if("save_mcp_env"===r){const e=this.checkAuth();if(!e.ok)return{content:[{type:"text",text:`🔒 ${e.error}`}],isError:!0};const{service_id:t,env:r}=o;if(!t||!r||0===Object.keys(r).length)return{content:[{type:"text",text:"❌ service_id と env(キー/値のペア)が必要です"}],isError:!0};log(`[save_mcp_env] ${t}: ${Object.keys(r).join(", ")}`);try{const e=await updateMcpEnv(t,r,s);if(!e.success)return{content:[{type:"text",text:`❌ サーバー保存失敗: ${e.error}`}],isError:!0};const o=Object.keys(r).join(", ");return e.warning?{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n${e.warning}`}]}:{content:[{type:"text",text:`🔐 環境変数をサーバーに保存しました\n\nサービス: ${t}\n保存キー: ${o}\n\n※ ローカルには保存されません。次回起動時にサーバーから自動取得されます。`}]}}catch(e){return log(`[save_mcp_env] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("browse_mcp_store"===r){log("[browse_mcp_store]");try{const e=await listAvailableMcps({},s,LANGUAGE);if(!e||!e.success)return{content:[{type:"text",text:`❌ MCPカタログの取得に失敗: ${e?.error||"Unknown"}`}],isError:!0};const t=e.data||[];if(0===t.length)return{content:[{type:"text",text:"📦 インストール可能なMCPはありません。"}]};let r=`📦 MCPストア一覧 (${t.length}件)\n\n`;return t.forEach(e=>{const t=e.isOfficial?" [公式]":"";r+=`**${e.name}**${t}\n`,r+=` ID: ${e.mcpId}\n`,r+=` ${e.description}\n`,r+=` ツール数: ${e.toolCount}\n\n`}),r+='\nインストールするには: install_from_store(mcp_id: "MCP_ID")',{content:[{type:"text",text:r}]}}catch(e){return log(`[browse_mcp_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if("install_from_store"===r){const{mcp_id:e}=o;log(`[install_from_store] mcp_id=${e}`);try{const t=await downloadMcpPackage(e,s);if(!t||!t.success)return{content:[{type:"text",text:`❌ MCPダウンロードに失敗: ${t?.error||"Unknown"}`}],isError:!0};const r=t.data,o=r.packageSource||{},i=r.files||[];let n=o.hostingType||o.hosting&&o.hosting.type||null;if(s){let t=!1;try{const r=await getGatewayConfig(s);if(r.success){const o=(r.data?.services||[]).find(t=>t.mcpId===e);o&&(t=!0===o.alwaysOn,n||(n=o.hostingType))}}catch(e){}const r=await installMcpService(e,t,s);if(!r.success)return{content:[{type:"text",text:`❌ サーバー登録失敗: ${r.error||"Unknown"}`}],isError:!0};log(`[install_from_store] Server sync OK: ${e} (alwaysOn=${t})`)}if("cloud_proxy"===n){log(`[install_from_store] "${e}" is cloud_proxy, skipping local extract`);let t=`✅ MCP「${o.name||e}」をインストールしました(cloud_proxy)\n\n`;return t+="☁ クラウド実行のためローカル展開は不要です\n",o.installNotes&&(t+=`\n📝 ${o.installNotes}\n`),t+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:t}]}}if(0===i.length)return{content:[{type:"text",text:"❌ MCPパッケージにファイルが含まれていません。"}],isError:!0};const a=path.join(os.homedir(),".claude","mcp-servers",e),c=installPackageFiles({installDir:a,files:i,needsNpmInstall:!!o.needsNpmInstall,runNpmInstall:e=>runProductionNpmInstall(e)});if(!c.ok)return log(`[install_from_store] ${e} failed at ${c.stage}: ${c.error.message}`),{content:[{type:"text",text:formatInstallFailure(o.name||e,c)}],isError:!0};log(`[install_from_store] ${i.length} files written to ${a}`);let l=`✅ MCP「${o.name||e}」をインストールしました\n\n`;l+=`📁 インストール先: ${a}\n`,l+=`📄 ファイル数: ${i.length}\n`,o.installNotes&&(l+=`\n📝 ${o.installNotes}\n`);const d=(o.requiredEnvVars||[]).filter(e=>!process.env[e]);return d.length>0&&(l+="\n⚠️ 以下の環境変数を save_mcp_env で設定してください:\n",d.forEach(e=>{l+=` - ${e}\n`})),l+="\n権限に含まれていれば、次の接続で道具一覧に出ます(activate_service を撃つ必要はありません)。",{content:[{type:"text",text:l}]}}catch(e){return log(`[install_from_store] error: ${e.message}`),{content:[{type:"text",text:`❌ ${e.message}`}],isError:!0}}}if(this._cloudProxyTools&&this._cloudProxyTools.has(r)){const e=this._cloudProxyTools.get(r);log(`[callTool] Cloud proxy: ${r} → ${e.mcpId}/${e.toolName}`);try{const t=await callMcpToolProxy(e.mcpId,e.toolName,o,s);return t.success&&t.data?t.data.content?t.data:{content:[{type:"text",text:JSON.stringify(t.data,null,2)}]}:{content:[{type:"text",text:`❌ プロキシエラー: ${t.error||"Unknown"}`}],isError:!0}}catch(e){return log(`[callTool] Cloud proxy error "${r}": ${e.message}`),{content:[{type:"text",text:`❌ クラウドプロキシエラー: ${e.message}`}],isError:!0}}}try{const{found:e,result:t}=await this.childManager.callTool(r,o);if(e)return t&&t.content?t:{content:[{type:"text",text:String(t)}]}}catch(e){return log(`[callTool] Error proxying "${r}": ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}const i=this._catalogTools.get(r);if(i&&"cloud_proxy"!==i.hostingType){const e=i.mcpId;this._catalogFromCache&&this._configRefresh&&(log(`[callTool] "${e}" は目録キャッシュ由来。起動仕様の到着を待つ。`),await this._configRefresh),this.startChildOnDemand(e);const t=await this.childManager.waitForReady(e,3e4);if(!t.ready){if("failed"===t.reason){const r=t.error?.message||String(t.error||"Unknown");return log(`[callTool] "${e}" failed to start: ${r}`),{content:[{type:"text",text:`❌ "${e}" の起動に失敗しました: ${r}`}],isError:!0}}return"timeout"===t.reason?(log(`[callTool] "${e}" not ready within 30000ms for "${r}".`),{content:[{type:"text",text:CHILD_NOT_READY_TEXT}],isError:!0}):(log(`[callTool] "${e}" could not be started for "${r}" (reason=${t.reason}).`),{content:[{type:"text",text:`❌ "${e}" を起動できませんでした(設定が見つかりません)。`}],isError:!0})}try{const{found:t,result:s}=await this.childManager.callTool(r,o);if(t)return s&&s.content?s:{content:[{type:"text",text:String(s)}]};log(`[callTool] "${r}" is in catalog but child "${e}" does not have it.`)}catch(e){return log(`[callTool] Error proxying "${r}" after ready: ${e.message}`),{content:[{type:"text",text:`❌ ツール実行エラー: ${e.message}`}],isError:!0}}}return{content:[{type:"text",text:`❌ Unknown tool: ${r}`}],isError:!0}})}startChildrenForMissingSchema(){let e=0;for(const t of this._needChildForSchema){const r=this._catalogServices.get(t);if(r)if("cloud_proxy"!==r.hostingType){if(!this.childManager.isActive(t)&&!this.childManager.isStarting(t)){log(`[schemaChild] "${t}" (${r.hostingType}) を起こして実 schema を取りに行く`);try{this.startChildOnDemand(t),e++}catch(e){log(`[schemaChild] "${t}" の起動に失敗: ${e.message}(⛔ 席は落とさない)`)}}}else log(`[schemaChild] "${t}" は cloud_proxy のため子を起こせない。schema 欠損の道具は一覧に出ない。`)}return log(`[schemaChild] ${e} service(=目録に schema が無い道具を持つ service の数)`),e}async loadCatalog(e){if(!e)return log("[catalog] No token, starting with no catalog tools."),[];let t=[];try{const r=await getGatewayConfig(e);if(!r.success)return log(`[catalog] Server config failed: ${r.error||"Unknown"}. Starting with no services.`),[];t=r.data?.services||[],log(`[catalog] Server config: ${t.length} services`),this.applyCatalog(t),writeCatalogCache(CATALOG_CACHE_FILE,t,catalogCacheFingerprint(e),log)}catch(e){return log(`[catalog] Server config error: ${e.message}. Starting with no services.`),[]}try{const e=collectUpdateNotices(t);this.updateNoticeText=buildUpdateNoticeText(e);for(const t of e)"not-applicable"!==t.status&&log(`[versionCheck] ${t.mcpId}: ${t.status} (${t.reason}) catalog=${t.catalogVersion} local=${t.localVersion}`)}catch(e){log(`[versionCheck] skipped: ${e.message}`)}return t}startChildOnDemand(e){const t=this._catalogServices.get(e);if(!t||!0===t.deactivated)return null;if("cloud_proxy"===t.hostingType)return null;if(this.childManager.isActive(e))return null;const r=this.childManager.isStarting(e),o=this.childManager.beginActivateExternal(e,this.buildChildService(e,t));return r||log(`[startChild] "${e}" (${t.hostingType}) starting on demand...`),o.then(t=>this.reconcileChildWithCatalog(e,t)).catch(t=>log(`[startChild] "${e}" post-start error: ${t.message}`)),o}async reconcileChildWithCatalog(e,t){const r=decideCatalogReconciliation(this._catalogTools,e,t);if(!r.ready){const o=t&&t.error?`: ${t.error.message||t.error}`:"";return log(`[versionDrift] "${e}" not ready (${r.reason})${o} → 剪定も通知もしない`),r}if(!r.notify)return log(`[versionDrift] "${e}" matches catalog (${t.tools.length} tools) → no list_changed`),r;for(const e of r.prune)this._catalogTools.delete(e),this._cloudProxyTools.delete(e);const o=r.diff;log(`[versionDrift] "${e}" differs from catalog: +${o.added.length} -${o.removed.length} ~${o.changed.length} → list_changed`);try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[versionDrift] notification error: ${e.message}`)}return r}toolListSignature(){return stableJson(this.buildToolList().map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema})))}async refreshCatalogInBackground(e){const t=this.toolListSignature();try{await this.loadCatalog(e)}catch(e){return log(`[catalogRefresh] failed, keeping cached catalog: ${e.message}`),{notified:!1,reason:"failed"}}if(t===this.toolListSignature())return log("[catalogRefresh] server catalog matches cache → no list_changed"),{notified:!1,reason:"same"};log("[catalogRefresh] server catalog differs from cache → list_changed");try{await this.server.notification({method:"notifications/tools/list_changed"})}catch(e){log(`[catalogRefresh] notification error: ${e.message}`)}return{notified:!0,reason:"diff"}}async run(){const e=resolveToken(),t=catalogCacheFingerprint(e),r=readCatalogCache(CATALOG_CACHE_FILE,t,log);r?(this.applyCatalog(r,{fromCache:!0}),log(`[catalogCache] ${this._catalogTools.size} tools from cache (HTTP は待たない)`)):(log("[catalogCache] no usable cache, waiting for server config"),await this.loadCatalog(e));const o=new StdioServerTransport;await this.server.connect(o),log(`[Gateway] BlueLamp Gateway v2.0.0 started. (${this._catalogTools.size} catalog tools available at connect)`),r?(this._configRefresh=this.refreshCatalogInBackground(e).catch(e=>(log(`[catalogRefresh] unexpected: ${e.message}`),{notified:!1,reason:"failed"})),this._configRefresh.then(()=>this.startChildrenForMissingSchema()).catch(e=>log(`[schemaChild] deferred start failed: ${e.message}`))):this.startChildrenForMissingSchema();const s=resolveToken();if(KEYWORD&&s){log(`[Gateway] Auto-loading knowledge for keyword: ${KEYWORD}`);const e=await getPromptFromPortal(KEYWORD,s,LANGUAGE);e&&e.success?log(`[Gateway] Auto-load successful: ${e.title}`):log(`[Gateway] Auto-load failed for keyword: ${KEYWORD}`)}}}function buildCatalogRegistry(e){const t=new Map,r=new Map,o=[],s=[];for(const i of Array.isArray(e)?e:[]){if(!i||"string"!=typeof i.mcpId||!i.mcpId)continue;const e=i.hostingType||null;if(!0===i.deactivated)continue;const n=Array.isArray(i.tools)?i.tools:[];if(0===n.length){o.push({mcpId:i.mcpId,hostingType:e,reason:"no-tools"});continue}const a=[];for(const s of n){if(!s||"string"!=typeof s.name||!s.name){o.push({mcpId:i.mcpId,hostingType:e,reason:"unnamed-tool"});continue}s.inputSchema&&"object"==typeof s.inputSchema||a.push(s.name);const n=`${i.mcpId}__${s.name}`,c={name:n,mcpId:i.mcpId,toolName:s.name,hostingType:e,description:s.description,inputSchema:s.inputSchema};t.set(n,c),"cloud_proxy"===e&&r.set(n,{mcpId:i.mcpId,toolName:s.name,description:s.description,inputSchema:s.inputSchema})}a.length>0&&(o.push({mcpId:i.mcpId,hostingType:e,reason:"no-input-schema",count:a.length,total:n.length,tools:a}),s.push(i.mcpId))}return{catalog:t,cloudProxy:r,warnings:o,needChildForSchema:s}}function catalogEntryToTool(e){return{name:e.name,description:e.description||`[${e.mcpId}] ${e.toolName}`,inputSchema:e.inputSchema||{type:"object",properties:{}}}}function buildGatewayToolList(e,t,r){const o=new Map;for(const t of Array.isArray(e)?e:[])t&&t.name&&o.set(t.name,t);if(t&&"function"==typeof t.values)for(const e of t.values())o.has(e.name)||o.set(e.name,catalogEntryToTool(e));for(const e of Array.isArray(r)?r:[])e&&e.name&&o.set(e.name,e);return[...o.values()]}function sanitizeServicesForCache(e){return(Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.mcpId&&e.mcpId).map(e=>({mcpId:e.mcpId,name:e.name,description:e.description,hostingType:e.hostingType||null,deactivated:!0===e.deactivated,tools:(Array.isArray(e.tools)?e.tools:[]).filter(e=>e&&"string"==typeof e.name&&e.name).map(e=>({name:e.name,description:e.description,inputSchema:e.inputSchema}))}))}function catalogCacheFingerprint(e){return e?require("crypto").createHash("sha256").update(String(e)).digest("hex").slice(0,16):null}function sliceTailPreservingSurrogates(e,t){const r=String(null==e?"":e);if(r.length<=t)return r;const o=r.slice(-t),s=o.charCodeAt(0);return s>=56320&&s<=57343?o.slice(1):o}function stableJson(e){return void 0===e?"null":null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(stableJson).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${stableJson(e[t])}`).join(",")}}`}function diffChildToolsAgainstCatalog(e,t,r){const o=`${t}__`,s=new Set;if(e&&"function"==typeof e.keys)for(const t of e.keys())"string"==typeof t&&t.startsWith(o)&&s.add(t);const i=[],n=[],a=new Set;for(const t of Array.isArray(r)?r:[]){if(!t||!t.name)continue;a.add(t.name);const r=e&&"function"==typeof e.get?e.get(t.name):void 0;r?stableJson(r.description)===stableJson(t.description)&&stableJson(r.inputSchema)===stableJson(t.inputSchema)||n.push(t.name):i.push(t.name)}const c=[...s].filter(e=>!a.has(e));return{added:i,removed:c,changed:n,hasDiff:i.length+c.length+n.length>0}}function decideCatalogReconciliation(e,t,r){if(!r||!0!==r.ok||!Array.isArray(r.tools))return{ready:!1,prune:[],notify:!1,reason:r&&r.reason||"not-ready",diff:null};const o=diffChildToolsAgainstCatalog(e,t,r.tools);return{ready:!0,prune:o.removed,notify:o.hasDiff,reason:"ready",diff:o}}function readCatalogCache(e,t,r){const o=r||(()=>{});if(!t)return null;let s,i;try{s=fs.readFileSync(e,"utf8")}catch(e){return"ENOENT"!==e.code&&o(`[catalogCache] read failed: ${e.message}`),null}try{i=JSON.parse(s)}catch(e){return o(`[catalogCache] broken JSON, ignoring: ${e.message}`),null}return i&&1===i.version?i.tokenFingerprint!==t?(o("[catalogCache] fingerprint mismatch (口座が違う), ignoring"),null):Array.isArray(i.services)?i.services:(o("[catalogCache] services が配列でない, ignoring"),null):(o(`[catalogCache] version mismatch (${i&&i.version}), ignoring`),null)}function writeCatalogCache(e,t,r,o){const s=o||(()=>{});if(!r)return!1;try{fs.mkdirSync(path.dirname(e),{recursive:!0});const o={version:1,tokenFingerprint:r,savedAt:(new Date).toISOString(),services:sanitizeServicesForCache(t)},s=`${e}.${process.pid}.tmp`;return fs.writeFileSync(s,JSON.stringify(o),{mode:384}),fs.renameSync(s,e),!0}catch(e){return s(`[catalogCache] write failed: ${e.message}`),!1}}function isCloudProxyService(e,t){if(!e||"function"!=typeof e.keys)return!1;const r=`${t}__`;for(const t of e.keys())if("string"==typeof t&&t.startsWith(r))return!0;return!1}if(module.exports={ChildMcpProcess:ChildMcpProcess,ChildMcpManager:ChildMcpManager,BlueLampServer:BlueLampServer,isCloudProxyService:isCloudProxyService,buildCatalogRegistry:buildCatalogRegistry,buildGatewayToolList:buildGatewayToolList,catalogEntryToTool:catalogEntryToTool,diffChildToolsAgainstCatalog:diffChildToolsAgainstCatalog,decideCatalogReconciliation:decideCatalogReconciliation,sanitizeServicesForCache:sanitizeServicesForCache,catalogCacheFingerprint:catalogCacheFingerprint,readCatalogCache:readCatalogCache,writeCatalogCache:writeCatalogCache,CATALOG_CACHE_FILE:CATALOG_CACHE_FILE,CATALOG_CACHE_VERSION:1,stableJson:stableJson,sliceTailPreservingSurrogates:sliceTailPreservingSurrogates,CHILD_READY_TIMEOUT_MS:3e4,CHILD_NOT_READY_TEXT:CHILD_NOT_READY_TEXT},require.main===module){const e=new BlueLampServer;let t=!1;const r=r=>{if(!t){t=!0;try{log(`[Gateway] Shutdown (${r}) - cleaning up children...`)}catch(e){}if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{try{log(`[Gateway] Stopping child: ${t}`)}catch(e){}r&&"external"===r.type&&r.process&&"function"==typeof r.process.stop&&r.process.stop()}catch(e){}setTimeout(()=>process.exit(0),200).unref()}};process.on("SIGTERM",()=>r("SIGTERM")),process.on("SIGINT",()=>r("SIGINT")),process.on("SIGHUP",()=>r("SIGHUP")),process.stdin.on("end",()=>r("stdin-end")),process.stdin.on("close",()=>r("stdin-close")),process.on("exit",()=>{if(e&&e.childManager&&e.childManager.children)for(const[t,r]of e.childManager.children)try{r&&r.process&&r.process.process&&"function"==typeof r.process.process.kill&&r.process.process.kill("SIGKILL")}catch(e){}}),e.run().catch(e=>{log(`[Gateway] Fatal: ${e.message}`),process.exit(1)})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const fs=require("fs"),path=require("path"),PRESERVE_ACROSS_SWAP=["node_modules"];function backupPathFor(e,t=new Date){return`${e}.bak-${t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")}`}function assertInsideInstallDir(e,t){const n=path.resolve(e),r=path.resolve(t);if(r!==n&&!r.startsWith(n+path.sep))throw new Error(`install: 展開先が installDir の外を指しています(${r})`);return r}function rmrf(e){fs.rmSync(e,{recursive:!0,force:!0})}function installPackageFiles(e){const{installDir:t,files:n,needsNpmInstall:r=!1,runNpmInstall:s,now:a}=e,o=fs.existsSync(t),c=o?backupPathFor(t,a):null;o&&fs.renameSync(t,c);const i=(e,n)=>{try{rmrf(t)}catch(e){}if(!c)return{ok:!1,rolledBack:!1,backupPath:null,stage:e,error:n};try{return fs.renameSync(c,t),{ok:!1,rolledBack:!0,backupPath:null,stage:e,error:n}}catch(t){return{ok:!1,rolledBack:!1,restoreFailed:!0,backupPath:c,stage:e,error:new Error(`${n.message} / さらに旧版の復元にも失敗: ${t.message}`)}}};let l=!1;try{fs.mkdirSync(t,{recursive:!0});for(const e of n){const n=assertInsideInstallDir(t,path.join(t,e.path));if(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,e.content,"utf-8"),"number"==typeof e.mode)try{fs.chmodSync(n,511&e.mode)}catch(e){}}if(c&&!r)for(const e of PRESERVE_ACROSS_SWAP){const n=path.join(c,e),r=path.join(t,e);fs.existsSync(n)&&!fs.existsSync(r)&&(fs.renameSync(n,r),l=!0)}}catch(e){return i("extract",e)}if(r&&fs.existsSync(path.join(t,"package.json")))try{if("function"!=typeof s)throw new Error("runNpmInstall が渡されていません");s(t)}catch(e){return i("npm-install",e)}if(c)try{rmrf(c)}catch(e){}return{ok:!0,installDir:t,fileCount:n.length,restoredNodeModules:l}}function formatInstallFailure(e,t){const n="npm-install"===t.stage?"npm install":"ファイル展開";return t.restoreFailed?`❌ MCP「${e}」の${n}に失敗し、**旧版の復元にも失敗しました**。\n🚨 この機体の ${e} は壊れています。退避は次の場所に在ります:\n ${t.backupPath}\n詳細: ${t.error.message}`:t.rolledBack?`❌ MCP「${e}」の${n}に失敗したため、**旧版へ戻しました**(機体は元の状態です)。\n詳細: ${t.error.message}`:`❌ MCP「${e}」の${n}に失敗しました(新規インストールのため、作りかけは削除しました)。\n詳細: ${t.error.message}`}function withoutDevDependencies(e){const t=JSON.parse(e);return Object.prototype.hasOwnProperty.call(t,"devDependencies")?(delete t.devDependencies,`${JSON.stringify(t,null,2)}\n`):e}const PRODUCTION_NPM_INSTALL="npm install --omit=dev --no-audit --no-fund";function runProductionNpmInstall(e,{exec:t=require("child_process").execSync}={}){const n=path.join(e,"package.json"),r=fs.readFileSync(n,"utf-8"),s=withoutDevDependencies(r);s!==r&&fs.writeFileSync(n,s,"utf-8")
|
|
1
|
+
"use strict";const fs=require("fs"),path=require("path"),PRESERVE_ACROSS_SWAP=["node_modules"];function backupPathFor(e,t=new Date){return`${e}.bak-${t.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}Z$/,"Z")}`}function assertInsideInstallDir(e,t){const n=path.resolve(e),r=path.resolve(t);if(r!==n&&!r.startsWith(n+path.sep))throw new Error(`install: 展開先が installDir の外を指しています(${r})`);return r}function rmrf(e){fs.rmSync(e,{recursive:!0,force:!0})}function installPackageFiles(e){const{installDir:t,files:n,needsNpmInstall:r=!1,runNpmInstall:s,now:a}=e,o=fs.existsSync(t),c=o?backupPathFor(t,a):null;o&&fs.renameSync(t,c);const i=(e,n)=>{try{rmrf(t)}catch(e){}if(!c)return{ok:!1,rolledBack:!1,backupPath:null,stage:e,error:n};try{return fs.renameSync(c,t),{ok:!1,rolledBack:!0,backupPath:null,stage:e,error:n}}catch(t){return{ok:!1,rolledBack:!1,restoreFailed:!0,backupPath:c,stage:e,error:new Error(`${n.message} / さらに旧版の復元にも失敗: ${t.message}`)}}};let l=!1;try{fs.mkdirSync(t,{recursive:!0});for(const e of n){const n=assertInsideInstallDir(t,path.join(t,e.path));if(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,e.content,"utf-8"),"number"==typeof e.mode)try{fs.chmodSync(n,511&e.mode)}catch(e){}}if(c&&!r)for(const e of PRESERVE_ACROSS_SWAP){const n=path.join(c,e),r=path.join(t,e);fs.existsSync(n)&&!fs.existsSync(r)&&(fs.renameSync(n,r),l=!0)}}catch(e){return i("extract",e)}if(r&&fs.existsSync(path.join(t,"package.json")))try{if("function"!=typeof s)throw new Error("runNpmInstall が渡されていません");s(t)}catch(e){return i("npm-install",e)}if(c)try{rmrf(c)}catch(e){}return{ok:!0,installDir:t,fileCount:n.length,restoredNodeModules:l}}function formatInstallFailure(e,t){const n="npm-install"===t.stage?"npm install":"ファイル展開";return t.restoreFailed?`❌ MCP「${e}」の${n}に失敗し、**旧版の復元にも失敗しました**。\n🚨 この機体の ${e} は壊れています。退避は次の場所に在ります:\n ${t.backupPath}\n詳細: ${t.error.message}`:t.rolledBack?`❌ MCP「${e}」の${n}に失敗したため、**旧版へ戻しました**(機体は元の状態です)。\n詳細: ${t.error.message}`:`❌ MCP「${e}」の${n}に失敗しました(新規インストールのため、作りかけは削除しました)。\n詳細: ${t.error.message}`}function withoutDevDependencies(e){const t=JSON.parse(e);return Object.prototype.hasOwnProperty.call(t,"devDependencies")?(delete t.devDependencies,`${JSON.stringify(t,null,2)}\n`):e}const PRODUCTION_NPM_INSTALL="npm install --omit=dev --no-audit --no-fund",NPM_INSTALL_TIMEOUT_MS=9e5;function runProductionNpmInstall(e,{exec:t=require("child_process").execSync}={}){const n=path.join(e,"package.json"),r=fs.readFileSync(n,"utf-8"),s=withoutDevDependencies(r);s!==r&&fs.writeFileSync(n,s,"utf-8");try{t(PRODUCTION_NPM_INSTALL,{cwd:e,timeout:9e5,stdio:"pipe"})}catch(e){if(e&&"ETIMEDOUT"===e.code)throw new Error(`npm install が 15 分で終わらず打ち切りました(網が極端に遅いか npm が固まった見込み)。撃ち直すと、取得済みの分は npm の置き場から使われます。(${e.message})`);throw e}}module.exports={installPackageFiles:installPackageFiles,formatInstallFailure:formatInstallFailure,backupPathFor:backupPathFor,PRESERVE_ACROSS_SWAP:PRESERVE_ACROSS_SWAP,withoutDevDependencies:withoutDevDependencies,runProductionNpmInstall:runProductionNpmInstall,PRODUCTION_NPM_INSTALL:PRODUCTION_NPM_INSTALL,NPM_INSTALL_TIMEOUT_MS:9e5};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fetch=(...t)=>import("node-fetch").then(({default:e})=>e(...t)),https=require("https"),fs=require("fs"),path=require("path"),os=require("os"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t}=requireFromPackage(require,"../../i18n"),PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnvironment=PORTAL_URL_ENV.includes("localhost"),httpsAgent=new https.Agent({rejectUnauthorized:!isDevEnvironment}),PORTAL_URL=PORTAL_URL_ENV||(isDevEnvironment?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(t){const e=`[${(new Date).toISOString()}] ${t}\n`;fs.appendFileSync(LOG_FILE,e),logger.debug(t)}let _cliVersion;function getCliVersion(){if(void 0!==_cliVersion)return _cliVersion;let t=__dirname;for(let e=0;e<6;e++){try{const e=require(path.join(t,"package.json"));if(e&&e.version&&e.bin&&Object.keys(e.bin).some(t=>/^[a-z]+\d+$/.test(t)))return _cliVersion=e.version,_cliVersion}catch(t){}const e=path.dirname(t);if(e===t)break;t=e}return _cliVersion=null,_cliVersion}async function getPromptFromPortal(e,o,r="ja"){try{if(log(`[portal-api-client] getPromptFromPortal called with keyword: ${e}, lang: ${r}, token: ${o?"present":"missing"}`),!o)return log("[portal-api-client] No authentication token provided"),{success:!1,error:t("mcp.noAuthToken")};const s=process.env.MUSUHI_TENANT_ID||"",a=encodeURIComponent(e);let n=`${PORTAL_URL}/cli/prompts/knowledge-injection/${a}?lang=${r}`;s&&(n+=`&tenant_id=${encodeURIComponent(s)}`),log(`[portal-api-client] Direct keyword search: ${n}`),log("[portal-api-client] About to call fetch...");const c=await fetch(n,{method:"GET",headers:{"X-CLI-Token":o,...getCliVersion()?{"X-CLI-Version":getCliVersion()}:{},...s?{"X-Tenant-ID":s}:{},"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:n.startsWith("https")?httpsAgent:void 0});if(log("[portal-api-client] Fetch completed"),log(`[portal-api-client] Response status: ${c.status}`),log(`[portal-api-client] Response headers: ${JSON.stringify(Object.fromEntries(c.headers.entries()))}`),!c.ok){log(`[portal-api-client] Portal API error: ${c.status}`);const e=await c.text();log(`[portal-api-client] Error response: ${e}`);let o=t("mcp.portalApiError",{status:c.status}),r=null;try{const t=JSON.parse(e);t.message&&(o=t.message),t.code&&(r=t.code)}catch(t){}return{success:!1,error:o,status:c.status,code:r}}const i=await c.json();if(log("[portal-api-client] Direct keyword search completed successfully"),!i.success)return log("[portal-api-client] API returned success: false"),{success:!1,error:i.message||t("mcp.apiReturnedError")};const l=i.data;if(!l)return log(`[portal-api-client] No prompt found for keyword: ${e}`),{success:!1,error:t("mcp.promptNotFoundForKeyword",{keyword:e})};if(log(`[portal-api-client] Found prompt: ${l.title}`),l.content||l.text)return{success:!0,knowledge:l.content||l.text,title:l.title,tags:l.tags||[]};try{const t=`${PORTAL_URL}/cli/prompts/${l.id}`;log(`[portal-api-client] Fetching prompt details from: ${t}`);const e=await fetch(t,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:httpsAgent});if(e.ok){const t=await e.json();if(t.success&&t.data?.prompt?.content)return log("[portal-api-client] Successfully fetched prompt content"),{success:!0,knowledge:t.data.prompt.content,title:l.title||l.name,tags:l.tags}}}catch(t){log(`[portal-api-client] Error fetching prompt details: ${t.message}`)}return{success:!0,knowledge:l.description||`# ${l.title}\n\n${t("mcp.knowledgeLoadError",{error:"detail fetch failed"})}`,title:l.title||l.name,tags:l.tags}}catch(e){return log(`[portal-api-client] FATAL ERROR in getPromptFromPortal: ${e.message}`),log(`[portal-api-client] Error stack: ${e.stack}`),logger.error(`Failed to get prompt from Portal: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function getSlotConfig(t,e){try{if(!e)return log("[portal-api-client] getSlotConfig: No token"),{ok:!1,reason:"no-token"};const o=process.env.MUSUHI_TENANT_ID||"",r=`${PORTAL_URL}/cli/slot-config/${t}`;log(`[portal-api-client] Fetching slot config: ${r}${o?` (tenant=${o})`:""}`);const s=await fetch(r,{method:"GET",headers:{"X-CLI-Token":e,...getCliVersion()?{"X-CLI-Version":getCliVersion()}:{},...o?{"X-Tenant-ID":o}:{},"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){log(`[portal-api-client] Slot config API error: ${s.status}`);let t=null;try{const e=await s.json();t=e?.error?.message||e?.message||null}catch{t=null}return 404===s.status?{ok:!1,reason:"not-configured",status:404,message:t}:403===s.status?{ok:!1,reason:"rank",status:403,message:t}:{ok:!1,reason:"http",status:s.status,message:t}}const a=await s.json();return a.success&&a.data?.keyword?(log(`[portal-api-client] Slot ${t}: custom keyword "${a.data.keyword}"`),{ok:!0,keyword:a.data.keyword}):(log(`[portal-api-client] Slot ${t}: no custom config`),{ok:!0,keyword:null})}catch(t){return log(`[portal-api-client] getSlotConfig error: ${t.message}`),{ok:!1,reason:"network",status:null,message:t.message}}}async function reportViolation(e,o,r,s){try{if(log(`[portal-api-client] reportViolation called: severity=${o}`),!r)return{success:!1,error:t("mcp.violationNoToken")};const a=`${PORTAL_URL}/cli/report-violation`,n={reason:e,severity:o};s&&(n.userMessage=s);const c=await fetch(a,{method:"POST",headers:{"X-CLI-Token":r,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(n)});if(!c.ok){const e=await c.text();return log(`[portal-api-client] reportViolation error: ${c.status} ${e}`),{success:!1,error:t("mcp.violationApiError",{status:c.status})}}const i=await c.json();return log(`[portal-api-client] reportViolation result: action=${i.action}, count=${i.violationCount}`),i}catch(e){return log(`[portal-api-client] reportViolation FATAL: ${e.message}`),{success:!1,error:t("mcp.violationCommunicationError",{error:e.message})}}}async function listAvailableMcps(e={},o,r="ja"){try{if(log(`[portal-api-client] listAvailableMcps called: ${JSON.stringify(e)}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const s=new URLSearchParams;e.search&&s.set("search",e.search),e.limit&&s.set("limit",String(e.limit)),s.set("lang",r);const a=`${PORTAL_URL}/mcp/catalog?${s.toString()}`;log(`[portal-api-client] Fetching MCP catalog: ${a}`);const n=await fetch(a,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0});if(!n.ok){const e=await n.text();log(`[portal-api-client] listAvailableMcps error: ${n.status} ${e}`);let o=t("mcp.listMcpsFailed",{error:`HTTP ${n.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const c=await n.json();return log(`[portal-api-client] listAvailableMcps: ${c.data?.length||0} MCPs found`),{success:!0,data:c.data||[]}}catch(e){return log(`[portal-api-client] listAvailableMcps FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function getMcpInfo(e,o,r="ja"){try{if(log(`[portal-api-client] getMcpInfo called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const s=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/details?lang=${r}`;log(`[portal-api-client] Fetching MCP info: ${s}`);const a=await fetch(s,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:s.startsWith("https")?httpsAgent:void 0});if(!a.ok){const e=await a.text();log(`[portal-api-client] getMcpInfo error: ${a.status} ${e}`);let o=t("mcp.getMcpInfoFailed",{error:`HTTP ${a.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const n=await a.json();return log(`[portal-api-client] getMcpInfo: found ${n.data?.name||e}`),{success:!0,data:n.data}}catch(e){return log(`[portal-api-client] getMcpInfo FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function callMcpTool(e,o,r,s){try{if(log(`[portal-api-client] callMcpTool called: mcpId=${e}, tool=${o}`),!s)return{success:!1,error:t("mcp.noAuthToken")};const a=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/call`,n={tool_name:o,arguments:r};log(`[portal-api-client] Calling MCP tool: ${a}`);const c=await fetch(a,{method:"POST",headers:{"X-CLI-Token":s,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(n)});if(!c.ok){const e=await c.text();log(`[portal-api-client] callMcpTool error: ${c.status} ${e}`);let o=t("mcp.callToolFailed",{error:`HTTP ${c.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const i=await c.json();return log("[portal-api-client] callMcpTool: success"),{success:!0,data:i.data}}catch(e){return log(`[portal-api-client] callMcpTool FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function scanMcpCode(e,o){try{if(log("[portal-api-client] scanMcpCode called"),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/scan`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({code:e})});if(!s.ok){const t=await s.text();return log(`[portal-api-client] scanMcpCode error: ${s.status} ${t}`),{success:!1,error:`HTTP ${s.status}`}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] scanMcpCode FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deployUserMcp(e,o){try{if(log(`[portal-api-client] deployUserMcp called: name=${e.name}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/deploy`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(e)});if(!s.ok){const t=await s.text();log(`[portal-api-client] deployUserMcp error: ${s.status} ${t}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);if(o.error&&(e=o.error),o.details)return{success:!1,error:e,details:o.details}}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deployUserMcp FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function listUserCreatedMcps(e){try{if(log("[portal-api-client] listUserCreatedMcps called"),!e)return{success:!1,error:t("mcp.noAuthToken")};const o=`${PORTAL_URL}/mcp/user/me/created`,r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:(await r.text(),log(`[portal-api-client] listUserCreatedMcps error: ${r.status}`),{success:!1,error:`HTTP ${r.status}`})}catch(e){return log(`[portal-api-client] listUserCreatedMcps FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deleteUserMcp(e,o){try{if(log(`[portal-api-client] deleteUserMcp called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}`,s=await fetch(r,{method:"DELETE",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] deleteUserMcp error: ${s.status}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.error&&(e=o.error)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deleteUserMcp FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function listMyPrompts(e){try{if(log("[portal-api-client] listMyPrompts called"),!e)return{success:!1,error:t("mcp.noAuthToken")};const o=`${PORTAL_URL}/cli/my-agents`,r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data||[]}:(await r.text(),log(`[portal-api-client] listMyPrompts error: ${r.status}`),{success:!1,error:`HTTP ${r.status}`})}catch(e){return log(`[portal-api-client] listMyPrompts FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function saveMyPrompt(e,o){try{if(log(`[portal-api-client] saveMyPrompt called: title=${e.title}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/cli/my-agents`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(e)});if(!s.ok){const t=await s.text();log(`[portal-api-client] saveMyPrompt error: ${s.status} ${t}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] saveMyPrompt FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deleteMyPrompt(e,o){try{if(log(`[portal-api-client] deleteMyPrompt called: id=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/cli/my-agents/${encodeURIComponent(e)}`,s=await fetch(r,{method:"DELETE",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] deleteMyPrompt error: ${s.status}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deleteMyPrompt FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function downloadMcpPackage(e,o){try{if(log(`[portal-api-client] downloadMcpPackage called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/download`;log(`[portal-api-client] Downloading MCP package: ${r}`);const s=await fetch(r,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] downloadMcpPackage error: ${s.status} ${t}`);let e=`Download failed: HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}const a=await s.json();return log(`[portal-api-client] downloadMcpPackage: ${a.data?.files?.length||0} files`),{success:!0,data:a.data}}catch(e){return log(`[portal-api-client] downloadMcpPackage FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function getGatewayConfig(t){try{if(!t)return{success:!1,error:"No token"};const e=`${PORTAL_URL}/mcp/user/me/gateway-config`;log(`[portal-api-client] getGatewayConfig: ${e}`);const o=await fetch(e,{method:"GET",headers:{"X-CLI-Token":t,"Content-Type":"application/json"},agent:e.startsWith("https")?httpsAgent:void 0});if(!o.ok)return{success:!1,error:`HTTP ${o.status}`};const r=await o.json();return log(`[portal-api-client] getGatewayConfig: ${r.data?.services?.length||0} services`),{success:!0,data:r.data}}catch(t){return log(`[portal-api-client] getGatewayConfig error: ${t.message}`),{success:!1,error:t.message}}}async function searchMcpServices(t,e){try{if(!e)return{success:!1,error:"No token"};const o=`${PORTAL_URL}/mcp/search?q=${encodeURIComponent(t)}`;log(`[portal-api-client] searchMcpServices: ${o}`);const r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:{success:!1,error:`HTTP ${r.status}`}}catch(t){return log(`[portal-api-client] searchMcpServices error: ${t.message}`),{success:!1,error:t.message}}}async function installMcpService(t,e,o,r){try{if(!o)return{success:!1,error:"No token"};const s=`${PORTAL_URL}/mcp/user/me/install`,a={mcpId:t,alwaysOn:e??!1};r&&Object.keys(r).length>0&&(a.envOverrides=r);const n=await fetch(s,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(a)});return n.ok?{success:!0,data:(await n.json()).data}:{success:!1,error:`HTTP ${n.status}`}}catch(t){return log(`[portal-api-client] installMcpService error: ${t.message}`),{success:!1,error:t.message}}}async function setServiceDeactivated(t,e,o){try{if(!o)return{success:!1,error:"No token"};const r=e?"deactivate":"activate",s=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}/${r}`,a=await fetch(s,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0});return a.ok?{success:!0,data:(await a.json()).data}:{success:!1,error:`HTTP ${a.status}`}}catch(t){return log(`[portal-api-client] setServiceDeactivated error: ${t.message}`),{success:!1,error:t.message}}}async function uninstallMcpService(t,e){try{if(!e)return{success:!1,error:"No token"};const o=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}`,r=await fetch(o,{method:"DELETE",headers:{"X-CLI-Token":e,"Content-Type":"application/json"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:{success:!1,error:`HTTP ${r.status}`}}catch(t){return log(`[portal-api-client] uninstallMcpService error: ${t.message}`),{success:!1,error:t.message}}}async function updateMcpEnv(t,e,o){try{if(!o)return{success:!1,error:"No token"};const r=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}/env`,s=await fetch(r,{method:"PUT",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({env:e})});if(!s.ok)return{success:!1,error:`HTTP ${s.status}`};const a=await s.json();return{success:!0,data:a.data,warning:a.warning||null}}catch(t){return log(`[portal-api-client] updateMcpEnv error: ${t.message}`),{success:!1,error:t.message}}}async function callMcpToolProxy(t,e,o,r){try{if(!r)return{success:!1,error:"No token"};const s=`${PORTAL_URL}/mcp/${encodeURIComponent(t)}/call`,a=await fetch(s,{method:"POST",headers:{"X-CLI-Token":r,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({tool_name:e,arguments:o})});if(!a.ok){const t=await a.text();return{success:!1,error:`HTTP ${a.status}: ${t.slice(0,200)}`}}return await a.json()}catch(t){return log(`[portal-api-client] callMcpToolProxy error: ${t.message}`),{success:!1,error:t.message}}}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),module.exports={getPromptFromPortal:getPromptFromPortal,getSlotConfig:getSlotConfig,reportViolation:reportViolation,listAvailableMcps:listAvailableMcps,getMcpInfo:getMcpInfo,downloadMcpPackage:downloadMcpPackage,callMcpTool:callMcpTool,scanMcpCode:scanMcpCode,deployUserMcp:deployUserMcp,listUserCreatedMcps:listUserCreatedMcps,deleteUserMcp:deleteUserMcp,listMyPrompts:listMyPrompts,saveMyPrompt:saveMyPrompt,deleteMyPrompt:deleteMyPrompt,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy};
|
|
1
|
+
const fetch=(...t)=>import("node-fetch").then(({default:e})=>e(...t)),https=require("https"),fs=require("fs"),path=require("path"),os=require("os"),{requireFromPackage:requireFromPackage}=require("../requireFromPackage.cjs"),{logger:logger}=requireFromPackage(require,"../../utils/logger"),{t:t}=requireFromPackage(require,"../../i18n"),PORTAL_URL_ENV=process.env.BLUELAMP_PORTAL_API_BASE_URL||process.env.PORTAL_URL||"",isDevEnvironment=PORTAL_URL_ENV.includes("localhost"),httpsAgent=new https.Agent({rejectUnauthorized:!isDevEnvironment}),PORTAL_URL=PORTAL_URL_ENV||(isDevEnvironment?"http://localhost:8430/api/bluelamp":"https://api.motohi.jp/api/bluelamp"),LOG_DIR=path.join(os.homedir(),".bluelamp-vscode","logs"),LOG_FILE=path.join(LOG_DIR,"mcp.log");function log(t){const e=`[${(new Date).toISOString()}] ${t}\n`;fs.appendFileSync(LOG_FILE,e),logger.debug(t)}let _cliVersion;function getCliVersion(){if(void 0!==_cliVersion)return _cliVersion;let t=__dirname;for(let e=0;e<6;e++){try{const e=require(path.join(t,"package.json"));if(e&&e.version&&e.bin&&Object.keys(e.bin).some(t=>/^[a-z]+\d+$/.test(t)))return _cliVersion=e.version,_cliVersion}catch(t){}const e=path.dirname(t);if(e===t)break;t=e}return _cliVersion=null,_cliVersion}async function getPromptFromPortal(e,o,r="ja"){try{if(log(`[portal-api-client] getPromptFromPortal called with keyword: ${e}, lang: ${r}, token: ${o?"present":"missing"}`),!o)return log("[portal-api-client] No authentication token provided"),{success:!1,error:t("mcp.noAuthToken")};const s=process.env.MUSUHI_TENANT_ID||"",a=encodeURIComponent(e);let n=`${PORTAL_URL}/cli/prompts/knowledge-injection/${a}?lang=${r}`;s&&(n+=`&tenant_id=${encodeURIComponent(s)}`),log(`[portal-api-client] Direct keyword search: ${n}`),log("[portal-api-client] About to call fetch...");const c=await fetch(n,{method:"GET",headers:{"X-CLI-Token":o,...getCliVersion()?{"X-CLI-Version":getCliVersion()}:{},...s?{"X-Tenant-ID":s}:{},"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:n.startsWith("https")?httpsAgent:void 0});if(log("[portal-api-client] Fetch completed"),log(`[portal-api-client] Response status: ${c.status}`),log(`[portal-api-client] Response headers: ${JSON.stringify(Object.fromEntries(c.headers.entries()))}`),!c.ok){log(`[portal-api-client] Portal API error: ${c.status}`);const e=await c.text();log(`[portal-api-client] Error response: ${e}`);let o=t("mcp.portalApiError",{status:c.status}),r=null;try{const t=JSON.parse(e);t.message&&(o=t.message),t.code&&(r=t.code)}catch(t){}return{success:!1,error:o,status:c.status,code:r}}const i=await c.json();if(log("[portal-api-client] Direct keyword search completed successfully"),!i.success)return log("[portal-api-client] API returned success: false"),{success:!1,error:i.message||t("mcp.apiReturnedError")};const l=i.data;if(!l)return log(`[portal-api-client] No prompt found for keyword: ${e}`),{success:!1,error:t("mcp.promptNotFoundForKeyword",{keyword:e})};if(log(`[portal-api-client] Found prompt: ${l.title}`),l.content||l.text)return{success:!0,knowledge:l.content||l.text,title:l.title,tags:l.tags||[]};try{const t=`${PORTAL_URL}/cli/prompts/${l.id}`;log(`[portal-api-client] Fetching prompt details from: ${t}`);const e=await fetch(t,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:httpsAgent});if(e.ok){const t=await e.json();if(t.success&&t.data?.prompt?.content)return log("[portal-api-client] Successfully fetched prompt content"),{success:!0,knowledge:t.data.prompt.content,title:l.title||l.name,tags:l.tags}}}catch(t){log(`[portal-api-client] Error fetching prompt details: ${t.message}`)}return{success:!0,knowledge:l.description||`# ${l.title}\n\n${t("mcp.knowledgeLoadError",{error:"detail fetch failed"})}`,title:l.title||l.name,tags:l.tags}}catch(e){return log(`[portal-api-client] FATAL ERROR in getPromptFromPortal: ${e.message}`),log(`[portal-api-client] Error stack: ${e.stack}`),logger.error(`Failed to get prompt from Portal: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message}),networkFailure:Boolean(e&&"FetchError"===e.name)}}}async function getSlotConfig(t,e){try{if(!e)return log("[portal-api-client] getSlotConfig: No token"),{ok:!1,reason:"no-token"};const o=process.env.MUSUHI_TENANT_ID||"",r=`${PORTAL_URL}/cli/slot-config/${t}`;log(`[portal-api-client] Fetching slot config: ${r}${o?` (tenant=${o})`:""}`);const s=await fetch(r,{method:"GET",headers:{"X-CLI-Token":e,...getCliVersion()?{"X-CLI-Version":getCliVersion()}:{},...o?{"X-Tenant-ID":o}:{},"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){log(`[portal-api-client] Slot config API error: ${s.status}`);let t=null;try{const e=await s.json();t=e?.error?.message||e?.message||null}catch{t=null}return 404===s.status?{ok:!1,reason:"not-configured",status:404,message:t}:403===s.status?{ok:!1,reason:"rank",status:403,message:t}:{ok:!1,reason:"http",status:s.status,message:t}}const a=await s.json();return a.success&&a.data?.keyword?(log(`[portal-api-client] Slot ${t}: custom keyword "${a.data.keyword}"`),{ok:!0,keyword:a.data.keyword}):(log(`[portal-api-client] Slot ${t}: no custom config`),{ok:!0,keyword:null})}catch(t){return log(`[portal-api-client] getSlotConfig error: ${t.message}`),{ok:!1,reason:"network",status:null,message:t.message}}}async function reportViolation(e,o,r,s){try{if(log(`[portal-api-client] reportViolation called: severity=${o}`),!r)return{success:!1,error:t("mcp.violationNoToken")};const a=`${PORTAL_URL}/cli/report-violation`,n={reason:e,severity:o};s&&(n.userMessage=s);const c=await fetch(a,{method:"POST",headers:{"X-CLI-Token":r,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(n)});if(!c.ok){const e=await c.text();return log(`[portal-api-client] reportViolation error: ${c.status} ${e}`),{success:!1,error:t("mcp.violationApiError",{status:c.status})}}const i=await c.json();return log(`[portal-api-client] reportViolation result: action=${i.action}, count=${i.violationCount}`),i}catch(e){return log(`[portal-api-client] reportViolation FATAL: ${e.message}`),{success:!1,error:t("mcp.violationCommunicationError",{error:e.message})}}}async function listAvailableMcps(e={},o,r="ja"){try{if(log(`[portal-api-client] listAvailableMcps called: ${JSON.stringify(e)}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const s=new URLSearchParams;e.search&&s.set("search",e.search),e.limit&&s.set("limit",String(e.limit)),s.set("lang",r);const a=`${PORTAL_URL}/mcp/catalog?${s.toString()}`;log(`[portal-api-client] Fetching MCP catalog: ${a}`);const n=await fetch(a,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0});if(!n.ok){const e=await n.text();log(`[portal-api-client] listAvailableMcps error: ${n.status} ${e}`);let o=t("mcp.listMcpsFailed",{error:`HTTP ${n.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const c=await n.json();return log(`[portal-api-client] listAvailableMcps: ${c.data?.length||0} MCPs found`),{success:!0,data:c.data||[]}}catch(e){return log(`[portal-api-client] listAvailableMcps FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function getMcpInfo(e,o,r="ja"){try{if(log(`[portal-api-client] getMcpInfo called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const s=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/details?lang=${r}`;log(`[portal-api-client] Fetching MCP info: ${s}`);const a=await fetch(s,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:s.startsWith("https")?httpsAgent:void 0});if(!a.ok){const e=await a.text();log(`[portal-api-client] getMcpInfo error: ${a.status} ${e}`);let o=t("mcp.getMcpInfoFailed",{error:`HTTP ${a.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const n=await a.json();return log(`[portal-api-client] getMcpInfo: found ${n.data?.name||e}`),{success:!0,data:n.data}}catch(e){return log(`[portal-api-client] getMcpInfo FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function callMcpTool(e,o,r,s){try{if(log(`[portal-api-client] callMcpTool called: mcpId=${e}, tool=${o}`),!s)return{success:!1,error:t("mcp.noAuthToken")};const a=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/call`,n={tool_name:o,arguments:r};log(`[portal-api-client] Calling MCP tool: ${a}`);const c=await fetch(a,{method:"POST",headers:{"X-CLI-Token":s,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:a.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(n)});if(!c.ok){const e=await c.text();log(`[portal-api-client] callMcpTool error: ${c.status} ${e}`);let o=t("mcp.callToolFailed",{error:`HTTP ${c.status}`});try{const t=JSON.parse(e);t.message&&(o=t.message)}catch(t){}return{success:!1,error:o}}const i=await c.json();return log("[portal-api-client] callMcpTool: success"),{success:!0,data:i.data}}catch(e){return log(`[portal-api-client] callMcpTool FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function scanMcpCode(e,o){try{if(log("[portal-api-client] scanMcpCode called"),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/scan`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({code:e})});if(!s.ok){const t=await s.text();return log(`[portal-api-client] scanMcpCode error: ${s.status} ${t}`),{success:!1,error:`HTTP ${s.status}`}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] scanMcpCode FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deployUserMcp(e,o){try{if(log(`[portal-api-client] deployUserMcp called: name=${e.name}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/deploy`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(e)});if(!s.ok){const t=await s.text();log(`[portal-api-client] deployUserMcp error: ${s.status} ${t}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);if(o.error&&(e=o.error),o.details)return{success:!1,error:e,details:o.details}}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deployUserMcp FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function listUserCreatedMcps(e){try{if(log("[portal-api-client] listUserCreatedMcps called"),!e)return{success:!1,error:t("mcp.noAuthToken")};const o=`${PORTAL_URL}/mcp/user/me/created`,r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:(await r.text(),log(`[portal-api-client] listUserCreatedMcps error: ${r.status}`),{success:!1,error:`HTTP ${r.status}`})}catch(e){return log(`[portal-api-client] listUserCreatedMcps FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deleteUserMcp(e,o){try{if(log(`[portal-api-client] deleteUserMcp called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}`,s=await fetch(r,{method:"DELETE",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] deleteUserMcp error: ${s.status}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.error&&(e=o.error)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deleteUserMcp FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function listMyPrompts(e){try{if(log("[portal-api-client] listMyPrompts called"),!e)return{success:!1,error:t("mcp.noAuthToken")};const o=`${PORTAL_URL}/cli/my-agents`,r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data||[]}:(await r.text(),log(`[portal-api-client] listMyPrompts error: ${r.status}`),{success:!1,error:`HTTP ${r.status}`})}catch(e){return log(`[portal-api-client] listMyPrompts FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function saveMyPrompt(e,o){try{if(log(`[portal-api-client] saveMyPrompt called: title=${e.title}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/cli/my-agents`,s=await fetch(r,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(e)});if(!s.ok){const t=await s.text();log(`[portal-api-client] saveMyPrompt error: ${s.status} ${t}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] saveMyPrompt FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function deleteMyPrompt(e,o){try{if(log(`[portal-api-client] deleteMyPrompt called: id=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/cli/my-agents/${encodeURIComponent(e)}`,s=await fetch(r,{method:"DELETE",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] deleteMyPrompt error: ${s.status}`);let e=`HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}return{success:!0,data:(await s.json()).data}}catch(e){return log(`[portal-api-client] deleteMyPrompt FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function downloadMcpPackage(e,o){try{if(log(`[portal-api-client] downloadMcpPackage called: mcpId=${e}`),!o)return{success:!1,error:t("mcp.noAuthToken")};const r=`${PORTAL_URL}/mcp/${encodeURIComponent(e)}/download`;log(`[portal-api-client] Downloading MCP package: ${r}`);const s=await fetch(r,{method:"GET",headers:{"X-CLI-Token":o,"Content-Type":"application/json","User-Agent":"BlueLamp-VSCode-MCP/1.0"},agent:r.startsWith("https")?httpsAgent:void 0});if(!s.ok){const t=await s.text();log(`[portal-api-client] downloadMcpPackage error: ${s.status} ${t}`);let e=`Download failed: HTTP ${s.status}`;try{const o=JSON.parse(t);o.message&&(e=o.message)}catch(t){}return{success:!1,error:e}}const a=await s.json();return log(`[portal-api-client] downloadMcpPackage: ${a.data?.files?.length||0} files`),{success:!0,data:a.data}}catch(e){return log(`[portal-api-client] downloadMcpPackage FATAL: ${e.message}`),{success:!1,error:t("mcp.portalCommunicationError",{error:e.message})}}}async function getGatewayConfig(t){try{if(!t)return{success:!1,error:"No token"};const e=`${PORTAL_URL}/mcp/user/me/gateway-config`;log(`[portal-api-client] getGatewayConfig: ${e}`);const o=await fetch(e,{method:"GET",headers:{"X-CLI-Token":t,"Content-Type":"application/json"},agent:e.startsWith("https")?httpsAgent:void 0});if(!o.ok)return{success:!1,error:`HTTP ${o.status}`};const r=await o.json();return log(`[portal-api-client] getGatewayConfig: ${r.data?.services?.length||0} services`),{success:!0,data:r.data}}catch(t){return log(`[portal-api-client] getGatewayConfig error: ${t.message}`),{success:!1,error:t.message}}}async function searchMcpServices(t,e){try{if(!e)return{success:!1,error:"No token"};const o=`${PORTAL_URL}/mcp/search?q=${encodeURIComponent(t)}`;log(`[portal-api-client] searchMcpServices: ${o}`);const r=await fetch(o,{method:"GET",headers:{"X-CLI-Token":e,"Content-Type":"application/json"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:{success:!1,error:`HTTP ${r.status}`}}catch(t){return log(`[portal-api-client] searchMcpServices error: ${t.message}`),{success:!1,error:t.message}}}async function installMcpService(t,e,o,r){try{if(!o)return{success:!1,error:"No token"};const s=`${PORTAL_URL}/mcp/user/me/install`,a={mcpId:t,alwaysOn:e??!1};r&&Object.keys(r).length>0&&(a.envOverrides=r);const n=await fetch(s,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0,body:JSON.stringify(a)});return n.ok?{success:!0,data:(await n.json()).data}:{success:!1,error:`HTTP ${n.status}`}}catch(t){return log(`[portal-api-client] installMcpService error: ${t.message}`),{success:!1,error:t.message}}}async function setServiceDeactivated(t,e,o){try{if(!o)return{success:!1,error:"No token"};const r=e?"deactivate":"activate",s=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}/${r}`,a=await fetch(s,{method:"POST",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0});return a.ok?{success:!0,data:(await a.json()).data}:{success:!1,error:`HTTP ${a.status}`}}catch(t){return log(`[portal-api-client] setServiceDeactivated error: ${t.message}`),{success:!1,error:t.message}}}async function uninstallMcpService(t,e){try{if(!e)return{success:!1,error:"No token"};const o=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}`,r=await fetch(o,{method:"DELETE",headers:{"X-CLI-Token":e,"Content-Type":"application/json"},agent:o.startsWith("https")?httpsAgent:void 0});return r.ok?{success:!0,data:(await r.json()).data}:{success:!1,error:`HTTP ${r.status}`}}catch(t){return log(`[portal-api-client] uninstallMcpService error: ${t.message}`),{success:!1,error:t.message}}}async function updateMcpEnv(t,e,o){try{if(!o)return{success:!1,error:"No token"};const r=`${PORTAL_URL}/mcp/user/me/services/${encodeURIComponent(t)}/env`,s=await fetch(r,{method:"PUT",headers:{"X-CLI-Token":o,"Content-Type":"application/json"},agent:r.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({env:e})});if(!s.ok)return{success:!1,error:`HTTP ${s.status}`};const a=await s.json();return{success:!0,data:a.data,warning:a.warning||null}}catch(t){return log(`[portal-api-client] updateMcpEnv error: ${t.message}`),{success:!1,error:t.message}}}async function callMcpToolProxy(t,e,o,r){try{if(!r)return{success:!1,error:"No token"};const s=`${PORTAL_URL}/mcp/${encodeURIComponent(t)}/call`,a=await fetch(s,{method:"POST",headers:{"X-CLI-Token":r,"Content-Type":"application/json"},agent:s.startsWith("https")?httpsAgent:void 0,body:JSON.stringify({tool_name:e,arguments:o})});if(!a.ok){const t=await a.text();return{success:!1,error:`HTTP ${a.status}: ${t.slice(0,200)}`}}return await a.json()}catch(t){return log(`[portal-api-client] callMcpToolProxy error: ${t.message}`),{success:!1,error:t.message}}}fs.existsSync(LOG_DIR)||fs.mkdirSync(LOG_DIR,{recursive:!0}),module.exports={getPromptFromPortal:getPromptFromPortal,getSlotConfig:getSlotConfig,reportViolation:reportViolation,listAvailableMcps:listAvailableMcps,getMcpInfo:getMcpInfo,downloadMcpPackage:downloadMcpPackage,callMcpTool:callMcpTool,scanMcpCode:scanMcpCode,deployUserMcp:deployUserMcp,listUserCreatedMcps:listUserCreatedMcps,deleteUserMcp:deleteUserMcp,listMyPrompts:listMyPrompts,saveMyPrompt:saveMyPrompt,deleteMyPrompt:deleteMyPrompt,getGatewayConfig:getGatewayConfig,searchMcpServices:searchMcpServices,installMcpService:installMcpService,setServiceDeactivated:setServiceDeactivated,uninstallMcpService:uninstallMcpService,updateMcpEnv:updateMcpEnv,callMcpToolProxy:callMcpToolProxy};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bluelamp-secret exec` — 金庫の値を**子プロセスの環境変数にだけ**入れてコマンドを走らせる(箱 n=2018 設計④)。
|
|
3
|
+
*
|
|
4
|
+
* 🕳 塞いでいる穴: MCP の `get_secret` は値を返り値にしていたため、値が会話の転写に残った。
|
|
5
|
+
* その口を閉じると、値を Bash で使っていた手順(21 日で 235 回)が壊れる。⇒ 値を「見せずに使う」口をここに置く。
|
|
6
|
+
*
|
|
7
|
+
* 🔑 値の通り道は「金庫の HTTP 応答 → 子の env」の 1 本だけ。
|
|
8
|
+
* ⛔ argv・標準出力・標準エラー・ログ・ファイル・エラーの文言に値を出さない。
|
|
9
|
+
* ⛔ HTTP の本文・fetch の例外をそのまま出さない(本文は値を、例外は要求を引用しうる)。
|
|
10
|
+
* 失敗は「どの ENV 名・どの金庫の名前が・なぜ」だけを返す。
|
|
11
|
+
* 🔑 使用の記録: `GET /vault/:service` は backend 側で監査 `get`(誰が・どの秘密を)を 1 行書く。
|
|
12
|
+
*
|
|
13
|
+
* 判定と組み立ては純粋関数(unit で直接撃つ)、網と子プロセスは `runSecretExec` の deps に寄せる。
|
|
14
|
+
*/
|
|
15
|
+
/** ENV 名として受ける形。⛔ これ以外(小文字・記号・先頭数字)は受けない */
|
|
16
|
+
export declare const ENV_NAME_PATTERN: RegExp;
|
|
17
|
+
/** 既定のポータル API(lib/mcp-server/portal-api-client.cjs・TokenManager と同じ値) */
|
|
18
|
+
export declare const DEFAULT_PORTAL_API_BASE_URL = "https://api.motohi.jp/api/bluelamp";
|
|
19
|
+
export declare const USAGE: string;
|
|
20
|
+
export interface SecretBinding {
|
|
21
|
+
env: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
export type ParseResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
bindings: SecretBinding[];
|
|
27
|
+
command: string;
|
|
28
|
+
args: string[];
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
help: boolean;
|
|
32
|
+
error: string;
|
|
33
|
+
};
|
|
34
|
+
/** 引数を解析する(`process.argv.slice(2)` を受ける)。値はここに来ない=金庫の名前だけ */
|
|
35
|
+
export declare function parseExecArgs(argv: readonly string[]): ParseResult;
|
|
36
|
+
/** 基底 URL(BLUELAMP_PORTAL_API_BASE_URL > PORTAL_URL > 既定。portal-api-client.cjs と同じ順) */
|
|
37
|
+
export declare function resolvePortalApiBaseUrl(env: Readonly<Record<string, string | undefined>>): string;
|
|
38
|
+
/**
|
|
39
|
+
* 金庫の 1 件を引く URL。
|
|
40
|
+
* 🔑 vault の sub-app は内部で `/api/vault/...` に経路を切るが、bluelamp module ごと `/api/bluelamp` に
|
|
41
|
+
* 載っているので外から見える経路は `<base>/vault/...`(base は /api/bluelamp まで含む)。
|
|
42
|
+
* 🔬 実測(無認証): `/api/bluelamp/api/vault/x` → 404/`/api/bluelamp/vault/x` → 401。
|
|
43
|
+
* mcp-list/bluelamp/playwright/src/fillSecret.ts の VAULT_FILL_PATH と同じ結論。
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildVaultUrl(baseUrl: string, name: string): string;
|
|
46
|
+
/** 子の env を合成する(親を書き換えない) */
|
|
47
|
+
export declare function composeChildEnv(parentEnv: Readonly<Record<string, string | undefined>>, values: Readonly<Record<string, string>>): Record<string, string>;
|
|
48
|
+
/** 金庫の応答本文から値を取り出す。形が違えば null(⛔ 本文を呼び手へ返さない) */
|
|
49
|
+
export declare function extractSecretValue(body: unknown): string | null;
|
|
50
|
+
/** HTTP の状態を理由の文言にする(⛔ 本文は使わない) */
|
|
51
|
+
export declare function describeVaultStatus(status: number): string;
|
|
52
|
+
/** 子の終わり方を終了コードにする(シグナルは 128+番号=シェルと同じ) */
|
|
53
|
+
export declare function exitCodeOf(code: number | null, signal: string | null, signalNumbers: Readonly<Record<string, number>>): number;
|
|
54
|
+
interface FetchResponseLike {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
status: number;
|
|
57
|
+
json(): Promise<unknown>;
|
|
58
|
+
}
|
|
59
|
+
interface ChildLike {
|
|
60
|
+
on(event: 'exit', cb: (code: number | null, signal: string | null) => void): unknown;
|
|
61
|
+
on(event: 'error', cb: (err: {
|
|
62
|
+
code?: string;
|
|
63
|
+
}) => void): unknown;
|
|
64
|
+
kill(signal?: string): unknown;
|
|
65
|
+
}
|
|
66
|
+
export interface SecretExecDeps {
|
|
67
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
68
|
+
loadToken: () => string | null;
|
|
69
|
+
fetch: (url: string, init: {
|
|
70
|
+
method: string;
|
|
71
|
+
headers: Record<string, string>;
|
|
72
|
+
}) => Promise<FetchResponseLike>;
|
|
73
|
+
spawn: (command: string, args: string[], options: {
|
|
74
|
+
stdio: 'inherit';
|
|
75
|
+
shell: false;
|
|
76
|
+
env: Record<string, string>;
|
|
77
|
+
}) => ChildLike;
|
|
78
|
+
stderr: (text: string) => void;
|
|
79
|
+
signalNumbers: Readonly<Record<string, number>>;
|
|
80
|
+
/** 親が受けたシグナルを子へ回す口(無ければ回さない)。戻り値は解除関数 */
|
|
81
|
+
onParentSignal?: (forward: (signal: string) => void) => () => void;
|
|
82
|
+
}
|
|
83
|
+
/** 実行する。戻り値は終了コード */
|
|
84
|
+
export declare function runSecretExec(argv: readonly string[], deps: SecretExecDeps): Promise<number>;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=secretExec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretExec.d.ts","sourceRoot":"","sources":["../../src/lib/secretExec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,QAAuB,CAAC;AAErD,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,uCAAuC,CAAC;AAEhF,eAAO,MAAM,KAAK,QAGqE,CAAC;AAExF,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GACxE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CA8ClE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,CAGjG;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,6BAA6B;AAC7B,wBAAgB,eAAe,CAC7B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACvD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKxB;AAED,gDAAgD;AAChD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK/D;AAED,mCAAmC;AACnC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,2CAA2C;AAC3C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAI9H;AAID,UAAU,iBAAiB;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAED,UAAU,SAAS;IACjB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC;IACrF,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAClD,SAAS,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9G,KAAK,EAAE,CACL,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KACrE,SAAS,CAAC;IACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,yCAAyC;IACzC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CACpE;AAID,qBAAqB;AACrB,wBAAsB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAqFlG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function parseExecArgs(e){if(0===e.length||"-h"===e[0]||"--help"===e[0]||"help"===e[0])return{ok:!1,help:!0,error:""};if("exec"!==e[0])return{ok:!1,help:!1,error:"不明な操作です(受けるのは exec だけ)"};const r=e.indexOf("--");if(-1===r)return{ok:!1,help:!1,error:"`--` の後に実行するコマンドを書いてください"};const t=e.slice(1,r),o=e[r+1];if(void 0===o||""===o)return{ok:!1,help:!1,error:"`--` の後に実行するコマンドがありません"};if(0===t.length)return{ok:!1,help:!1,error:"<ENV名>=<金庫の名前> を 1 つ以上書いてください"};const n=[],s=new Set;for(const e of t){const r=e.indexOf("=");if(r<=0)return{ok:!1,help:!1,error:`<ENV名>=<金庫の名前> の形ではない引数があります(${n.length+1} 番目)`};const t=e.slice(0,r),o=e.slice(r+1);if(!exports.ENV_NAME_PATTERN.test(t))return{ok:!1,help:!1,error:`ENV 名は ^[A-Z_][A-Z0-9_]*$ の形だけ受けます(${n.length+1} 番目)`};if(""===o)return{ok:!1,help:!1,error:`${t}: 金庫の名前が空です`};if("."===o||".."===o)return{ok:!1,help:!1,error:`${t}: 金庫の名前に . / .. は使えません`};if(s.has(t))return{ok:!1,help:!1,error:`${t}: 同じ ENV 名が 2 回あります`};s.add(t),n.push({env:t,name:o})}return{ok:!0,bindings:n,command:o,args:e.slice(r+2)}}function resolvePortalApiBaseUrl(e){return(e.BLUELAMP_PORTAL_API_BASE_URL||e.PORTAL_URL||exports.DEFAULT_PORTAL_API_BASE_URL).replace(/\/+$/,"")}function buildVaultUrl(e,r){return`${e}/vault/${encodeURIComponent(r)}`}function composeChildEnv(e,r){const t={};for(const[r,o]of Object.entries(e))"string"==typeof o&&(t[r]=o);for(const[e,o]of Object.entries(r))t[e]=o;return t}function extractSecretValue(e){if(!e||"object"!=typeof e)return null;const r=e;return!0===r.success&&r.data&&"object"==typeof r.data&&"string"==typeof r.data.secret?r.data.secret:null}function describeVaultStatus(e){return 401===e?"未ログインかトークンが無効です(bluelamp1 などでログインし直してください)":403===e?"この秘密へのアクセス権がありません":404===e?"金庫に見つかりません":`金庫が HTTP ${e} を返しました`}function exitCodeOf(e,r,t){return"number"==typeof e?e:r&&"number"==typeof t[r]?128+t[r]:1}Object.defineProperty(exports,"__esModule",{value:!0}),exports.USAGE=exports.DEFAULT_PORTAL_API_BASE_URL=exports.ENV_NAME_PATTERN=void 0,exports.parseExecArgs=parseExecArgs,exports.resolvePortalApiBaseUrl=resolvePortalApiBaseUrl,exports.buildVaultUrl=buildVaultUrl,exports.composeChildEnv=composeChildEnv,exports.extractSecretValue=extractSecretValue,exports.describeVaultStatus=describeVaultStatus,exports.exitCodeOf=exitCodeOf,exports.runSecretExec=runSecretExec,exports.ENV_NAME_PATTERN=/^[A-Z_][A-Z0-9_]*$/,exports.DEFAULT_PORTAL_API_BASE_URL="https://api.motohi.jp/api/bluelamp",exports.USAGE='使い方: bluelamp-secret exec <ENV名>=<金庫の名前> [<ENV名>=<金庫の名前> …] -- <コマンド> [引数…]\n 金庫の値を子プロセスの環境変数にだけ入れて <コマンド> を実行し、その終了コードで終わります。\n 値は画面・引数・ログに出ません。シェルの展開が要るときは -- sh -c \'psql "$PGURL" -c "select 1"\' のように渡します。\n';const PREFIX="bluelamp-secret: ";async function runSecretExec(e,r){const t=parseExecArgs(e);if(!t.ok)return t.help?(r.stderr(exports.USAGE),0===e.length?2:0):(r.stderr(`${PREFIX}${t.error}\n${exports.USAGE}`),2);const o=r.loadToken();if(!o)return r.stderr(`${PREFIX}未ログインです(bluelamp1 などでログインしてから撃ってください)\n`),1;const n=resolvePortalApiBaseUrl(r.env),s={};for(const{env:e,name:c}of t.bindings){let t,l;try{t=await r.fetch(buildVaultUrl(n,c),{method:"GET",headers:{"X-CLI-Token":o,Accept:"application/json","User-Agent":"bluelamp-secret/1"}})}catch{return r.stderr(`${PREFIX}${e}=${c}: ポータルに届きませんでした\n`),1}if(!t.ok)return r.stderr(`${PREFIX}${e}=${c}: ${describeVaultStatus(t.status)}\n`),1;try{l=await t.json()}catch{return r.stderr(`${PREFIX}${e}=${c}: 金庫の応答を読めませんでした\n`),1}const a=extractSecretValue(l);if(null===a)return r.stderr(`${PREFIX}${e}=${c}: 金庫の応答に値がありませんでした\n`),1;s[e]=a}const c=composeChildEnv(r.env,s);return new Promise(e=>{let o=!1,n=()=>{};const s=r=>{o||(o=!0,n(),e(r))};let l;try{l=r.spawn(t.command,t.args,{stdio:"inherit",shell:!1,env:c})}catch(e){const o=e?.code;return r.stderr(`${PREFIX}コマンドを起動できませんでした: ${t.command}${o?`(${o})`:""}\n`),void s(127)}r.onParentSignal&&(n=r.onParentSignal(e=>{try{l.kill(e)}catch{}})),l.on("error",e=>{r.stderr(`${PREFIX}コマンドを起動できませんでした: ${t.command}${e?.code?`(${e.code})`:""}\n`),s(127)}),l.on("exit",(e,t)=>s(exitCodeOf(e,t,r.signalNumbers)))})}
|