multi-workspace-for-notion 0.1.0 → 0.2.0
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.
Potentially problematic release.
This version of multi-workspace-for-notion might be problematic. Click here for more details.
- package/README.ko.md +32 -33
- package/README.md +32 -33
- package/package.json +3 -2
- package/plugin/server.mjs +445 -465
package/plugin/server.mjs
CHANGED
|
@@ -15609,34 +15609,180 @@ var StdioServerTransport = class {
|
|
|
15609
15609
|
};
|
|
15610
15610
|
|
|
15611
15611
|
// src/config.ts
|
|
15612
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
15612
|
+
import { mkdir as mkdir2, readFile, rename, rm as rm2, writeFile } from "node:fs/promises";
|
|
15613
15613
|
import { homedir } from "node:os";
|
|
15614
|
+
import { join as join2 } from "node:path";
|
|
15615
|
+
|
|
15616
|
+
// src/keychain.ts
|
|
15617
|
+
import { randomBytes } from "node:crypto";
|
|
15618
|
+
import { spawn } from "node:child_process";
|
|
15619
|
+
var SERVICE = "multi-workspace-for-notion";
|
|
15620
|
+
var CHUNK_CHARS = 3e3;
|
|
15621
|
+
var keychain = {
|
|
15622
|
+
async get(account) {
|
|
15623
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15624
|
+
const manifest = await readItem(account);
|
|
15625
|
+
if (manifest === void 0) return void 0;
|
|
15626
|
+
const { generation, count } = parseManifest(manifest);
|
|
15627
|
+
const chunks = [];
|
|
15628
|
+
for (let i = 0; i < count; i++) {
|
|
15629
|
+
const chunk = await readItem(chunkAccount(account, generation, i));
|
|
15630
|
+
if (chunk === void 0) break;
|
|
15631
|
+
chunks.push(chunk);
|
|
15632
|
+
}
|
|
15633
|
+
if (chunks.length === count) return Buffer.from(chunks.join(""), "base64").toString("utf8");
|
|
15634
|
+
}
|
|
15635
|
+
throw new Error(`\uD0A4\uCCB4\uC778 \uAC12\uC774 \uC77C\uBD80 \uC5C6\uC2B5\uB2C8\uB2E4: ${account}`);
|
|
15636
|
+
},
|
|
15637
|
+
async set(account, value) {
|
|
15638
|
+
assertSafeAccount(account);
|
|
15639
|
+
const previous = await readItem(account);
|
|
15640
|
+
const encoded = Buffer.from(value, "utf8").toString("base64");
|
|
15641
|
+
const generation = randomBytes(4).toString("hex");
|
|
15642
|
+
const count = Math.max(1, Math.ceil(encoded.length / CHUNK_CHARS));
|
|
15643
|
+
for (let i = 0; i < count; i++) {
|
|
15644
|
+
await writeItem(chunkAccount(account, generation, i), encoded.slice(i * CHUNK_CHARS, (i + 1) * CHUNK_CHARS));
|
|
15645
|
+
}
|
|
15646
|
+
await writeItem(account, `${generation}:${count}`);
|
|
15647
|
+
if (previous !== void 0) await deleteChunks(account, previous);
|
|
15648
|
+
},
|
|
15649
|
+
async delete(account) {
|
|
15650
|
+
const manifest = await readItem(account);
|
|
15651
|
+
if (manifest === void 0) return;
|
|
15652
|
+
await deleteItem(account);
|
|
15653
|
+
await deleteChunks(account, manifest);
|
|
15654
|
+
}
|
|
15655
|
+
};
|
|
15656
|
+
function parseManifest(manifest) {
|
|
15657
|
+
const match = /^([0-9a-f]+):(\d+)$/.exec(manifest);
|
|
15658
|
+
if (!match) throw new Error(`\uD0A4\uCCB4\uC778 \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${manifest.slice(0, 20)}`);
|
|
15659
|
+
return { generation: match[1], count: Number(match[2]) };
|
|
15660
|
+
}
|
|
15661
|
+
async function deleteChunks(account, manifest) {
|
|
15662
|
+
const { generation, count } = parseManifest(manifest);
|
|
15663
|
+
for (let i = 0; i < count; i++) await deleteItem(chunkAccount(account, generation, i));
|
|
15664
|
+
}
|
|
15665
|
+
var chunkAccount = (account, generation, index) => `${account}.${generation}.${index}`;
|
|
15666
|
+
function assertSafeAccount(account) {
|
|
15667
|
+
if (!/^[A-Za-z0-9:_.-]+$/.test(account)) throw new Error(`\uD0A4\uCCB4\uC778 \uACC4\uC815 \uC774\uB984\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uBB38\uC790: ${account}`);
|
|
15668
|
+
}
|
|
15669
|
+
async function readItem(account) {
|
|
15670
|
+
const { code, stdout, stderr } = await run(["find-generic-password", "-s", SERVICE, "-a", account, "-w"]);
|
|
15671
|
+
if (code === 44) return void 0;
|
|
15672
|
+
if (code !== 0) throw new Error(`\uD0A4\uCCB4\uC778 \uC77D\uAE30 \uC2E4\uD328 (${code}): ${stderr.trim()}`);
|
|
15673
|
+
return stdout.trim();
|
|
15674
|
+
}
|
|
15675
|
+
async function writeItem(account, value) {
|
|
15676
|
+
const { stdout, stderr } = await run(["-i"], `add-generic-password -U -s ${SERVICE} -a ${account} -w ${value}
|
|
15677
|
+
`);
|
|
15678
|
+
if (await readItem(account) !== value) {
|
|
15679
|
+
throw new Error(`\uD0A4\uCCB4\uC778 \uC4F0\uAE30 \uC2E4\uD328: ${(stderr || stdout).trim()}`);
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
async function deleteItem(account) {
|
|
15683
|
+
const { code, stderr } = await run(["delete-generic-password", "-s", SERVICE, "-a", account]);
|
|
15684
|
+
if (code !== 0 && code !== 44) throw new Error(`\uD0A4\uCCB4\uC778 \uC0AD\uC81C \uC2E4\uD328 (${code}): ${stderr.trim()}`);
|
|
15685
|
+
}
|
|
15686
|
+
function run(args2, input) {
|
|
15687
|
+
return new Promise((resolve, reject) => {
|
|
15688
|
+
const child = spawn("/usr/bin/security", args2, { stdio: ["pipe", "pipe", "pipe"] });
|
|
15689
|
+
let stdout = "";
|
|
15690
|
+
let stderr = "";
|
|
15691
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
15692
|
+
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
15693
|
+
child.on("error", reject);
|
|
15694
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
15695
|
+
child.stdin.end(input ?? "");
|
|
15696
|
+
});
|
|
15697
|
+
}
|
|
15698
|
+
|
|
15699
|
+
// src/lock.ts
|
|
15700
|
+
import { mkdir, rm, stat } from "node:fs/promises";
|
|
15614
15701
|
import { join } from "node:path";
|
|
15702
|
+
var STALE_MS = 1e4;
|
|
15703
|
+
var RETRY_MS = 20;
|
|
15704
|
+
async function withFileLock(name, fn) {
|
|
15705
|
+
const dir = join(configDir(), "locks");
|
|
15706
|
+
const path = join(dir, `${name.replace(/[^A-Za-z0-9_.-]/g, "_")}.lock`);
|
|
15707
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
15708
|
+
for (; ; ) {
|
|
15709
|
+
try {
|
|
15710
|
+
await mkdir(path);
|
|
15711
|
+
break;
|
|
15712
|
+
} catch (error2) {
|
|
15713
|
+
if (error2.code !== "EEXIST") throw error2;
|
|
15714
|
+
const age = await stat(path).then((s) => Date.now() - s.mtimeMs, () => 0);
|
|
15715
|
+
if (age > STALE_MS) await rm(path, { recursive: true, force: true });
|
|
15716
|
+
else await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
|
|
15717
|
+
}
|
|
15718
|
+
}
|
|
15719
|
+
try {
|
|
15720
|
+
return await fn();
|
|
15721
|
+
} finally {
|
|
15722
|
+
await rm(path, { recursive: true, force: true });
|
|
15723
|
+
}
|
|
15724
|
+
}
|
|
15725
|
+
|
|
15726
|
+
// src/config.ts
|
|
15615
15727
|
function configDir() {
|
|
15616
|
-
return process.env.MWN_CONFIG_DIR ??
|
|
15728
|
+
return process.env.MWN_CONFIG_DIR ?? join2(homedir(), ".config", "multi-workspace-for-notion");
|
|
15729
|
+
}
|
|
15730
|
+
var credentialAccount = (workspaceId) => `workspace:${workspaceId}`;
|
|
15731
|
+
var cachePath = (workspaceId) => join2(configDir(), "cache", `catalog-${workspaceId}.json`);
|
|
15732
|
+
var configPath = () => join2(configDir(), "config.json");
|
|
15733
|
+
function uniqueName(name, workspaceId, others) {
|
|
15734
|
+
return others.some((w) => w.name === name) ? `${name} (${workspaceId.slice(0, 8)})` : name;
|
|
15617
15735
|
}
|
|
15618
|
-
var configPath = () => join(configDir(), "config.json");
|
|
15619
15736
|
async function loadWorkspaces() {
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
if (error2.code === "ENOENT") return [];
|
|
15625
|
-
throw error2;
|
|
15626
|
-
}
|
|
15737
|
+
const file = await readConfig();
|
|
15738
|
+
if (!file) return [];
|
|
15739
|
+
if (file.version === 2) return file.workspaces;
|
|
15740
|
+
return withFileLock("config", migrateIfNeeded);
|
|
15627
15741
|
}
|
|
15628
15742
|
async function saveWorkspaces(workspaces) {
|
|
15629
|
-
await
|
|
15743
|
+
await mkdir2(configDir(), { recursive: true, mode: 448 });
|
|
15630
15744
|
const tmp = `${configPath()}.${process.pid}.tmp`;
|
|
15631
|
-
const data = { version:
|
|
15745
|
+
const data = { version: 2, workspaces };
|
|
15632
15746
|
await writeFile(tmp, `${JSON.stringify(data, null, 2)}
|
|
15633
15747
|
`, { mode: 384 });
|
|
15634
15748
|
await rename(tmp, configPath());
|
|
15635
15749
|
}
|
|
15750
|
+
async function updateWorkspaces(change) {
|
|
15751
|
+
await withFileLock("config", async () => saveWorkspaces(change(await migrateIfNeeded())));
|
|
15752
|
+
}
|
|
15753
|
+
async function readConfig() {
|
|
15754
|
+
try {
|
|
15755
|
+
return JSON.parse(await readFile(configPath(), "utf8"));
|
|
15756
|
+
} catch (error2) {
|
|
15757
|
+
if (error2.code === "ENOENT") return void 0;
|
|
15758
|
+
throw error2;
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
15761
|
+
async function migrateIfNeeded() {
|
|
15762
|
+
const file = await readConfig();
|
|
15763
|
+
if (!file) return [];
|
|
15764
|
+
if (file.version === 2) return file.workspaces;
|
|
15765
|
+
const migrated = [];
|
|
15766
|
+
for (const { alias, notionWorkspaceName, ...rest } of file.workspaces) {
|
|
15767
|
+
const from = `workspace:${alias}`;
|
|
15768
|
+
const to = credentialAccount(rest.notionWorkspaceId);
|
|
15769
|
+
await withFileLock(from, async () => {
|
|
15770
|
+
const credentials = await keychain.get(from);
|
|
15771
|
+
if (credentials !== void 0) {
|
|
15772
|
+
await keychain.set(to, credentials);
|
|
15773
|
+
await keychain.delete(from);
|
|
15774
|
+
}
|
|
15775
|
+
});
|
|
15776
|
+
await rm2(join2(configDir(), "cache", `catalog-${alias}.json`), { force: true });
|
|
15777
|
+
migrated.push({ ...rest, name: uniqueName(notionWorkspaceName, rest.notionWorkspaceId, migrated) });
|
|
15778
|
+
}
|
|
15779
|
+
await saveWorkspaces(migrated);
|
|
15780
|
+
return migrated;
|
|
15781
|
+
}
|
|
15636
15782
|
|
|
15637
15783
|
// src/connect.ts
|
|
15638
15784
|
import { spawn as spawn2 } from "node:child_process";
|
|
15639
|
-
import { rm as
|
|
15785
|
+
import { rm as rm3 } from "node:fs/promises";
|
|
15640
15786
|
|
|
15641
15787
|
// node_modules/pkce-challenge/dist/index.node.js
|
|
15642
15788
|
var crypto;
|
|
@@ -16635,6 +16781,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
16635
16781
|
import { createServer } from "node:http";
|
|
16636
16782
|
async function startCallbackServer() {
|
|
16637
16783
|
let deliver;
|
|
16784
|
+
let expectedState;
|
|
16638
16785
|
const received = new Promise((resolve) => deliver = resolve);
|
|
16639
16786
|
const handler = (req, res) => {
|
|
16640
16787
|
const url2 = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -16645,12 +16792,18 @@ async function startCallbackServer() {
|
|
|
16645
16792
|
}
|
|
16646
16793
|
const error2 = url2.searchParams.get("error");
|
|
16647
16794
|
const code = url2.searchParams.get("code");
|
|
16795
|
+
const state = url2.searchParams.get("state");
|
|
16796
|
+
if (!error2 && !code || expectedState === void 0 || state !== expectedState) {
|
|
16797
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
16798
|
+
res.end(`<!doctype html><meta charset="utf-8"><p style="font-family:system-ui">\uC798\uBABB\uB41C \uCF5C\uBC31 \uC694\uCCAD\uC785\uB2C8\uB2E4.</p>`);
|
|
16799
|
+
return;
|
|
16800
|
+
}
|
|
16648
16801
|
const ok = !error2 && code;
|
|
16649
16802
|
res.writeHead(ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
16650
16803
|
res.end(
|
|
16651
16804
|
`<!doctype html><meta charset="utf-8"><title>Multi-Workspace for Notion</title><p style="font-family:system-ui;margin:20vh auto;max-width:420px">` + (ok ? "\uC778\uC99D\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4. \uC774 \uCC3D\uC744 \uB2EB\uACE0 \uD130\uBBF8\uB110\uB85C \uB3CC\uC544\uAC00\uC138\uC694." : "\uC778\uC99D\uC774 \uCDE8\uC18C\uB418\uC5C8\uAC70\uB098 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD130\uBBF8\uB110\uC744 \uD655\uC778\uD558\uC138\uC694.") + `</p>`
|
|
16652
16805
|
);
|
|
16653
|
-
deliver?.(ok ? { code, state
|
|
16806
|
+
deliver?.(ok ? { code, state } : { error: error2 ?? "code\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4" });
|
|
16654
16807
|
deliver = void 0;
|
|
16655
16808
|
};
|
|
16656
16809
|
const servers = [createServer(handler)];
|
|
@@ -16660,7 +16813,8 @@ async function startCallbackServer() {
|
|
|
16660
16813
|
if (await listen(ipv62, port, "::1").then(() => true, () => false)) servers.push(ipv62);
|
|
16661
16814
|
return {
|
|
16662
16815
|
redirectUrl: `http://localhost:${port}/callback`,
|
|
16663
|
-
async waitForCode(
|
|
16816
|
+
async waitForCode(expected, timeoutMs) {
|
|
16817
|
+
expectedState = expected;
|
|
16664
16818
|
let timer;
|
|
16665
16819
|
const timeout = new Promise((_, reject) => {
|
|
16666
16820
|
timer = setTimeout(() => reject(new Error("\uC778\uC99D \uB300\uAE30 \uC2DC\uAC04\uC774 \uC9C0\uB0AC\uC2B5\uB2C8\uB2E4.")), timeoutMs);
|
|
@@ -16668,7 +16822,6 @@ async function startCallbackServer() {
|
|
|
16668
16822
|
try {
|
|
16669
16823
|
const result = await Promise.race([received, timeout]);
|
|
16670
16824
|
if ("error" in result) throw new Error(`Notion \uC778\uC99D \uC2E4\uD328: ${result.error}`);
|
|
16671
|
-
if (result.state !== expectedState) throw new Error("state\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC544 \uC778\uC99D \uC751\uB2F5\uC744 \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4.");
|
|
16672
16825
|
return result.code;
|
|
16673
16826
|
} finally {
|
|
16674
16827
|
clearTimeout(timer);
|
|
@@ -16689,89 +16842,6 @@ function listen(server, port, host) {
|
|
|
16689
16842
|
});
|
|
16690
16843
|
}
|
|
16691
16844
|
|
|
16692
|
-
// src/keychain.ts
|
|
16693
|
-
import { randomBytes } from "node:crypto";
|
|
16694
|
-
import { spawn } from "node:child_process";
|
|
16695
|
-
var SERVICE = "multi-workspace-for-notion";
|
|
16696
|
-
var CHUNK_CHARS = 3e3;
|
|
16697
|
-
var keychain = {
|
|
16698
|
-
async get(account) {
|
|
16699
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
16700
|
-
const manifest = await readItem(account);
|
|
16701
|
-
if (manifest === void 0) return void 0;
|
|
16702
|
-
const { generation, count } = parseManifest(manifest);
|
|
16703
|
-
const chunks = [];
|
|
16704
|
-
for (let i = 0; i < count; i++) {
|
|
16705
|
-
const chunk = await readItem(chunkAccount(account, generation, i));
|
|
16706
|
-
if (chunk === void 0) break;
|
|
16707
|
-
chunks.push(chunk);
|
|
16708
|
-
}
|
|
16709
|
-
if (chunks.length === count) return Buffer.from(chunks.join(""), "base64").toString("utf8");
|
|
16710
|
-
}
|
|
16711
|
-
throw new Error(`\uD0A4\uCCB4\uC778 \uAC12\uC774 \uC77C\uBD80 \uC5C6\uC2B5\uB2C8\uB2E4: ${account}`);
|
|
16712
|
-
},
|
|
16713
|
-
async set(account, value) {
|
|
16714
|
-
assertSafeAccount(account);
|
|
16715
|
-
const previous = await readItem(account);
|
|
16716
|
-
const encoded = Buffer.from(value, "utf8").toString("base64");
|
|
16717
|
-
const generation = randomBytes(4).toString("hex");
|
|
16718
|
-
const count = Math.max(1, Math.ceil(encoded.length / CHUNK_CHARS));
|
|
16719
|
-
for (let i = 0; i < count; i++) {
|
|
16720
|
-
await writeItem(chunkAccount(account, generation, i), encoded.slice(i * CHUNK_CHARS, (i + 1) * CHUNK_CHARS));
|
|
16721
|
-
}
|
|
16722
|
-
await writeItem(account, `${generation}:${count}`);
|
|
16723
|
-
if (previous !== void 0) await deleteChunks(account, previous);
|
|
16724
|
-
},
|
|
16725
|
-
async delete(account) {
|
|
16726
|
-
const manifest = await readItem(account);
|
|
16727
|
-
if (manifest === void 0) return;
|
|
16728
|
-
await deleteItem(account);
|
|
16729
|
-
await deleteChunks(account, manifest);
|
|
16730
|
-
}
|
|
16731
|
-
};
|
|
16732
|
-
function parseManifest(manifest) {
|
|
16733
|
-
const match = /^([0-9a-f]+):(\d+)$/.exec(manifest);
|
|
16734
|
-
if (!match) throw new Error(`\uD0A4\uCCB4\uC778 \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${manifest.slice(0, 20)}`);
|
|
16735
|
-
return { generation: match[1], count: Number(match[2]) };
|
|
16736
|
-
}
|
|
16737
|
-
async function deleteChunks(account, manifest) {
|
|
16738
|
-
const { generation, count } = parseManifest(manifest);
|
|
16739
|
-
for (let i = 0; i < count; i++) await deleteItem(chunkAccount(account, generation, i));
|
|
16740
|
-
}
|
|
16741
|
-
var chunkAccount = (account, generation, index) => `${account}.${generation}.${index}`;
|
|
16742
|
-
function assertSafeAccount(account) {
|
|
16743
|
-
if (!/^[A-Za-z0-9:_.-]+$/.test(account)) throw new Error(`\uD0A4\uCCB4\uC778 \uACC4\uC815 \uC774\uB984\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uBB38\uC790: ${account}`);
|
|
16744
|
-
}
|
|
16745
|
-
async function readItem(account) {
|
|
16746
|
-
const { code, stdout, stderr } = await run(["find-generic-password", "-s", SERVICE, "-a", account, "-w"]);
|
|
16747
|
-
if (code === 44) return void 0;
|
|
16748
|
-
if (code !== 0) throw new Error(`\uD0A4\uCCB4\uC778 \uC77D\uAE30 \uC2E4\uD328 (${code}): ${stderr.trim()}`);
|
|
16749
|
-
return stdout.trim();
|
|
16750
|
-
}
|
|
16751
|
-
async function writeItem(account, value) {
|
|
16752
|
-
const { stdout, stderr } = await run(["-i"], `add-generic-password -U -s ${SERVICE} -a ${account} -w ${value}
|
|
16753
|
-
`);
|
|
16754
|
-
if (await readItem(account) !== value) {
|
|
16755
|
-
throw new Error(`\uD0A4\uCCB4\uC778 \uC4F0\uAE30 \uC2E4\uD328: ${(stderr || stdout).trim()}`);
|
|
16756
|
-
}
|
|
16757
|
-
}
|
|
16758
|
-
async function deleteItem(account) {
|
|
16759
|
-
const { code, stderr } = await run(["delete-generic-password", "-s", SERVICE, "-a", account]);
|
|
16760
|
-
if (code !== 0 && code !== 44) throw new Error(`\uD0A4\uCCB4\uC778 \uC0AD\uC81C \uC2E4\uD328 (${code}): ${stderr.trim()}`);
|
|
16761
|
-
}
|
|
16762
|
-
function run(args2, input) {
|
|
16763
|
-
return new Promise((resolve, reject) => {
|
|
16764
|
-
const child = spawn("/usr/bin/security", args2, { stdio: ["pipe", "pipe", "pipe"] });
|
|
16765
|
-
let stdout = "";
|
|
16766
|
-
let stderr = "";
|
|
16767
|
-
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
16768
|
-
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
16769
|
-
child.on("error", reject);
|
|
16770
|
-
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
16771
|
-
child.stdin.end(input ?? "");
|
|
16772
|
-
});
|
|
16773
|
-
}
|
|
16774
|
-
|
|
16775
16845
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
16776
16846
|
function isZ4Schema(s) {
|
|
16777
16847
|
const schema = s;
|
|
@@ -19238,7 +19308,7 @@ var StreamableHTTPClientTransport = class {
|
|
|
19238
19308
|
|
|
19239
19309
|
// src/meta.ts
|
|
19240
19310
|
var SERVER_NAME = "multi-workspace-for-notion";
|
|
19241
|
-
var SERVER_VERSION = "0.
|
|
19311
|
+
var SERVER_VERSION = "0.2.0";
|
|
19242
19312
|
|
|
19243
19313
|
// src/notion.ts
|
|
19244
19314
|
var NOTION_MCP_URL = new URL("https://mcp.notion.com/mcp");
|
|
@@ -19268,7 +19338,7 @@ var NotionSession = class _NotionSession {
|
|
|
19268
19338
|
async callTool(name, args2) {
|
|
19269
19339
|
return await this.client.callTool({ name, arguments: args2 });
|
|
19270
19340
|
}
|
|
19271
|
-
/** notion-get-self
|
|
19341
|
+
/** Asks notion-get-self which workspace this token actually belongs to. */
|
|
19272
19342
|
async getSelf() {
|
|
19273
19343
|
const result = await this.client.callTool({ name: "notion-get-self", arguments: {} });
|
|
19274
19344
|
const text2 = result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
|
|
@@ -19280,66 +19350,15 @@ var NotionSession = class _NotionSession {
|
|
|
19280
19350
|
}
|
|
19281
19351
|
};
|
|
19282
19352
|
function parseSelf(text2) {
|
|
19283
|
-
const
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
if (!id || !name) throw new Error(`notion-get-self \uC751\uB2F5\uC5D0\uC11C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${text2.slice(0, 300)}`);
|
|
19287
|
-
return { workspaceId: id, workspaceName: name };
|
|
19288
|
-
}
|
|
19289
|
-
function findString(value, keys) {
|
|
19290
|
-
if (!value || typeof value !== "object") return void 0;
|
|
19291
|
-
for (const [key, child] of Object.entries(value)) {
|
|
19292
|
-
if (keys.includes(key) && typeof child === "string") return child;
|
|
19293
|
-
const nested = findString(child, keys);
|
|
19294
|
-
if (nested) return nested;
|
|
19353
|
+
const workspace = JSON.parse(text2).workspace;
|
|
19354
|
+
if (typeof workspace?.id !== "string" || typeof workspace.name !== "string") {
|
|
19355
|
+
throw new Error(`notion-get-self \uC751\uB2F5\uC5D0\uC11C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${text2.slice(0, 300)}`);
|
|
19295
19356
|
}
|
|
19296
|
-
return
|
|
19297
|
-
}
|
|
19298
|
-
function findNested(value, parentKey, keys) {
|
|
19299
|
-
if (!value || typeof value !== "object") return void 0;
|
|
19300
|
-
for (const [key, child] of Object.entries(value)) {
|
|
19301
|
-
if (key === parentKey) {
|
|
19302
|
-
const found = findString(child, keys);
|
|
19303
|
-
if (found) return found;
|
|
19304
|
-
}
|
|
19305
|
-
const nested = findNested(child, parentKey, keys);
|
|
19306
|
-
if (nested) return nested;
|
|
19307
|
-
}
|
|
19308
|
-
return void 0;
|
|
19357
|
+
return { workspaceId: workspace.id, workspaceName: workspace.name };
|
|
19309
19358
|
}
|
|
19310
19359
|
|
|
19311
19360
|
// src/oauth.ts
|
|
19312
19361
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
19313
|
-
|
|
19314
|
-
// src/lock.ts
|
|
19315
|
-
import { mkdir as mkdir2, rm, stat } from "node:fs/promises";
|
|
19316
|
-
import { join as join2 } from "node:path";
|
|
19317
|
-
var STALE_MS = 1e4;
|
|
19318
|
-
var RETRY_MS = 20;
|
|
19319
|
-
async function withFileLock(name, fn) {
|
|
19320
|
-
const dir = join2(configDir(), "locks");
|
|
19321
|
-
const path = join2(dir, `${name.replace(/[^A-Za-z0-9_.-]/g, "_")}.lock`);
|
|
19322
|
-
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
19323
|
-
for (; ; ) {
|
|
19324
|
-
try {
|
|
19325
|
-
await mkdir2(path);
|
|
19326
|
-
break;
|
|
19327
|
-
} catch (error2) {
|
|
19328
|
-
if (error2.code !== "EEXIST") throw error2;
|
|
19329
|
-
const age = await stat(path).then((s) => Date.now() - s.mtimeMs, () => 0);
|
|
19330
|
-
if (age > STALE_MS) await rm(path, { recursive: true, force: true });
|
|
19331
|
-
else await new Promise((resolve) => setTimeout(resolve, RETRY_MS));
|
|
19332
|
-
}
|
|
19333
|
-
}
|
|
19334
|
-
try {
|
|
19335
|
-
return await fn();
|
|
19336
|
-
} finally {
|
|
19337
|
-
await rm(path, { recursive: true, force: true });
|
|
19338
|
-
}
|
|
19339
|
-
}
|
|
19340
|
-
|
|
19341
|
-
// src/oauth.ts
|
|
19342
|
-
var credentialAccount = (alias) => `workspace:${alias}`;
|
|
19343
19362
|
var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
19344
19363
|
constructor(account, stored, onRedirect) {
|
|
19345
19364
|
this.account = account;
|
|
@@ -19351,17 +19370,20 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
|
19351
19370
|
onRedirect;
|
|
19352
19371
|
verifier;
|
|
19353
19372
|
expectedState;
|
|
19354
|
-
/**
|
|
19373
|
+
/** The keychain value was seen or saved. If it disappears later, the workspace was disconnected. */
|
|
19355
19374
|
persisted = false;
|
|
19356
|
-
/**
|
|
19357
|
-
|
|
19358
|
-
|
|
19375
|
+
/**
|
|
19376
|
+
* For a new connection. The workspace (and so its ID) is only known after sign-in, so this saves under a
|
|
19377
|
+
* temporary account that the caller moves to `workspace:<id>` with moveTo(). redirectUrl is the callback server's address.
|
|
19378
|
+
*/
|
|
19379
|
+
static forNewConnection(redirectUrl, onRedirect) {
|
|
19380
|
+
return new _WorkspaceAuthProvider(`pending:${randomBytes2(8).toString("hex")}`, { redirectUrl }, onRedirect);
|
|
19359
19381
|
}
|
|
19360
|
-
/**
|
|
19361
|
-
static async forExisting(
|
|
19362
|
-
const raw = await keychain.get(credentialAccount(
|
|
19382
|
+
/** For a connected workspace. Undefined when nothing is stored. */
|
|
19383
|
+
static async forExisting(workspaceId) {
|
|
19384
|
+
const raw = await keychain.get(credentialAccount(workspaceId));
|
|
19363
19385
|
if (raw === void 0) return void 0;
|
|
19364
|
-
const provider = new _WorkspaceAuthProvider(credentialAccount(
|
|
19386
|
+
const provider = new _WorkspaceAuthProvider(credentialAccount(workspaceId), JSON.parse(raw), void 0);
|
|
19365
19387
|
provider.persisted = true;
|
|
19366
19388
|
return provider;
|
|
19367
19389
|
}
|
|
@@ -19381,7 +19403,7 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
|
19381
19403
|
this.expectedState = randomBytes2(24).toString("base64url");
|
|
19382
19404
|
return this.expectedState;
|
|
19383
19405
|
}
|
|
19384
|
-
/**
|
|
19406
|
+
/** The state the callback must echo back. Set once auth() has started an authorization. */
|
|
19385
19407
|
get pendingState() {
|
|
19386
19408
|
return this.expectedState;
|
|
19387
19409
|
}
|
|
@@ -19392,18 +19414,15 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
|
19392
19414
|
await this.update((s) => s.clientInformation = clientInformation);
|
|
19393
19415
|
}
|
|
19394
19416
|
/**
|
|
19395
|
-
* Claude
|
|
19396
|
-
*
|
|
19417
|
+
* Each Claude session runs its own server process, so re-read the keychain every time to pick up tokens another
|
|
19418
|
+
* process refreshed. Refresh tokens are single-use: a stale one from memory fails with invalid_grant.
|
|
19397
19419
|
*/
|
|
19398
19420
|
async tokens() {
|
|
19399
19421
|
await this.reload();
|
|
19400
19422
|
return this.stored.tokens;
|
|
19401
19423
|
}
|
|
19402
19424
|
async saveTokens(tokens) {
|
|
19403
|
-
await this.update((s) =>
|
|
19404
|
-
s.tokens = tokens;
|
|
19405
|
-
s.tokensSavedAt = Date.now();
|
|
19406
|
-
});
|
|
19425
|
+
await this.update((s) => s.tokens = tokens);
|
|
19407
19426
|
}
|
|
19408
19427
|
async redirectToAuthorization(authorizationUrl) {
|
|
19409
19428
|
if (!this.onRedirect) {
|
|
@@ -19411,7 +19430,7 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
|
19411
19430
|
}
|
|
19412
19431
|
await this.onRedirect(authorizationUrl);
|
|
19413
19432
|
}
|
|
19414
|
-
// PKCE verifier
|
|
19433
|
+
// The PKCE verifier lives only for one authorization, so it stays in memory.
|
|
19415
19434
|
saveCodeVerifier(codeVerifier) {
|
|
19416
19435
|
this.verifier = codeVerifier;
|
|
19417
19436
|
}
|
|
@@ -19431,30 +19450,36 @@ var WorkspaceAuthProvider = class _WorkspaceAuthProvider {
|
|
|
19431
19450
|
if (scope === "verifier") return;
|
|
19432
19451
|
await this.update((s) => {
|
|
19433
19452
|
if (scope === "all" || scope === "client") delete s.clientInformation;
|
|
19434
|
-
if (scope === "all")
|
|
19435
|
-
delete s.tokens;
|
|
19436
|
-
delete s.tokensSavedAt;
|
|
19437
|
-
}
|
|
19453
|
+
if (scope === "all") delete s.tokens;
|
|
19438
19454
|
if (scope === "all" || scope === "discovery") delete s.discoveryState;
|
|
19439
19455
|
});
|
|
19440
19456
|
}
|
|
19441
19457
|
async deleteStored() {
|
|
19442
19458
|
await keychain.delete(this.account);
|
|
19443
19459
|
}
|
|
19460
|
+
/** Moves the stored credentials to another keychain account (a new connection's final `workspace:<id>`). */
|
|
19461
|
+
async moveTo(account) {
|
|
19462
|
+
const raw = await keychain.get(this.account);
|
|
19463
|
+
if (raw === void 0) throw new Error("\uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
19464
|
+
await keychain.set(account, raw);
|
|
19465
|
+
await keychain.delete(this.account);
|
|
19466
|
+
this.account = account;
|
|
19467
|
+
}
|
|
19444
19468
|
async reload() {
|
|
19445
19469
|
const raw = await keychain.get(this.account);
|
|
19446
19470
|
if (raw !== void 0) this.stored = JSON.parse(raw);
|
|
19447
19471
|
}
|
|
19448
19472
|
/**
|
|
19449
|
-
*
|
|
19450
|
-
*
|
|
19451
|
-
*
|
|
19473
|
+
* Writing the in-memory copy wholesale would overwrite newer tokens another process saved meanwhile.
|
|
19474
|
+
* So take the lock, re-read the keychain and change only the given fields.
|
|
19475
|
+
* If the value vanished because another process disconnected the workspace, write nothing,
|
|
19476
|
+
* so deleted tokens stay deleted.
|
|
19452
19477
|
*/
|
|
19453
19478
|
async update(change) {
|
|
19454
19479
|
await withFileLock(this.account, async () => {
|
|
19455
19480
|
const raw = await keychain.get(this.account);
|
|
19456
19481
|
if (raw !== void 0) this.stored = JSON.parse(raw);
|
|
19457
|
-
else if (this.persisted) throw new Error("\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \
|
|
19482
|
+
else if (this.persisted) throw new Error("\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uB2E4\uB978 \uC138\uC158\uC5D0\uC11C \uC5F0\uACB0\uC774 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8 \uB300\uD654\uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
|
|
19458
19483
|
change(this.stored);
|
|
19459
19484
|
await keychain.set(this.account, JSON.stringify(this.stored));
|
|
19460
19485
|
this.persisted = true;
|
|
@@ -19490,114 +19515,12 @@ async function revokeTokens(stored, fetchFn = fetch) {
|
|
|
19490
19515
|
}
|
|
19491
19516
|
}
|
|
19492
19517
|
|
|
19493
|
-
// src/upstream.ts
|
|
19494
|
-
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
19495
|
-
import { join as join3 } from "node:path";
|
|
19496
|
-
var UpstreamPool = class {
|
|
19497
|
-
constructor(open2 = openWorkspace) {
|
|
19498
|
-
this.open = open2;
|
|
19499
|
-
}
|
|
19500
|
-
open;
|
|
19501
|
-
sessions = /* @__PURE__ */ new Map();
|
|
19502
|
-
session(alias) {
|
|
19503
|
-
let pending = this.sessions.get(alias);
|
|
19504
|
-
if (!pending) {
|
|
19505
|
-
pending = this.open(alias);
|
|
19506
|
-
const opened = pending;
|
|
19507
|
-
opened.catch(() => {
|
|
19508
|
-
if (this.sessions.get(alias) === opened) this.sessions.delete(alias);
|
|
19509
|
-
});
|
|
19510
|
-
this.sessions.set(alias, pending);
|
|
19511
|
-
}
|
|
19512
|
-
return pending;
|
|
19513
|
-
}
|
|
19514
|
-
/**
|
|
19515
|
-
* 업스트림 MCP 세션이 만료되면 서버가 404를 돌려준다. 그 경우 요청이 처리되지 않았으므로
|
|
19516
|
-
* 세션을 새로 열어 한 번 더 보낸다. 다른 오류는 이미 처리됐을 수 있어 다시 보내지 않는다.
|
|
19517
|
-
*/
|
|
19518
|
-
async callTool(alias, name, args2) {
|
|
19519
|
-
try {
|
|
19520
|
-
return await (await this.session(alias)).callTool(name, args2);
|
|
19521
|
-
} catch (error2) {
|
|
19522
|
-
if (!(error2 instanceof StreamableHTTPError && error2.code === 404)) throw error2;
|
|
19523
|
-
await this.reset(alias);
|
|
19524
|
-
return (await this.session(alias)).callTool(name, args2);
|
|
19525
|
-
}
|
|
19526
|
-
}
|
|
19527
|
-
async reset(alias) {
|
|
19528
|
-
const pending = this.sessions.get(alias);
|
|
19529
|
-
this.sessions.delete(alias);
|
|
19530
|
-
await pending?.then((s) => s.close()).catch(() => {
|
|
19531
|
-
});
|
|
19532
|
-
}
|
|
19533
|
-
async closeAll() {
|
|
19534
|
-
await Promise.all([...this.sessions.keys()].map((alias) => this.reset(alias)));
|
|
19535
|
-
}
|
|
19536
|
-
};
|
|
19537
|
-
var LIVE_TIMEOUT_MS = 15e3;
|
|
19538
|
-
async function loadCatalogs(workspaces, pool) {
|
|
19539
|
-
const results = await Promise.all(
|
|
19540
|
-
workspaces.map(async (w) => {
|
|
19541
|
-
try {
|
|
19542
|
-
const catalog = await withTimeout(fetchLive(w.alias, pool), LIVE_TIMEOUT_MS);
|
|
19543
|
-
await writeCache(catalog);
|
|
19544
|
-
return catalog;
|
|
19545
|
-
} catch (error2) {
|
|
19546
|
-
const cached2 = await readCache(w.alias);
|
|
19547
|
-
return cached2 ?? { alias: w.alias, reason: error2.message };
|
|
19548
|
-
}
|
|
19549
|
-
})
|
|
19550
|
-
);
|
|
19551
|
-
return {
|
|
19552
|
-
catalogs: results.filter((r) => "tools" in r),
|
|
19553
|
-
failures: results.filter((r) => "reason" in r)
|
|
19554
|
-
};
|
|
19555
|
-
}
|
|
19556
|
-
async function fetchLive(alias, pool) {
|
|
19557
|
-
const session = await pool.session(alias);
|
|
19558
|
-
return {
|
|
19559
|
-
alias,
|
|
19560
|
-
tools: await session.listTools(),
|
|
19561
|
-
instructions: session.instructions,
|
|
19562
|
-
source: "live",
|
|
19563
|
-
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19564
|
-
};
|
|
19565
|
-
}
|
|
19566
|
-
var cachePath = (alias) => join3(configDir(), "cache", `catalog-${alias}.json`);
|
|
19567
|
-
async function writeCache(catalog) {
|
|
19568
|
-
await mkdir3(join3(configDir(), "cache"), { recursive: true, mode: 448 });
|
|
19569
|
-
const tmp = `${cachePath(catalog.alias)}.${process.pid}.tmp`;
|
|
19570
|
-
await writeFile2(tmp, JSON.stringify(catalog), { mode: 384 });
|
|
19571
|
-
await rename2(tmp, cachePath(catalog.alias));
|
|
19572
|
-
}
|
|
19573
|
-
async function readCache(alias) {
|
|
19574
|
-
try {
|
|
19575
|
-
const cached2 = JSON.parse(await readFile2(cachePath(alias), "utf8"));
|
|
19576
|
-
return { ...cached2, source: "cache" };
|
|
19577
|
-
} catch {
|
|
19578
|
-
return void 0;
|
|
19579
|
-
}
|
|
19580
|
-
}
|
|
19581
|
-
function withTimeout(promise, ms) {
|
|
19582
|
-
let timer;
|
|
19583
|
-
return Promise.race([
|
|
19584
|
-
promise,
|
|
19585
|
-
new Promise((_, reject) => {
|
|
19586
|
-
timer = setTimeout(() => reject(new Error(`${ms / 1e3}\uCD08 \uC548\uC5D0 \uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`)), ms);
|
|
19587
|
-
})
|
|
19588
|
-
]).finally(() => clearTimeout(timer));
|
|
19589
|
-
}
|
|
19590
|
-
|
|
19591
19518
|
// src/connect.ts
|
|
19592
|
-
var ALIAS_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
19593
19519
|
var AUTH_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
19520
|
+
var WORKSPACE_ID = /^[0-9a-f-]{32,36}$/i;
|
|
19594
19521
|
async function connectWorkspace(options) {
|
|
19595
|
-
const { alias, description } = options;
|
|
19596
|
-
assertAlias(alias);
|
|
19597
|
-
const existing = await loadWorkspaces();
|
|
19598
|
-
if (existing.some((w) => w.alias === alias)) throw new Error(`\uC774\uBBF8 \uC4F0\uACE0 \uC788\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
|
|
19599
19522
|
const callback = await startCallbackServer();
|
|
19600
|
-
const provider = WorkspaceAuthProvider.forNewConnection(
|
|
19523
|
+
const provider = WorkspaceAuthProvider.forNewConnection(callback.redirectUrl, (url2) => {
|
|
19601
19524
|
options.onAuthorizationUrl(url2);
|
|
19602
19525
|
openBrowser(url2);
|
|
19603
19526
|
});
|
|
@@ -19609,23 +19532,26 @@ async function connectWorkspace(options) {
|
|
|
19609
19532
|
}
|
|
19610
19533
|
session = await NotionSession.open(provider);
|
|
19611
19534
|
const self = await session.getSelf();
|
|
19612
|
-
|
|
19535
|
+
if (!WORKSPACE_ID.test(self.workspaceId)) throw new Error(`\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 ID\uC785\uB2C8\uB2E4: ${self.workspaceId}`);
|
|
19536
|
+
const duplicate = (await loadWorkspaces()).find((w) => w.notionWorkspaceId === self.workspaceId);
|
|
19613
19537
|
if (duplicate) {
|
|
19614
19538
|
throw new Error(
|
|
19615
|
-
`"${
|
|
19539
|
+
`"${duplicate.name}" \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 \uC774\uBBF8 \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Notion \uC778\uC99D \uD654\uBA74\uC5D0\uC11C \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB790\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
|
|
19616
19540
|
);
|
|
19617
19541
|
}
|
|
19618
19542
|
const tools = await session.listTools();
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19543
|
+
await provider.moveTo(credentialAccount(self.workspaceId));
|
|
19544
|
+
let entry;
|
|
19545
|
+
await updateWorkspaces((latest) => {
|
|
19546
|
+
entry = {
|
|
19547
|
+
name: uniqueName(self.workspaceName, self.workspaceId, latest),
|
|
19548
|
+
notionWorkspaceId: self.workspaceId,
|
|
19549
|
+
description: options.description,
|
|
19550
|
+
status: "connected",
|
|
19551
|
+
lastSuccessAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19552
|
+
};
|
|
19553
|
+
return [...latest, entry];
|
|
19554
|
+
});
|
|
19629
19555
|
return { entry, tools };
|
|
19630
19556
|
} catch (error2) {
|
|
19631
19557
|
await provider.deleteStored();
|
|
@@ -19635,54 +19561,27 @@ async function connectWorkspace(options) {
|
|
|
19635
19561
|
await session?.close();
|
|
19636
19562
|
}
|
|
19637
19563
|
}
|
|
19638
|
-
async function
|
|
19639
|
-
const
|
|
19640
|
-
if (!
|
|
19564
|
+
async function findWorkspace(name) {
|
|
19565
|
+
const entry = (await loadWorkspaces()).find((w) => w.name === name);
|
|
19566
|
+
if (!entry) throw new Error(`\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC544\uB2D9\uB2C8\uB2E4: ${name}`);
|
|
19567
|
+
return entry;
|
|
19568
|
+
}
|
|
19569
|
+
async function openWorkspace(name) {
|
|
19570
|
+
const entry = await findWorkspace(name);
|
|
19571
|
+
const provider = await WorkspaceAuthProvider.forExisting(entry.notionWorkspaceId);
|
|
19572
|
+
if (!provider) throw new Error(`"${name}"\uC758 \uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
|
|
19641
19573
|
return NotionSession.open(provider);
|
|
19642
19574
|
}
|
|
19643
|
-
async function disconnectWorkspace(
|
|
19644
|
-
const
|
|
19645
|
-
const raw = await withFileLock(credentialAccount(
|
|
19646
|
-
const found = await keychain.get(credentialAccount(
|
|
19647
|
-
await keychain.delete(credentialAccount(
|
|
19575
|
+
async function disconnectWorkspace(name) {
|
|
19576
|
+
const { notionWorkspaceId: id } = await findWorkspace(name);
|
|
19577
|
+
const raw = await withFileLock(credentialAccount(id), async () => {
|
|
19578
|
+
const found = await keychain.get(credentialAccount(id));
|
|
19579
|
+
await keychain.delete(credentialAccount(id));
|
|
19648
19580
|
return found;
|
|
19649
19581
|
});
|
|
19650
|
-
await
|
|
19651
|
-
|
|
19652
|
-
await
|
|
19653
|
-
const revoked = raw !== void 0 && await revokeTokens(JSON.parse(raw));
|
|
19654
|
-
return { removed: remaining.length !== workspaces.length || raw !== void 0, revoked };
|
|
19655
|
-
}
|
|
19656
|
-
async function renameWorkspace(alias, changes) {
|
|
19657
|
-
const workspaces = await loadWorkspaces();
|
|
19658
|
-
const entry = workspaces.find((w) => w.alias === alias);
|
|
19659
|
-
if (!entry) throw new Error(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
|
|
19660
|
-
const newAlias = changes.newAlias ?? alias;
|
|
19661
|
-
const moving = newAlias !== alias;
|
|
19662
|
-
if (moving) {
|
|
19663
|
-
assertAlias(newAlias);
|
|
19664
|
-
if (workspaces.some((w) => w.alias === newAlias)) throw new Error(`\uC774\uBBF8 \uC4F0\uACE0 \uC788\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${newAlias}`);
|
|
19665
|
-
}
|
|
19666
|
-
const updated = { ...entry, alias: newAlias, description: changes.description ?? entry.description };
|
|
19667
|
-
const save = () => saveWorkspaces(workspaces.map((w) => w.alias === alias ? updated : w));
|
|
19668
|
-
if (!moving) {
|
|
19669
|
-
await save();
|
|
19670
|
-
return updated;
|
|
19671
|
-
}
|
|
19672
|
-
await withFileLock(credentialAccount(alias), async () => {
|
|
19673
|
-
const credentials = await keychain.get(credentialAccount(alias));
|
|
19674
|
-
if (credentials === void 0) throw new Error(`"${alias}"\uC758 \uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`);
|
|
19675
|
-
await keychain.set(credentialAccount(newAlias), credentials);
|
|
19676
|
-
await save();
|
|
19677
|
-
await keychain.delete(credentialAccount(alias));
|
|
19678
|
-
});
|
|
19679
|
-
await rm2(cachePath(alias), { force: true });
|
|
19680
|
-
return updated;
|
|
19681
|
-
}
|
|
19682
|
-
function assertAlias(alias) {
|
|
19683
|
-
if (!ALIAS_PATTERN.test(alias)) {
|
|
19684
|
-
throw new Error("\uBCC4\uCE6D\uC740 \uC601\uC5B4 \uC18C\uBB38\uC790\xB7\uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4(\uCD5C\uB300 32\uC790).");
|
|
19685
|
-
}
|
|
19582
|
+
await rm3(cachePath(id), { force: true });
|
|
19583
|
+
await updateWorkspaces((workspaces) => workspaces.filter((w) => w.notionWorkspaceId !== id));
|
|
19584
|
+
return { revoked: raw !== void 0 && await revokeTokens(JSON.parse(raw)) };
|
|
19686
19585
|
}
|
|
19687
19586
|
function openBrowser(url2) {
|
|
19688
19587
|
const child = spawn2("open", [url2.href], { stdio: "ignore", detached: true });
|
|
@@ -20317,10 +20216,11 @@ ${tool.description ?? ""}` : tool.description,
|
|
|
20317
20216
|
inputSchema: {
|
|
20318
20217
|
...tool.inputSchema,
|
|
20319
20218
|
properties: {
|
|
20219
|
+
// A plain string, not an enum: Claude keeps a conversation's tool schemas even after tools/list_changed,
|
|
20220
|
+
// so an enum would go stale as soon as a workspace is connected. The server checks the name.
|
|
20320
20221
|
[WORKSPACE_PARAM]: {
|
|
20321
20222
|
type: "string",
|
|
20322
|
-
|
|
20323
|
-
description: "\uC774 \uD638\uCD9C\uC744 \uBCF4\uB0BC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D"
|
|
20223
|
+
description: `\uC774 \uD638\uCD9C\uC744 \uBCF4\uB0BC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984. \uC774 \uC11C\uBC84\uB97C \uC2DC\uC791\uD560 \uB54C \uC5F0\uACB0\uB3FC \uC788\uB358 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4: ${aliases.join(", ")}. \uC774\uD6C4 \uC0C8\uB85C \uC5F0\uACB0\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB3C4 \uBC14\uB85C \uC4F8 \uC218 \uC788\uB2E4.`
|
|
20324
20224
|
},
|
|
20325
20225
|
...tool.inputSchema.properties
|
|
20326
20226
|
},
|
|
@@ -20349,8 +20249,7 @@ async function callProxiedTool(catalog, name, args2, call) {
|
|
|
20349
20249
|
}
|
|
20350
20250
|
const others = available.filter((alias) => alias !== workspace);
|
|
20351
20251
|
if (!isNotFound(result) || others.length === 0) return result;
|
|
20352
|
-
|
|
20353
|
-
if (!readOnly) {
|
|
20252
|
+
if (!FAN_OUT_ON_NOT_FOUND.has(name)) {
|
|
20354
20253
|
return withNote(
|
|
20355
20254
|
result,
|
|
20356
20255
|
`[${workspace} \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uB2E4. \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4(${others.join(", ")})\uC758 ID\uC77C \uC218 \uC788\uB2E4. notion-fetch\uB85C \uADF8 ID\uAC00 \uC788\uB294 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uD655\uC778\uD55C \uB4A4, \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uC744 \uBC1B\uACE0 \uADF8 workspace\uB85C \uB2E4\uC2DC \uD638\uCD9C\uD55C\uB2E4.]`
|
|
@@ -20364,6 +20263,7 @@ async function callProxiedTool(catalog, name, args2, call) {
|
|
|
20364
20263
|
`[${workspace} \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uB294 \uC5C6\uC5B4\uC11C ${others[found]} \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uCC3E\uC740 \uACB0\uACFC\uB2E4. \uC774 \uB300\uC0C1\uC744 \uB2E4\uB8E8\uB294 \uB2E4\uC74C \uD638\uCD9C\uC740 workspace=${others[found]}\uB85C \uBCF4\uB0B8\uB2E4.]`
|
|
20365
20264
|
);
|
|
20366
20265
|
}
|
|
20266
|
+
var FAN_OUT_ON_NOT_FOUND = /* @__PURE__ */ new Set(["notion-fetch"]);
|
|
20367
20267
|
var isNotFound = (result) => result.isError === true && result.content.some((c) => c.type === "text" && c.text.includes("object_not_found"));
|
|
20368
20268
|
var withNote = (result, note) => ({
|
|
20369
20269
|
...result,
|
|
@@ -20412,108 +20312,103 @@ function errorResult(text2) {
|
|
|
20412
20312
|
}
|
|
20413
20313
|
|
|
20414
20314
|
// src/management.ts
|
|
20415
|
-
var
|
|
20315
|
+
var workspaceParam = { type: "string", description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984(workspace_list\uC758 name)" };
|
|
20416
20316
|
var managementTools = [
|
|
20417
20317
|
{
|
|
20418
20318
|
name: "workspace_list",
|
|
20419
|
-
description: "\uC5F0\uACB0\uB41C Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \
|
|
20319
|
+
description: "\uC5F0\uACB0\uB41C Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984, ID, \uC124\uBA85, \uC0C1\uD0DC, \uC5F0\uACB0\uC744 \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uD655\uC778\uD55C \uC2DC\uAC01\uC744 \uBCF4\uC5EC\uC900\uB2E4.",
|
|
20420
20320
|
inputSchema: { type: "object", properties: {} },
|
|
20421
20321
|
annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D", readOnlyHint: true }
|
|
20422
20322
|
},
|
|
20423
20323
|
{
|
|
20424
20324
|
name: "workspace_connect",
|
|
20425
|
-
description: "\uC0C8 Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC5F0\uACB0\uD55C\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uBE0C\uB77C\uC6B0\uC800\uC5D0 Notion \uB85C\uADF8\uC778 \uD654\uBA74\
|
|
20325
|
+
description: "\uC0C8 Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC5F0\uACB0\uD55C\uB2E4. \uCCAB \uD638\uCD9C\uC740 \uC0AC\uC6A9\uC790\uC758 \uBE0C\uB77C\uC6B0\uC800\uC5D0 Notion \uB85C\uADF8\uC778 \uD654\uBA74\uC744 \uC5F4\uACE0 \uADF8 \uB85C\uADF8\uC778 \uC8FC\uC18C(authorizationUrl)\uB97C \uBC14\uB85C \uB3CC\uB824\uC900\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uAC00 \uC548 \uC5F4\uB838\uC744 \uC218 \uC788\uC73C\uB2C8 \uC8FC\uC18C\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uC5EC\uC8FC\uACE0, \uC5F0\uACB0\uD560 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD558\uB77C\uACE0 \uD55C\uB2E4(\uCD5C\uB300 10\uBD84). \uC0AC\uC6A9\uC790\uAC00 \uC2B9\uC778\uD588\uB2E4\uACE0 \uD558\uBA74 \uB2E4\uC2DC \uD638\uCD9C\uD574 \uACB0\uACFC\uB97C \uBC1B\uB294\uB2E4. \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB294 Notion\uC5D0\uC11C\uC758 \uC774\uB984\uC73C\uB85C \uBD88\uB9AC\uACE0, \uC774 \uB300\uD654\uC5D0\uC11C \uBC14\uB85C workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uB2E4.",
|
|
20426
20326
|
inputSchema: {
|
|
20427
20327
|
type: "object",
|
|
20428
20328
|
properties: {
|
|
20429
|
-
alias: {
|
|
20430
|
-
type: "string",
|
|
20431
|
-
description: "\uC0C8 \uBCC4\uCE6D. \uC601\uC5B4 \uC18C\uBB38\uC790\uB098 \uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC601\uC5B4 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F4\uB2E4(\uCD5C\uB300 32\uC790)."
|
|
20432
|
-
},
|
|
20433
20329
|
description: { type: "string", description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uBB34\uC5C7\uC774 \uC788\uB294\uC9C0 \uC9E7\uC740 \uC124\uBA85. Claude\uAC00 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE0\uB97C \uB54C \uC4F4\uB2E4." }
|
|
20434
|
-
}
|
|
20435
|
-
required: ["alias"]
|
|
20330
|
+
}
|
|
20436
20331
|
},
|
|
20437
20332
|
annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0", readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
20438
20333
|
},
|
|
20439
20334
|
{
|
|
20440
20335
|
name: "workspace_disconnect",
|
|
20441
20336
|
description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0\uC744 \uD574\uC81C\uD55C\uB2E4. \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uC800\uC7A5\uB41C \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6B0\uACE0 Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C\uD55C\uB2E4. \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uC744 \uBC1B\uC740 \uB4A4 \uD638\uCD9C\uD55C\uB2E4.",
|
|
20442
|
-
inputSchema: { type: "object", properties: {
|
|
20337
|
+
inputSchema: { type: "object", properties: { workspace: workspaceParam }, required: ["workspace"] },
|
|
20443
20338
|
annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0 \uD574\uC81C", readOnlyHint: false, destructiveHint: true, idempotentHint: true }
|
|
20444
20339
|
},
|
|
20445
|
-
{
|
|
20446
|
-
name: "workspace_rename",
|
|
20447
|
-
description: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uBCC4\uCE6D\uC774\uB098 \uC124\uBA85\uC744 \uBC14\uAFBC\uB2E4. \uB2E4\uC2DC \uB85C\uADF8\uC778\uD558\uC9C0 \uC54A\uB294\uB2E4. \uBC14\uB010 \uBCC4\uCE6D\uC740 \uC0C8 \uB300\uD654\uBD80\uD130 workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uB2E4.",
|
|
20448
|
-
inputSchema: {
|
|
20449
|
-
type: "object",
|
|
20450
|
-
properties: {
|
|
20451
|
-
alias: { type: "string", description: "\uC9C0\uAE08 \uBCC4\uCE6D" },
|
|
20452
|
-
new_alias: {
|
|
20453
|
-
type: "string",
|
|
20454
|
-
description: "\uC0C8 \uBCC4\uCE6D. \uC601\uC5B4 \uC18C\uBB38\uC790\uB098 \uC22B\uC790\uB85C \uC2DC\uC791\uD558\uACE0 \uC601\uC5B4 \uC18C\uBB38\uC790, \uC22B\uC790, -, _\uB9CC \uC4F4\uB2E4(\uCD5C\uB300 32\uC790). \uBC14\uAFB8\uC9C0 \uC54A\uC73C\uBA74 \uC0DD\uB7B5\uD55C\uB2E4."
|
|
20455
|
-
},
|
|
20456
|
-
description: { type: "string", description: "\uC0C8 \uC124\uBA85. \uBC14\uAFB8\uC9C0 \uC54A\uC73C\uBA74 \uC0DD\uB7B5\uD55C\uB2E4." }
|
|
20457
|
-
},
|
|
20458
|
-
required: ["alias"]
|
|
20459
|
-
},
|
|
20460
|
-
annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC774\uB984 \uBC14\uAFB8\uAE30", readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
20461
|
-
},
|
|
20462
20340
|
{
|
|
20463
20341
|
name: "workspace_check",
|
|
20464
20342
|
description: "\uC800\uC7A5\uB41C \uC778\uC99D\uC73C\uB85C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uB2E4\uC2DC \uBD99\uC5B4\uC11C \uC5F0\uACB0\uC774 \uC0B4\uC544 \uC788\uB294\uC9C0, \uCC98\uC74C \uC5F0\uACB0\uD55C \uADF8 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB3C4\uAD6C \uC218\uB97C \uC54C\uB824\uC900\uB2E4. \uACB0\uACFC\uB85C \uC0C1\uD0DC\uB97C \uAC31\uC2E0\uD55C\uB2E4.",
|
|
20465
|
-
inputSchema: { type: "object", properties: {
|
|
20343
|
+
inputSchema: { type: "object", properties: { workspace: workspaceParam }, required: ["workspace"] },
|
|
20466
20344
|
annotations: { title: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0 \uD655\uC778", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
20467
20345
|
}
|
|
20468
20346
|
];
|
|
20347
|
+
var pendingConnect;
|
|
20348
|
+
async function connectInBackground(description, onChanged, connect = connectWorkspace) {
|
|
20349
|
+
if (pendingConnect?.outcome) {
|
|
20350
|
+
const { outcome } = pendingConnect;
|
|
20351
|
+
pendingConnect = void 0;
|
|
20352
|
+
return outcome;
|
|
20353
|
+
}
|
|
20354
|
+
if (pendingConnect) {
|
|
20355
|
+
return json({ status: "waiting_for_approval", authorizationUrl: pendingConnect.url, note: "\uC544\uC9C1 \uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4." });
|
|
20356
|
+
}
|
|
20357
|
+
const state = {};
|
|
20358
|
+
pendingConnect = state;
|
|
20359
|
+
let urlReady = () => {
|
|
20360
|
+
};
|
|
20361
|
+
const gotUrl = new Promise((resolve) => urlReady = resolve);
|
|
20362
|
+
const done = connect({
|
|
20363
|
+
description,
|
|
20364
|
+
onAuthorizationUrl: (url2) => {
|
|
20365
|
+
state.url = url2.href;
|
|
20366
|
+
urlReady();
|
|
20367
|
+
}
|
|
20368
|
+
}).then(
|
|
20369
|
+
async ({ entry, tools }) => {
|
|
20370
|
+
await onChanged().catch(() => {
|
|
20371
|
+
});
|
|
20372
|
+
state.outcome = json({ status: "connected", connected: entry, toolCount: tools.length });
|
|
20373
|
+
},
|
|
20374
|
+
(error2) => {
|
|
20375
|
+
state.outcome = errorResult(error2.message);
|
|
20376
|
+
}
|
|
20377
|
+
);
|
|
20378
|
+
await Promise.race([gotUrl, done]);
|
|
20379
|
+
if (state.outcome) {
|
|
20380
|
+
pendingConnect = void 0;
|
|
20381
|
+
return state.outcome;
|
|
20382
|
+
}
|
|
20383
|
+
return json({
|
|
20384
|
+
status: "waiting_for_approval",
|
|
20385
|
+
authorizationUrl: state.url,
|
|
20386
|
+
note: "\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC774 \uC8FC\uC18C\uB85C Notion\uC5D0 \uB85C\uADF8\uC778\uD574 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD55C \uB4A4, workspace_connect\uB97C \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694."
|
|
20387
|
+
});
|
|
20388
|
+
}
|
|
20469
20389
|
var text = (value) => ({ content: [{ type: "text", text: value }] });
|
|
20470
20390
|
var json = (value) => text(JSON.stringify(value, null, 2));
|
|
20471
|
-
async function callManagementTool(name, args2,
|
|
20472
|
-
const
|
|
20391
|
+
async function callManagementTool(name, args2, onChanged) {
|
|
20392
|
+
const workspace = typeof args2.workspace === "string" ? args2.workspace : "";
|
|
20473
20393
|
try {
|
|
20474
20394
|
switch (name) {
|
|
20475
20395
|
case "workspace_list":
|
|
20476
20396
|
return json(await loadWorkspaces());
|
|
20477
|
-
case "workspace_connect":
|
|
20478
|
-
|
|
20479
|
-
const { entry, tools } = await connectWorkspace({
|
|
20480
|
-
alias,
|
|
20481
|
-
description: typeof args2.description === "string" ? args2.description : "",
|
|
20482
|
-
onAuthorizationUrl: () => {
|
|
20483
|
-
}
|
|
20484
|
-
});
|
|
20485
|
-
return json({
|
|
20486
|
-
connected: entry,
|
|
20487
|
-
toolCount: tools.length,
|
|
20488
|
-
note: "\uC0C8 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 Notion \uB3C4\uAD6C\uB294 \uC0C8 \uB300\uD654\uBD80\uD130 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
20489
|
-
});
|
|
20490
|
-
}
|
|
20397
|
+
case "workspace_connect":
|
|
20398
|
+
return await connectInBackground(typeof args2.description === "string" ? args2.description : "", onChanged);
|
|
20491
20399
|
case "workspace_disconnect": {
|
|
20492
|
-
if (!
|
|
20493
|
-
const {
|
|
20494
|
-
|
|
20495
|
-
await onDisconnected(alias);
|
|
20400
|
+
if (!workspace) return errorResult("workspace\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
20401
|
+
const { revoked } = await disconnectWorkspace(workspace);
|
|
20402
|
+
await onChanged(workspace);
|
|
20496
20403
|
return json({
|
|
20497
|
-
disconnected:
|
|
20404
|
+
disconnected: workspace,
|
|
20498
20405
|
revokedOnNotion: revoked,
|
|
20499
20406
|
note: revoked ? "\uC774 \uCEF4\uD4E8\uD130\uC758 \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6B0\uACE0, Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4." : "\uC774 \uCEF4\uD4E8\uD130\uC758 \uD1A0\uD070\uACFC \uC124\uC815\uC744 \uC9C0\uC6E0\uC2B5\uB2C8\uB2E4. Notion \uCABD \uD1A0\uD070 \uD574\uC81C\uB294 \uC2E4\uD328\uD588\uC73C\uB2C8, Notion \uC124\uC815\uC758 \uC5F0\uACB0(Connections) \uBA54\uB274\uC5D0\uC11C \uD574\uC81C\uD558\uC138\uC694."
|
|
20500
20407
|
});
|
|
20501
20408
|
}
|
|
20502
|
-
case "workspace_rename": {
|
|
20503
|
-
if (!alias) return errorResult("alias\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
20504
|
-
const newAlias = typeof args2.new_alias === "string" ? args2.new_alias : void 0;
|
|
20505
|
-
const description = typeof args2.description === "string" ? args2.description : void 0;
|
|
20506
|
-
if (newAlias === void 0 && description === void 0) return errorResult("new_alias\uB098 description\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
20507
|
-
const entry = await renameWorkspace(alias, { newAlias, description });
|
|
20508
|
-
if (entry.alias !== alias) await onDisconnected(alias);
|
|
20509
|
-
return json({
|
|
20510
|
-
renamed: entry,
|
|
20511
|
-
note: entry.alias === alias ? void 0 : "\uC0C8 \uBCC4\uCE6D\uC740 \uC0C8 \uB300\uD654\uBD80\uD130 workspace \uAC12\uC73C\uB85C \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
20512
|
-
});
|
|
20513
|
-
}
|
|
20514
20409
|
case "workspace_check":
|
|
20515
|
-
if (!
|
|
20516
|
-
return await checkWorkspace(
|
|
20410
|
+
if (!workspace) return errorResult("workspace\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
20411
|
+
return await checkWorkspace(workspace);
|
|
20517
20412
|
default:
|
|
20518
20413
|
return void 0;
|
|
20519
20414
|
}
|
|
@@ -20521,81 +20416,177 @@ async function callManagementTool(name, args2, onDisconnected) {
|
|
|
20521
20416
|
return errorResult(error2.message);
|
|
20522
20417
|
}
|
|
20523
20418
|
}
|
|
20524
|
-
async function checkWorkspace(
|
|
20525
|
-
const entry =
|
|
20526
|
-
if (!entry) return errorResult(`\uC5F0\uACB0\uB41C \uC801 \uC5C6\uB294 \uBCC4\uCE6D\uC785\uB2C8\uB2E4: ${alias}`);
|
|
20419
|
+
async function checkWorkspace(name) {
|
|
20420
|
+
const entry = await findWorkspace(name);
|
|
20527
20421
|
let status;
|
|
20528
20422
|
let detail;
|
|
20529
20423
|
try {
|
|
20530
|
-
const session = await openWorkspace(
|
|
20424
|
+
const session = await openWorkspace(name);
|
|
20531
20425
|
try {
|
|
20532
20426
|
const self = await session.getSelf();
|
|
20533
20427
|
const tools = await session.listTools();
|
|
20534
20428
|
const sameWorkspace = self.workspaceId === entry.notionWorkspaceId;
|
|
20535
20429
|
status = sameWorkspace ? "connected" : "error";
|
|
20536
|
-
detail = sameWorkspace ? {
|
|
20537
|
-
problem: `\uCC98\uC74C \uC5F0\uACB0\uD55C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4("${entry.notionWorkspaceName}")\uAC00 \uC544\uB2C8\uB77C "${self.workspaceName}"\uC5D0 \uBD99\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0\uC744 \uD574\uC81C\uD558\uACE0 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.`
|
|
20538
|
-
};
|
|
20430
|
+
detail = sameWorkspace ? { notionName: self.workspaceName, toolCount: tools.length } : { problem: `\uC800\uC7A5\uB41C \uC778\uC99D\uC774 \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4("${self.workspaceName}")\uC5D0 \uBD99\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0\uC744 \uD574\uC81C\uD558\uACE0 \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.` };
|
|
20539
20431
|
} finally {
|
|
20540
20432
|
await session.close();
|
|
20541
20433
|
}
|
|
20542
20434
|
} catch (error2) {
|
|
20543
20435
|
const message = error2.message;
|
|
20544
|
-
|
|
20436
|
+
const signInDead = error2 instanceof InvalidGrantError || error2 instanceof UnauthorizedError || /저장된 인증/.test(message);
|
|
20437
|
+
status = signInDead ? "needs_reauth" : "error";
|
|
20545
20438
|
detail = { problem: message };
|
|
20546
20439
|
}
|
|
20547
|
-
await
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
const result = json({
|
|
20440
|
+
await updateWorkspaces(
|
|
20441
|
+
(workspaces) => workspaces.map(
|
|
20442
|
+
(w) => w.notionWorkspaceId === entry.notionWorkspaceId ? { ...w, status, lastSuccessAt: status === "connected" ? (/* @__PURE__ */ new Date()).toISOString() : w.lastSuccessAt } : w
|
|
20443
|
+
)
|
|
20444
|
+
);
|
|
20445
|
+
const result = json({ workspace: name, status, ...detail });
|
|
20553
20446
|
return status === "connected" ? result : { ...result, isError: true };
|
|
20554
20447
|
}
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
|
|
20448
|
+
|
|
20449
|
+
// src/upstream.ts
|
|
20450
|
+
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
20451
|
+
import { join as join3 } from "node:path";
|
|
20452
|
+
var UpstreamPool = class {
|
|
20453
|
+
constructor(open2 = openWorkspace) {
|
|
20454
|
+
this.open = open2;
|
|
20455
|
+
}
|
|
20456
|
+
open;
|
|
20457
|
+
sessions = /* @__PURE__ */ new Map();
|
|
20458
|
+
session(alias) {
|
|
20459
|
+
const existing = this.sessions.get(alias);
|
|
20460
|
+
if (existing) return existing;
|
|
20461
|
+
const opened = this.open(alias);
|
|
20462
|
+
opened.catch(() => {
|
|
20463
|
+
if (this.sessions.get(alias) === opened) this.sessions.delete(alias);
|
|
20464
|
+
});
|
|
20465
|
+
this.sessions.set(alias, opened);
|
|
20466
|
+
return opened;
|
|
20467
|
+
}
|
|
20468
|
+
/**
|
|
20469
|
+
* An expired upstream MCP session gets a 404 and the request was not processed, so reopen and send it once more.
|
|
20470
|
+
* Other errors may come after the request was processed, so those are never resent.
|
|
20471
|
+
*/
|
|
20472
|
+
async callTool(alias, name, args2) {
|
|
20473
|
+
try {
|
|
20474
|
+
return await (await this.session(alias)).callTool(name, args2);
|
|
20475
|
+
} catch (error2) {
|
|
20476
|
+
if (!(error2 instanceof StreamableHTTPError && error2.code === 404)) throw error2;
|
|
20477
|
+
await this.reset(alias);
|
|
20478
|
+
return (await this.session(alias)).callTool(name, args2);
|
|
20479
|
+
}
|
|
20480
|
+
}
|
|
20481
|
+
async reset(alias) {
|
|
20482
|
+
const pending = this.sessions.get(alias);
|
|
20483
|
+
this.sessions.delete(alias);
|
|
20484
|
+
await pending?.then((s) => s.close()).catch(() => {
|
|
20485
|
+
});
|
|
20486
|
+
}
|
|
20487
|
+
async closeAll() {
|
|
20488
|
+
await Promise.all([...this.sessions.keys()].map((alias) => this.reset(alias)));
|
|
20489
|
+
}
|
|
20490
|
+
};
|
|
20491
|
+
var LIVE_TIMEOUT_MS = 15e3;
|
|
20492
|
+
async function loadCatalogs(workspaces, pool) {
|
|
20493
|
+
const results = await Promise.all(
|
|
20494
|
+
workspaces.map(async (w) => {
|
|
20495
|
+
try {
|
|
20496
|
+
const catalog = await withTimeout(fetchLive(w.name, pool), LIVE_TIMEOUT_MS);
|
|
20497
|
+
await writeCache(catalog, w.notionWorkspaceId);
|
|
20498
|
+
return catalog;
|
|
20499
|
+
} catch (error2) {
|
|
20500
|
+
const cached2 = await readCache(w.notionWorkspaceId);
|
|
20501
|
+
return cached2 ? { ...cached2, alias: w.name } : { alias: w.name, reason: error2.message };
|
|
20502
|
+
}
|
|
20503
|
+
})
|
|
20504
|
+
);
|
|
20505
|
+
return {
|
|
20506
|
+
catalogs: results.filter((r) => "tools" in r),
|
|
20507
|
+
failures: results.filter((r) => "reason" in r)
|
|
20508
|
+
};
|
|
20509
|
+
}
|
|
20510
|
+
async function fetchLive(alias, pool) {
|
|
20511
|
+
const session = await pool.session(alias);
|
|
20512
|
+
return {
|
|
20513
|
+
alias,
|
|
20514
|
+
tools: await session.listTools(),
|
|
20515
|
+
instructions: session.instructions
|
|
20516
|
+
};
|
|
20517
|
+
}
|
|
20518
|
+
async function writeCache(catalog, workspaceId) {
|
|
20519
|
+
await mkdir3(join3(configDir(), "cache"), { recursive: true, mode: 448 });
|
|
20520
|
+
const tmp = `${cachePath(workspaceId)}.${process.pid}.tmp`;
|
|
20521
|
+
await writeFile2(tmp, JSON.stringify(catalog), { mode: 384 });
|
|
20522
|
+
await rename2(tmp, cachePath(workspaceId));
|
|
20523
|
+
}
|
|
20524
|
+
async function readCache(workspaceId) {
|
|
20525
|
+
try {
|
|
20526
|
+
return JSON.parse(await readFile2(cachePath(workspaceId), "utf8"));
|
|
20527
|
+
} catch {
|
|
20528
|
+
return void 0;
|
|
20529
|
+
}
|
|
20530
|
+
}
|
|
20531
|
+
function withTimeout(promise, ms) {
|
|
20532
|
+
let timer;
|
|
20533
|
+
return Promise.race([
|
|
20534
|
+
promise,
|
|
20535
|
+
new Promise((_, reject) => {
|
|
20536
|
+
timer = setTimeout(() => reject(new Error(`${ms / 1e3}\uCD08 \uC548\uC5D0 \uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`)), ms);
|
|
20537
|
+
})
|
|
20538
|
+
]).finally(() => clearTimeout(timer));
|
|
20558
20539
|
}
|
|
20559
20540
|
|
|
20560
20541
|
// src/server.ts
|
|
20561
20542
|
var log = (message) => console.error(`[${SERVER_NAME}] ${message}`);
|
|
20562
20543
|
async function createServer2() {
|
|
20563
|
-
const workspaces = await loadWorkspaces();
|
|
20564
20544
|
const pool = new UpstreamPool();
|
|
20545
|
+
const workspaces = await loadWorkspaces();
|
|
20565
20546
|
const { catalogs, failures } = await loadCatalogs(workspaces, pool);
|
|
20566
|
-
|
|
20567
|
-
const catalog = mergeCatalogs(catalogs, log);
|
|
20568
|
-
const searchAll = searchAllTool(catalog);
|
|
20547
|
+
let current = toolsFor(catalogs, failures);
|
|
20569
20548
|
const server = new Server(
|
|
20570
20549
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
20571
|
-
{ capabilities: { tools: {} }, instructions: buildInstructions(workspaces, catalogs, failures) }
|
|
20550
|
+
{ capabilities: { tools: { listChanged: true } }, instructions: buildInstructions(workspaces, catalogs, failures) }
|
|
20572
20551
|
);
|
|
20552
|
+
const refresh = async (staleAlias) => {
|
|
20553
|
+
if (staleAlias) await pool.reset(staleAlias);
|
|
20554
|
+
const latest = await loadCatalogs(await loadWorkspaces(), pool);
|
|
20555
|
+
current = toolsFor(latest.catalogs, latest.failures);
|
|
20556
|
+
await server.sendToolListChanged();
|
|
20557
|
+
};
|
|
20573
20558
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
20574
|
-
tools: [...managementTools, ...searchAll ? [searchAll] : [], ...catalog.tools]
|
|
20559
|
+
tools: [...managementTools, ...current.searchAll ? [current.searchAll] : [], ...current.catalog.tools]
|
|
20575
20560
|
}));
|
|
20576
20561
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
20577
20562
|
const { name, arguments: args2 } = request.params;
|
|
20563
|
+
const { catalog, searchAll } = current;
|
|
20578
20564
|
const call = (alias, tool, rest) => pool.callTool(alias, tool, rest);
|
|
20579
20565
|
if (searchAll && name === SEARCH_ALL) return callSearchAll(catalog, args2, call);
|
|
20580
|
-
const managed = await callManagementTool(name, args2 ?? {},
|
|
20566
|
+
const managed = await callManagementTool(name, args2 ?? {}, refresh);
|
|
20581
20567
|
return managed ?? callProxiedTool(catalog, name, args2, call);
|
|
20582
20568
|
});
|
|
20583
20569
|
server.onclose = () => void pool.closeAll();
|
|
20584
20570
|
return server;
|
|
20585
20571
|
}
|
|
20572
|
+
function toolsFor(catalogs, failures) {
|
|
20573
|
+
for (const f of failures) log(`${f.alias}: \uB3C4\uAD6C \uBAA9\uB85D\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${f.reason})`);
|
|
20574
|
+
const catalog = mergeCatalogs(catalogs, log);
|
|
20575
|
+
return { catalog, searchAll: searchAllTool(catalog) };
|
|
20576
|
+
}
|
|
20586
20577
|
function buildInstructions(workspaces, catalogs, failures) {
|
|
20587
20578
|
const failed = new Map(failures.map((f) => [f.alias, f.reason]));
|
|
20588
20579
|
const lines = workspaces.length === 0 ? ["- (\uC5C6\uC74C) workspace_connect \uB3C4\uAD6C\uB85C \uBA3C\uC800 \uC5F0\uACB0\uD574\uC57C \uD55C\uB2E4."] : workspaces.map((w) => {
|
|
20589
|
-
const label = `-
|
|
20590
|
-
return failed.has(w.
|
|
20580
|
+
const label = `- "${w.name}"${w.description ? `: ${w.description}` : ""}`;
|
|
20581
|
+
return failed.has(w.name) ? `${label} - \uC9C0\uAE08 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC74C: ${failed.get(w.name)}` : label;
|
|
20591
20582
|
});
|
|
20592
20583
|
const sections = [
|
|
20593
20584
|
[
|
|
20594
20585
|
"\uC774 \uC11C\uBC84(Multi-Workspace for Notion)\uB294 \uC5EC\uB7EC Notion \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uB3D9\uC2DC\uC5D0 \uC5F0\uACB0\uD574 \uB454\uB2E4.",
|
|
20595
|
-
`\uBAA8\uB4E0 Notion \uB3C4\uAD6C\uC5D0\uB294 ${WORKSPACE_PARAM} \uD30C\uB77C\uBBF8\uD130\uAC00 \uC788\uACE0, \uD638\uCD9C\uB9C8\uB2E4 \uB300\uC0C1 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \
|
|
20586
|
+
`\uBAA8\uB4E0 Notion \uB3C4\uAD6C\uC5D0\uB294 ${WORKSPACE_PARAM} \uD30C\uB77C\uBBF8\uD130\uAC00 \uC788\uACE0, \uD638\uCD9C\uB9C8\uB2E4 \uB300\uC0C1 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984(\uC544\uB798 \uBAA9\uB85D\uC758 \uC774\uB984 \uADF8\uB300\uB85C)\uC744 \uB123\uC5B4\uC57C \uD55C\uB2E4.`,
|
|
20596
20587
|
"\uC0AC\uC6A9\uC790\uAC00 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uB9D0\uD558\uC9C0 \uC54A\uC558\uACE0 \uB9E5\uB77D\uC73C\uB85C\uB3C4 \uC54C \uC218 \uC5C6\uC744 \uB54C, \uC4F0\uAE30\xB7\uC218\uC815\xB7\uC0AD\uC81C\uB294 \uC2E4\uD589\uD558\uAE30 \uC804\uC5D0 \uC5B4\uB290 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC778\uC9C0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3B\uB294\uB2E4.",
|
|
20597
20588
|
`\uC5EC\uB7EC \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uD55C\uAEBC\uBC88\uC5D0 \uCC3E\uC744 \uB54C\uB294 ${SEARCH_ALL} \uB3C4\uAD6C\uB97C \uC4F4\uB2E4(\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uB458 \uC774\uC0C1\uC77C \uB54C\uB9CC \uC788\uB2E4).`,
|
|
20598
|
-
"\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uB294 workspace_list, workspace_connect, workspace_disconnect,
|
|
20589
|
+
"\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uB294 workspace_list, workspace_connect, workspace_disconnect, workspace_check \uB3C4\uAD6C\uB85C \uD55C\uB2E4.",
|
|
20599
20590
|
"",
|
|
20600
20591
|
"\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4:",
|
|
20601
20592
|
...lines
|
|
@@ -20622,10 +20613,10 @@ var [command, ...args] = process.argv.slice(2);
|
|
|
20622
20613
|
var USAGE = `\uC0AC\uC6A9\uBC95:
|
|
20623
20614
|
multi-workspace-for-notion MCP \uC11C\uBC84(stdio)\uB85C \uC2E4\uD589
|
|
20624
20615
|
multi-workspace-for-notion list \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D
|
|
20625
|
-
multi-workspace-for-notion add
|
|
20626
|
-
multi-workspace-for-notion tools <\
|
|
20627
|
-
multi-workspace-for-notion
|
|
20628
|
-
|
|
20616
|
+
multi-workspace-for-notion add [\uC124\uBA85...] \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC5F0\uACB0(\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uACE0\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uC774\uB984\uC73C\uB85C \uC5F0\uACB0)
|
|
20617
|
+
multi-workspace-for-notion tools <\uC774\uB984> \uC800\uC7A5\uB41C \uC778\uC99D\uC73C\uB85C \uBD99\uC5B4\uC11C \uD655\uC778\uD558\uACE0 \uB3C4\uAD6C \uBAA9\uB85D \uCD9C\uB825
|
|
20618
|
+
multi-workspace-for-notion remove <\uC774\uB984> \uC5F0\uACB0 \uD574\uC81C\uC640 \uD1A0\uD070 \uC0AD\uC81C
|
|
20619
|
+
(\uC774\uB984\uC5D0 \uACF5\uBC31\uC774 \uC788\uC73C\uBA74 \uB530\uC634\uD45C\uB85C \uAC10\uC2FC\uB2E4: remove "\uD5C8 \uC740\uC11C\uC758 Notion")`;
|
|
20629
20620
|
try {
|
|
20630
20621
|
switch (command) {
|
|
20631
20622
|
case void 0: {
|
|
@@ -20639,7 +20630,7 @@ try {
|
|
|
20639
20630
|
console.log("\uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
20640
20631
|
}
|
|
20641
20632
|
for (const w of workspaces) {
|
|
20642
|
-
console.log(`${w.
|
|
20633
|
+
console.log(`${w.name} ${w.status} ${w.description}`);
|
|
20643
20634
|
}
|
|
20644
20635
|
break;
|
|
20645
20636
|
}
|
|
@@ -20648,11 +20639,8 @@ try {
|
|
|
20648
20639
|
break;
|
|
20649
20640
|
}
|
|
20650
20641
|
case "add": {
|
|
20651
|
-
const [alias, ...descriptionWords] = args;
|
|
20652
|
-
if (!alias) throw new UsageError();
|
|
20653
20642
|
const { entry, tools } = await connectWorkspace({
|
|
20654
|
-
|
|
20655
|
-
description: descriptionWords.join(" "),
|
|
20643
|
+
description: args.join(" "),
|
|
20656
20644
|
onAuthorizationUrl: (url2) => console.log(
|
|
20657
20645
|
`\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C Notion \uC778\uC99D \uD654\uBA74\uC744 \uC5FD\uB2C8\uB2E4. \uC5F0\uACB0\uD560 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uACE8\uB77C \uC2B9\uC778\uD558\uC138\uC694.
|
|
20658
20646
|
\uBE0C\uB77C\uC6B0\uC800\uAC00 \uC5F4\uB9AC\uC9C0 \uC54A\uC73C\uBA74 \uC774 \uC8FC\uC18C\uB85C \uB4E4\uC5B4\uAC00\uC138\uC694:
|
|
@@ -20660,38 +20648,30 @@ ${url2.href}
|
|
|
20660
20648
|
`
|
|
20661
20649
|
)
|
|
20662
20650
|
});
|
|
20663
|
-
console.log(`\uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4:
|
|
20651
|
+
console.log(`\uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4: "${entry.name}" (${entry.notionWorkspaceId})`);
|
|
20664
20652
|
console.log(`\uACF5\uC2DD \uB3C4\uAD6C ${tools.length}\uAC1C\uB97C \uBC1B\uC558\uC2B5\uB2C8\uB2E4.`);
|
|
20665
20653
|
break;
|
|
20666
20654
|
}
|
|
20667
20655
|
case "tools": {
|
|
20668
|
-
const [
|
|
20669
|
-
if (!
|
|
20670
|
-
const session = await openWorkspace(
|
|
20656
|
+
const [name] = args;
|
|
20657
|
+
if (!name) throw new UsageError();
|
|
20658
|
+
const session = await openWorkspace(name);
|
|
20671
20659
|
try {
|
|
20672
20660
|
const self = await session.getSelf();
|
|
20673
20661
|
const tools = await session.listTools();
|
|
20674
|
-
console.log(
|
|
20662
|
+
console.log(`"${name}" \u2192 Notion "${self.workspaceName}" (${self.workspaceId}), \uB3C4\uAD6C ${tools.length}\uAC1C`);
|
|
20675
20663
|
for (const tool of tools) console.log(` ${tool.name}`);
|
|
20676
20664
|
} finally {
|
|
20677
20665
|
await session.close();
|
|
20678
20666
|
}
|
|
20679
20667
|
break;
|
|
20680
20668
|
}
|
|
20681
|
-
case "rename": {
|
|
20682
|
-
const [alias, newAlias] = args;
|
|
20683
|
-
if (!alias || !newAlias) throw new UsageError();
|
|
20684
|
-
await renameWorkspace(alias, { newAlias });
|
|
20685
|
-
console.log(`\uBCC4\uCE6D\uC744 \uBC14\uAFE8\uC2B5\uB2C8\uB2E4: ${alias} \u2192 ${newAlias}`);
|
|
20686
|
-
break;
|
|
20687
|
-
}
|
|
20688
20669
|
case "remove": {
|
|
20689
|
-
const [
|
|
20690
|
-
if (!
|
|
20691
|
-
const {
|
|
20692
|
-
if (
|
|
20693
|
-
else
|
|
20694
|
-
else console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${alias}
|
|
20670
|
+
const [name] = args;
|
|
20671
|
+
if (!name) throw new UsageError();
|
|
20672
|
+
const { revoked } = await disconnectWorkspace(name);
|
|
20673
|
+
if (revoked) console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${name} (Notion \uCABD \uD1A0\uD070\uB3C4 \uD574\uC81C)`);
|
|
20674
|
+
else console.log(`\uC5F0\uACB0\uC744 \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4: ${name}
|
|
20695
20675
|
Notion \uCABD \uD1A0\uD070 \uD574\uC81C\uB294 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. Notion \uC124\uC815\uC758 \uC5F0\uACB0(Connections) \uBA54\uB274\uC5D0\uC11C \uD574\uC81C\uD558\uC138\uC694.`);
|
|
20696
20676
|
break;
|
|
20697
20677
|
}
|
|
@@ -20710,8 +20690,8 @@ ${USAGE}` : USAGE);
|
|
|
20710
20690
|
process.exitCode = 1;
|
|
20711
20691
|
}
|
|
20712
20692
|
function sessionStatus(workspaces) {
|
|
20713
|
-
if (workspaces.length === 0) return "Notion: \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. /multi-workspace:workspace-add
|
|
20714
|
-
const names = workspaces.map((w) =>
|
|
20715
|
-
const broken = workspaces.filter((w) => w.status !== "connected").map((w) => w.
|
|
20693
|
+
if (workspaces.length === 0) return "Notion: \uC5F0\uACB0\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. /multi-workspace:workspace-add \uB85C \uC5F0\uACB0\uD558\uC138\uC694.";
|
|
20694
|
+
const names = workspaces.map((w) => w.name).join(", ");
|
|
20695
|
+
const broken = workspaces.filter((w) => w.status !== "connected").map((w) => w.name);
|
|
20716
20696
|
return broken.length === 0 ? `Notion: ${names}` : `Notion: ${names} \xB7 \uD655\uC778 \uD544\uC694: ${broken.join(", ")} \u2192 /multi-workspace:workspace-check`;
|
|
20717
20697
|
}
|