context-vault 2.8.4 → 2.8.5
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/bin/cli.js +176 -43
- package/node_modules/@context-vault/core/package.json +1 -1
- package/package.json +2 -3
- package/scripts/local-server.js +1 -7
- package/scripts/postinstall.js +10 -1
- package/scripts/prepack.js +0 -16
package/bin/cli.js
CHANGED
|
@@ -222,6 +222,7 @@ ${bold("Usage:")}
|
|
|
222
222
|
${bold("Commands:")}
|
|
223
223
|
${cyan("setup")} Interactive MCP server installer
|
|
224
224
|
${cyan("connect")} --key cv_... Connect AI tools to hosted vault
|
|
225
|
+
${cyan("switch")} local|hosted Switch between local and hosted MCP modes
|
|
225
226
|
${cyan("serve")} Start the MCP server (used by AI clients)
|
|
226
227
|
${cyan("ui")} [--port 3141] Launch web dashboard
|
|
227
228
|
${cyan("reindex")} Rebuild search index from knowledge files
|
|
@@ -451,6 +452,7 @@ async function runSetup() {
|
|
|
451
452
|
vaultConfig.dataDir = dataDir;
|
|
452
453
|
vaultConfig.dbPath = join(dataDir, "vault.db");
|
|
453
454
|
vaultConfig.devDir = join(HOME, "dev");
|
|
455
|
+
vaultConfig.mode = "local";
|
|
454
456
|
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + "\n");
|
|
455
457
|
console.log(`\n ${green("+")} Wrote ${configPath}`);
|
|
456
458
|
|
|
@@ -635,10 +637,11 @@ async function configureClaude(tool, vaultDir) {
|
|
|
635
637
|
|
|
636
638
|
try {
|
|
637
639
|
if (isInstalledPackage()) {
|
|
638
|
-
const
|
|
640
|
+
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
641
|
+
const cmdArgs = [`"${launcherPath}"`];
|
|
639
642
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
640
643
|
execSync(
|
|
641
|
-
`claude mcp add -s user context-vault --
|
|
644
|
+
`claude mcp add -s user context-vault -- node ${cmdArgs.join(" ")}`,
|
|
642
645
|
{ stdio: "pipe", env },
|
|
643
646
|
);
|
|
644
647
|
} else {
|
|
@@ -667,12 +670,12 @@ async function configureCodex(tool, vaultDir) {
|
|
|
667
670
|
|
|
668
671
|
try {
|
|
669
672
|
if (isInstalledPackage()) {
|
|
670
|
-
const
|
|
673
|
+
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
674
|
+
const cmdArgs = [`"${launcherPath}"`];
|
|
671
675
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
672
|
-
execSync(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
);
|
|
676
|
+
execSync(`codex mcp add context-vault -- node ${cmdArgs.join(" ")}`, {
|
|
677
|
+
stdio: "pipe",
|
|
678
|
+
});
|
|
676
679
|
} else {
|
|
677
680
|
const cmdArgs = [`"${SERVER_PATH}"`];
|
|
678
681
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
@@ -715,11 +718,12 @@ function configureJsonTool(tool, vaultDir) {
|
|
|
715
718
|
delete config[tool.configKey]["context-mcp"];
|
|
716
719
|
|
|
717
720
|
if (isInstalledPackage()) {
|
|
718
|
-
const
|
|
721
|
+
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
722
|
+
const serverArgs = [];
|
|
719
723
|
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
720
724
|
config[tool.configKey]["context-vault"] = {
|
|
721
|
-
command: "
|
|
722
|
-
args: serverArgs,
|
|
725
|
+
command: "node",
|
|
726
|
+
args: [launcherPath, ...serverArgs],
|
|
723
727
|
};
|
|
724
728
|
} else {
|
|
725
729
|
const serverArgs = [SERVER_PATH];
|
|
@@ -948,6 +952,19 @@ async function runConnect() {
|
|
|
948
952
|
}
|
|
949
953
|
}
|
|
950
954
|
|
|
955
|
+
// Persist mode in config
|
|
956
|
+
const modeConfigPath = join(HOME, ".context-mcp", "config.json");
|
|
957
|
+
let modeConfig = {};
|
|
958
|
+
if (existsSync(modeConfigPath)) {
|
|
959
|
+
try {
|
|
960
|
+
modeConfig = JSON.parse(readFileSync(modeConfigPath, "utf-8"));
|
|
961
|
+
} catch {}
|
|
962
|
+
}
|
|
963
|
+
modeConfig.mode = "hosted";
|
|
964
|
+
modeConfig.hostedUrl = hostedUrl;
|
|
965
|
+
mkdirSync(join(HOME, ".context-mcp"), { recursive: true });
|
|
966
|
+
writeFileSync(modeConfigPath, JSON.stringify(modeConfig, null, 2) + "\n");
|
|
967
|
+
|
|
951
968
|
console.log();
|
|
952
969
|
console.log(
|
|
953
970
|
green(" ✓ Connected! Your AI tools can now access your hosted vault."),
|
|
@@ -1033,31 +1050,136 @@ function configureJsonToolHosted(tool, apiKey, hostedUrl) {
|
|
|
1033
1050
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1034
1051
|
}
|
|
1035
1052
|
|
|
1036
|
-
function
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1053
|
+
async function runSwitch() {
|
|
1054
|
+
const target = args[1];
|
|
1055
|
+
if (target !== "local" && target !== "hosted") {
|
|
1056
|
+
console.log(`\n ${bold("context-vault switch")} <local|hosted>\n`);
|
|
1057
|
+
console.log(` Switch between local and hosted MCP modes.\n`);
|
|
1058
|
+
console.log(
|
|
1059
|
+
` ${cyan("switch local")} Use local vault (SQLite + files on this device)`,
|
|
1060
|
+
);
|
|
1061
|
+
console.log(
|
|
1062
|
+
` ${cyan("switch hosted")} Use hosted vault (requires API key)\n`,
|
|
1063
|
+
);
|
|
1064
|
+
console.log(` Options:`);
|
|
1065
|
+
console.log(` --key <key> API key for hosted mode (cv_...)`);
|
|
1066
|
+
console.log(
|
|
1067
|
+
` --url <url> Hosted server URL (default: https://api.context-vault.com)\n`,
|
|
1068
|
+
);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1045
1071
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
PLATFORM === "darwin"
|
|
1051
|
-
? "open"
|
|
1052
|
-
: PLATFORM === "win32"
|
|
1053
|
-
? "start"
|
|
1054
|
-
: "xdg-open";
|
|
1072
|
+
const dataDir = join(HOME, ".context-mcp");
|
|
1073
|
+
const configPath = join(dataDir, "config.json");
|
|
1074
|
+
let vaultConfig = {};
|
|
1075
|
+
if (existsSync(configPath)) {
|
|
1055
1076
|
try {
|
|
1056
|
-
|
|
1077
|
+
vaultConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1057
1078
|
} catch {}
|
|
1058
|
-
return;
|
|
1059
1079
|
}
|
|
1060
1080
|
|
|
1081
|
+
const { detected } = await detectAllTools();
|
|
1082
|
+
|
|
1083
|
+
if (target === "local") {
|
|
1084
|
+
const launcherPath = join(dataDir, "server.mjs");
|
|
1085
|
+
if (!existsSync(launcherPath)) {
|
|
1086
|
+
const serverAbs = resolve(ROOT, "src", "server", "index.js");
|
|
1087
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1088
|
+
writeFileSync(launcherPath, `import "${serverAbs}";\n`);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
vaultConfig.mode = "local";
|
|
1092
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1093
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + "\n");
|
|
1094
|
+
|
|
1095
|
+
console.log();
|
|
1096
|
+
console.log(` ${bold("◇ context-vault")} ${dim("switch → local")}`);
|
|
1097
|
+
console.log();
|
|
1098
|
+
|
|
1099
|
+
const defaultVDir = join(HOME, "vault");
|
|
1100
|
+
const customVaultDir =
|
|
1101
|
+
vaultConfig.vaultDir &&
|
|
1102
|
+
resolve(vaultConfig.vaultDir) !== resolve(defaultVDir)
|
|
1103
|
+
? vaultConfig.vaultDir
|
|
1104
|
+
: null;
|
|
1105
|
+
|
|
1106
|
+
for (const tool of detected) {
|
|
1107
|
+
try {
|
|
1108
|
+
if (tool.configType === "cli" && tool.id === "codex") {
|
|
1109
|
+
await configureCodex(tool, customVaultDir);
|
|
1110
|
+
} else if (tool.configType === "cli") {
|
|
1111
|
+
await configureClaude(tool, customVaultDir);
|
|
1112
|
+
} else {
|
|
1113
|
+
configureJsonTool(tool, customVaultDir);
|
|
1114
|
+
}
|
|
1115
|
+
console.log(` ${green("+")} ${tool.name} — switched to local`);
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
console.log(` ${red("x")} ${tool.name} — ${e.message}`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
console.log();
|
|
1121
|
+
console.log(green(" ✓ Switched to local mode."));
|
|
1122
|
+
console.log(dim(` Server: node ${launcherPath}`));
|
|
1123
|
+
console.log();
|
|
1124
|
+
} else {
|
|
1125
|
+
const hostedUrl =
|
|
1126
|
+
getFlag("--url") ||
|
|
1127
|
+
vaultConfig.hostedUrl ||
|
|
1128
|
+
"https://api.context-vault.com";
|
|
1129
|
+
const apiKey = getFlag("--key") || vaultConfig.apiKey;
|
|
1130
|
+
|
|
1131
|
+
if (!apiKey) {
|
|
1132
|
+
console.error(
|
|
1133
|
+
red(` --key <api_key> required. Get yours at ${hostedUrl}/dashboard`),
|
|
1134
|
+
);
|
|
1135
|
+
process.exit(1);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
console.log();
|
|
1139
|
+
console.log(` ${bold("◇ context-vault")} ${dim("switch → hosted")}`);
|
|
1140
|
+
console.log();
|
|
1141
|
+
console.log(dim(" Verifying API key..."));
|
|
1142
|
+
|
|
1143
|
+
try {
|
|
1144
|
+
const response = await fetch(`${hostedUrl}/api/me`, {
|
|
1145
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1146
|
+
});
|
|
1147
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1148
|
+
const user = await response.json();
|
|
1149
|
+
console.log(` ${green("+")} Verified — ${user.email}\n`);
|
|
1150
|
+
} catch (e) {
|
|
1151
|
+
console.error(red(` Verification failed: ${e.message}`));
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
vaultConfig.mode = "hosted";
|
|
1156
|
+
vaultConfig.hostedUrl = hostedUrl;
|
|
1157
|
+
vaultConfig.apiKey = apiKey;
|
|
1158
|
+
mkdirSync(dataDir, { recursive: true });
|
|
1159
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + "\n");
|
|
1160
|
+
|
|
1161
|
+
for (const tool of detected) {
|
|
1162
|
+
try {
|
|
1163
|
+
if (tool.configType === "cli" && tool.id === "codex") {
|
|
1164
|
+
configureCodexHosted(apiKey, hostedUrl);
|
|
1165
|
+
} else if (tool.configType === "cli") {
|
|
1166
|
+
configureClaudeHosted(apiKey, hostedUrl);
|
|
1167
|
+
} else {
|
|
1168
|
+
configureJsonToolHosted(tool, apiKey, hostedUrl);
|
|
1169
|
+
}
|
|
1170
|
+
console.log(` ${green("+")} ${tool.name} — switched to hosted`);
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
console.log(` ${red("x")} ${tool.name} — ${e.message}`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
console.log();
|
|
1176
|
+
console.log(green(" ✓ Switched to hosted mode."));
|
|
1177
|
+
console.log(dim(` Endpoint: ${hostedUrl}/mcp`));
|
|
1178
|
+
console.log();
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function runUi() {
|
|
1061
1183
|
const port = parseInt(getFlag("--port") || "3141", 10);
|
|
1062
1184
|
const localServer = join(ROOT, "scripts", "local-server.js");
|
|
1063
1185
|
if (!existsSync(localServer)) {
|
|
@@ -1088,20 +1210,9 @@ function launchServer(port, localServer) {
|
|
|
1088
1210
|
const child = fork(localServer, [`--port=${port}`], { stdio: "inherit" });
|
|
1089
1211
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1090
1212
|
|
|
1091
|
-
|
|
1092
|
-
setTimeout(async () => {
|
|
1213
|
+
setTimeout(() => {
|
|
1093
1214
|
try {
|
|
1094
|
-
|
|
1095
|
-
try {
|
|
1096
|
-
const controller = new AbortController();
|
|
1097
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
1098
|
-
await fetch("https://context-vault.com", {
|
|
1099
|
-
method: "HEAD",
|
|
1100
|
-
signal: controller.signal,
|
|
1101
|
-
});
|
|
1102
|
-
clearTimeout(timeout);
|
|
1103
|
-
url = `https://context-vault.com?local=${port}`;
|
|
1104
|
-
} catch {}
|
|
1215
|
+
const url = `https://app.context-vault.com?local=${port}`;
|
|
1105
1216
|
console.log(`Opening ${url}`);
|
|
1106
1217
|
const open =
|
|
1107
1218
|
PLATFORM === "darwin"
|
|
@@ -1157,6 +1268,24 @@ async function runStatus() {
|
|
|
1157
1268
|
const { gatherVaultStatus } = await import("@context-vault/core/core/status");
|
|
1158
1269
|
|
|
1159
1270
|
const config = resolveConfig();
|
|
1271
|
+
|
|
1272
|
+
let mode = "local";
|
|
1273
|
+
let modeDetail = "";
|
|
1274
|
+
const rawConfigPath = join(HOME, ".context-mcp", "config.json");
|
|
1275
|
+
if (existsSync(rawConfigPath)) {
|
|
1276
|
+
try {
|
|
1277
|
+
const raw = JSON.parse(readFileSync(rawConfigPath, "utf-8"));
|
|
1278
|
+
mode = raw.mode || "local";
|
|
1279
|
+
if (mode === "hosted" && raw.hostedUrl) {
|
|
1280
|
+
const email = raw.email ? ` · ${raw.email}` : "";
|
|
1281
|
+
modeDetail = ` (${raw.hostedUrl}${email})`;
|
|
1282
|
+
} else {
|
|
1283
|
+
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
1284
|
+
modeDetail = ` (node ${launcherPath})`;
|
|
1285
|
+
}
|
|
1286
|
+
} catch {}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1160
1289
|
const db = await initDatabase(config.dbPath);
|
|
1161
1290
|
|
|
1162
1291
|
const status = gatherVaultStatus({ db, config });
|
|
@@ -1166,6 +1295,7 @@ async function runStatus() {
|
|
|
1166
1295
|
console.log();
|
|
1167
1296
|
console.log(` ${bold("◇ context-vault")} ${dim(`v${VERSION}`)}`);
|
|
1168
1297
|
console.log();
|
|
1298
|
+
console.log(` Mode: ${mode}${dim(modeDetail)}`);
|
|
1169
1299
|
console.log(
|
|
1170
1300
|
` Vault: ${config.vaultDir} ${dim(`(${config.vaultDirExists ? status.fileCount + " files" : "missing"})`)}`,
|
|
1171
1301
|
);
|
|
@@ -1916,6 +2046,9 @@ async function main() {
|
|
|
1916
2046
|
case "connect":
|
|
1917
2047
|
await runConnect();
|
|
1918
2048
|
break;
|
|
2049
|
+
case "switch":
|
|
2050
|
+
await runSwitch();
|
|
2051
|
+
break;
|
|
1919
2052
|
case "serve":
|
|
1920
2053
|
await runServe();
|
|
1921
2054
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"bin/",
|
|
21
21
|
"src/",
|
|
22
22
|
"scripts/",
|
|
23
|
-
"app-dist/",
|
|
24
23
|
"README.md",
|
|
25
24
|
"LICENSE"
|
|
26
25
|
],
|
|
@@ -56,7 +55,7 @@
|
|
|
56
55
|
"@context-vault/core"
|
|
57
56
|
],
|
|
58
57
|
"dependencies": {
|
|
59
|
-
"@context-vault/core": "^2.8.
|
|
58
|
+
"@context-vault/core": "^2.8.5",
|
|
60
59
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
61
60
|
"better-sqlite3": "^12.6.2",
|
|
62
61
|
"sqlite-vec": "^0.1.0"
|
package/scripts/local-server.js
CHANGED
|
@@ -46,13 +46,7 @@ import {
|
|
|
46
46
|
|
|
47
47
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
48
48
|
const LOCAL_ROOT = resolve(__dirname, "..");
|
|
49
|
-
|
|
50
|
-
// Try bundled path first (npm install), then workspace path (local dev)
|
|
51
|
-
const bundledDist = resolve(LOCAL_ROOT, "app-dist");
|
|
52
|
-
const workspaceDist = resolve(LOCAL_ROOT, "..", "app", "dist");
|
|
53
|
-
const APP_DIST = existsSync(join(bundledDist, "index.html"))
|
|
54
|
-
? bundledDist
|
|
55
|
-
: workspaceDist;
|
|
49
|
+
const APP_DIST = resolve(LOCAL_ROOT, "app-dist");
|
|
56
50
|
|
|
57
51
|
const MIME = {
|
|
58
52
|
".html": "text/html",
|
package/scripts/postinstall.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
13
|
+
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { homedir } from "node:os";
|
|
16
17
|
|
|
17
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const PKG_ROOT = join(__dirname, "..");
|
|
@@ -88,6 +89,14 @@ async function main() {
|
|
|
88
89
|
);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
|
|
93
|
+
// ── 3. Write local server launcher ───────────────────────────────────
|
|
94
|
+
const SERVER_ABS = join(PKG_ROOT, "src", "server", "index.js");
|
|
95
|
+
const DATA_DIR = join(homedir(), ".context-mcp");
|
|
96
|
+
const LAUNCHER = join(DATA_DIR, "server.mjs");
|
|
97
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
98
|
+
writeFileSync(LAUNCHER, `import "${SERVER_ABS}";\n`);
|
|
99
|
+
console.log("[context-vault] Local server launcher written to " + LAUNCHER);
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
main().catch(() => {});
|
package/scripts/prepack.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
mkdirSync,
|
|
7
7
|
readFileSync,
|
|
8
8
|
writeFileSync,
|
|
9
|
-
existsSync,
|
|
10
9
|
} from "node:fs";
|
|
11
10
|
import { join, dirname } from "node:path";
|
|
12
11
|
import { fileURLToPath } from "node:url";
|
|
@@ -16,8 +15,6 @@ const LOCAL_ROOT = join(__dirname, "..");
|
|
|
16
15
|
const NODE_MODULES = join(LOCAL_ROOT, "node_modules");
|
|
17
16
|
const CORE_SRC = join(LOCAL_ROOT, "..", "core");
|
|
18
17
|
const CORE_DEST = join(NODE_MODULES, "@context-vault", "core");
|
|
19
|
-
const APP_SRC = join(LOCAL_ROOT, "..", "app", "dist");
|
|
20
|
-
const APP_DEST = join(LOCAL_ROOT, "app-dist");
|
|
21
18
|
|
|
22
19
|
// Clean node_modules to prevent workspace deps from leaking into the tarball.
|
|
23
20
|
// Only @context-vault/core should be bundled.
|
|
@@ -44,16 +41,3 @@ delete corePkg.dependencies;
|
|
|
44
41
|
writeFileSync(corePkgPath, JSON.stringify(corePkg, null, 2) + "\n");
|
|
45
42
|
|
|
46
43
|
console.log("[prepack] Bundled @context-vault/core into node_modules");
|
|
47
|
-
|
|
48
|
-
// Copy pre-built web dashboard into app-dist/ (optional — UI falls back to cloud)
|
|
49
|
-
if (!existsSync(join(APP_SRC, "index.html"))) {
|
|
50
|
-
console.warn(
|
|
51
|
-
"[prepack] WARNING: Web dashboard not built — app-dist/ will not be bundled.\n" +
|
|
52
|
-
"[prepack] `context-vault ui` will open app.context-vault.com instead.\n" +
|
|
53
|
-
"[prepack] To bundle locally: build packages/app first.",
|
|
54
|
-
);
|
|
55
|
-
} else {
|
|
56
|
-
rmSync(APP_DEST, { recursive: true, force: true });
|
|
57
|
-
cpSync(APP_SRC, APP_DEST, { recursive: true });
|
|
58
|
-
console.log("[prepack] Bundled web dashboard into app-dist/");
|
|
59
|
-
}
|