context-vault 2.2.0 → 2.3.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.
- package/bin/cli.js +354 -32
- package/node_modules/@context-vault/core/package.json +36 -0
- package/{src → node_modules/@context-vault/core/src}/core/categories.js +1 -0
- package/{src → node_modules/@context-vault/core/src}/core/files.js +1 -0
- package/{src → node_modules/@context-vault/core/src}/index/embed.js +10 -1
- package/node_modules/@context-vault/core/src/index.js +29 -0
- package/{src → node_modules/@context-vault/core/src}/server/tools.js +107 -26
- package/package.json +7 -14
- package/src/server/index.js +21 -4
- package/ui/serve.js +7 -6
- package/LICENSE +0 -21
- package/README.md +0 -431
- package/smithery.yaml +0 -10
- package/src/capture/README.md +0 -23
- package/src/core/README.md +0 -20
- package/src/index/README.md +0 -28
- package/src/retrieve/README.md +0 -19
- package/src/server/README.md +0 -44
- /package/{src → node_modules/@context-vault/core/src}/capture/file-ops.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/capture/formatters.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/capture/index.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/core/config.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/core/frontmatter.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/core/status.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/index/db.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/index/index.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/retrieve/index.js +0 -0
- /package/{src → node_modules/@context-vault/core/src}/server/helpers.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* tools.js — MCP tool registrations
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* delete_context (remove), context_status (diag).
|
|
4
|
+
* Six tools: save_context (write/update), get_context (search), list_context (browse),
|
|
5
|
+
* delete_context (remove), submit_feedback (bug/feature reports), context_status (diag).
|
|
6
6
|
* Auto-reindex runs transparently on first tool call per session.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -63,9 +63,9 @@ export function registerTools(server, ctx) {
|
|
|
63
63
|
|
|
64
64
|
server.tool(
|
|
65
65
|
"get_context",
|
|
66
|
-
"Search your knowledge vault. Returns entries ranked by relevance using hybrid full-text + semantic search. Use this to find insights, decisions, patterns, or any saved context.",
|
|
66
|
+
"Search your knowledge vault. Returns entries ranked by relevance using hybrid full-text + semantic search. Use this to find insights, decisions, patterns, or any saved context. Each result includes an `id` you can use with save_context or delete_context.",
|
|
67
67
|
{
|
|
68
|
-
query: z.string().describe("Search query (natural language or keywords)"),
|
|
68
|
+
query: z.string().optional().describe("Search query (natural language or keywords). Optional if filters (tags, kind, category) are provided."),
|
|
69
69
|
kind: z.string().optional().describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
|
|
70
70
|
category: z.enum(["knowledge", "entity", "event"]).optional().describe("Filter by category"),
|
|
71
71
|
tags: z.array(z.string()).optional().describe("Filter by tags (entries must match at least one)"),
|
|
@@ -74,38 +74,69 @@ export function registerTools(server, ctx) {
|
|
|
74
74
|
limit: z.number().optional().describe("Max results to return (default 10)"),
|
|
75
75
|
},
|
|
76
76
|
async ({ query, kind, category, tags, since, until, limit }) => {
|
|
77
|
-
|
|
77
|
+
const hasQuery = query?.trim();
|
|
78
|
+
const hasFilters = kind || category || tags?.length || since || until;
|
|
79
|
+
if (!hasQuery && !hasFilters) return err("Required: query or at least one filter (kind, category, tags, since, until)", "INVALID_INPUT");
|
|
78
80
|
await ensureIndexed();
|
|
79
81
|
|
|
80
82
|
const kindFilter = kind ? normalizeKind(kind) : null;
|
|
81
|
-
const sorted = await hybridSearch(ctx, query, {
|
|
82
|
-
kindFilter,
|
|
83
|
-
categoryFilter: category || null,
|
|
84
|
-
since: since || null,
|
|
85
|
-
until: until || null,
|
|
86
|
-
limit: limit || 10,
|
|
87
|
-
});
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
let filtered;
|
|
85
|
+
if (hasQuery) {
|
|
86
|
+
// Hybrid search mode
|
|
87
|
+
const sorted = await hybridSearch(ctx, query, {
|
|
88
|
+
kindFilter,
|
|
89
|
+
categoryFilter: category || null,
|
|
90
|
+
since: since || null,
|
|
91
|
+
until: until || null,
|
|
92
|
+
limit: limit || 10,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Post-filter by tags if provided
|
|
96
|
+
filtered = tags?.length
|
|
97
|
+
? sorted.filter((r) => {
|
|
98
|
+
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
99
|
+
return tags.some((t) => entryTags.includes(t));
|
|
100
|
+
})
|
|
101
|
+
: sorted;
|
|
102
|
+
} else {
|
|
103
|
+
// Filter-only mode (no query, use SQL directly)
|
|
104
|
+
const clauses = [];
|
|
105
|
+
const params = [];
|
|
106
|
+
if (kindFilter) { clauses.push("kind = ?"); params.push(kindFilter); }
|
|
107
|
+
if (category) { clauses.push("category = ?"); params.push(category); }
|
|
108
|
+
if (since) { clauses.push("created_at >= ?"); params.push(since); }
|
|
109
|
+
if (until) { clauses.push("created_at <= ?"); params.push(until); }
|
|
110
|
+
clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
|
|
111
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
112
|
+
const effectiveLimit = limit || 10;
|
|
113
|
+
params.push(effectiveLimit);
|
|
114
|
+
const rows = ctx.db.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT ?`).all(...params);
|
|
115
|
+
|
|
116
|
+
filtered = tags?.length
|
|
117
|
+
? rows.filter((r) => {
|
|
118
|
+
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
119
|
+
return tags.some((t) => entryTags.includes(t));
|
|
120
|
+
})
|
|
121
|
+
: rows;
|
|
122
|
+
|
|
123
|
+
// Add score field for consistent output
|
|
124
|
+
for (const r of filtered) r.score = 0;
|
|
125
|
+
}
|
|
96
126
|
|
|
97
|
-
if (!filtered.length) return ok("No results found for: " + query);
|
|
127
|
+
if (!filtered.length) return ok(hasQuery ? "No results found for: " + query : "No entries found matching the given filters.");
|
|
98
128
|
|
|
99
129
|
const lines = [];
|
|
100
130
|
if (reindexFailed) lines.push(`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-mcp reindex\` to fix.\n`);
|
|
101
|
-
|
|
131
|
+
const heading = hasQuery ? `Results for "${query}"` : "Filtered entries";
|
|
132
|
+
lines.push(`## ${heading} (${filtered.length} matches)\n`);
|
|
102
133
|
for (let i = 0; i < filtered.length; i++) {
|
|
103
134
|
const r = filtered[i];
|
|
104
135
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
105
136
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
106
137
|
const relPath = r.file_path && config.vaultDir ? r.file_path.replace(config.vaultDir + "/", "") : r.file_path || "n/a";
|
|
107
138
|
lines.push(`### [${i + 1}/${filtered.length}] ${r.title || "(untitled)"} [${r.kind}/${r.category}]`);
|
|
108
|
-
lines.push(`${r.score.toFixed(3)} · ${tagStr} · ${relPath}
|
|
139
|
+
lines.push(`${r.score.toFixed(3)} · ${tagStr} · ${relPath} · id: \`${r.id}\``);
|
|
109
140
|
lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? "..." : ""));
|
|
110
141
|
lines.push("");
|
|
111
142
|
}
|
|
@@ -117,7 +148,7 @@ export function registerTools(server, ctx) {
|
|
|
117
148
|
|
|
118
149
|
server.tool(
|
|
119
150
|
"save_context",
|
|
120
|
-
"Save knowledge to your vault. Creates a .md file and indexes it for search. Use for any kind of context: insights, decisions, patterns, references, or any custom kind.",
|
|
151
|
+
"Save knowledge to your vault. Creates a .md file and indexes it for search. Use for any kind of context: insights, decisions, patterns, references, or any custom kind. To update an existing entry, pass its `id` — omitted fields are preserved.",
|
|
121
152
|
{
|
|
122
153
|
id: z.string().optional().describe("Entry ULID to update. When provided, updates the existing entry instead of creating new. Omitted fields are preserved."),
|
|
123
154
|
kind: z.string().optional().describe("Entry kind — determines folder (e.g. 'insight', 'decision', 'pattern', 'reference', or any custom kind). Required for new entries."),
|
|
@@ -155,6 +186,7 @@ export function registerTools(server, ctx) {
|
|
|
155
186
|
if (entry.title) parts.push(` title: ${entry.title}`);
|
|
156
187
|
const entryTags = entry.tags || [];
|
|
157
188
|
if (entryTags.length) parts.push(` tags: ${entryTags.join(", ")}`);
|
|
189
|
+
parts.push("", "_Search with get_context to verify changes._");
|
|
158
190
|
return ok(parts.join("\n"));
|
|
159
191
|
}
|
|
160
192
|
|
|
@@ -179,6 +211,7 @@ export function registerTools(server, ctx) {
|
|
|
179
211
|
const parts = [`✓ Saved ${kind} → ${relPath}`, ` id: ${entry.id}`];
|
|
180
212
|
if (title) parts.push(` title: ${title}`);
|
|
181
213
|
if (tags?.length) parts.push(` tags: ${tags.join(", ")}`);
|
|
214
|
+
parts.push("", "_Use this id to update or delete later._");
|
|
182
215
|
return ok(parts.join("\n"));
|
|
183
216
|
}
|
|
184
217
|
);
|
|
@@ -187,7 +220,7 @@ export function registerTools(server, ctx) {
|
|
|
187
220
|
|
|
188
221
|
server.tool(
|
|
189
222
|
"list_context",
|
|
190
|
-
"Browse vault entries without a search query. Returns id, title, kind, category, tags, created_at. Use get_context with a query for semantic search.",
|
|
223
|
+
"Browse vault entries without a search query. Returns id, title, kind, category, tags, created_at. Use get_context with a query for semantic search. Use this to browse by tags or find recent entries.",
|
|
191
224
|
{
|
|
192
225
|
kind: z.string().optional().describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
|
|
193
226
|
category: z.enum(["knowledge", "entity", "event"]).optional().describe("Filter by category"),
|
|
@@ -229,7 +262,7 @@ export function registerTools(server, ctx) {
|
|
|
229
262
|
const total = ctx.db.prepare(`SELECT COUNT(*) as c FROM vault ${where}`).get(...countParams).c;
|
|
230
263
|
|
|
231
264
|
params.push(effectiveLimit, effectiveOffset);
|
|
232
|
-
const rows = ctx.db.prepare(`SELECT id, title, kind, category, tags, created_at FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params);
|
|
265
|
+
const rows = ctx.db.prepare(`SELECT id, title, kind, category, tags, created_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params);
|
|
233
266
|
|
|
234
267
|
// Post-filter by tags if provided
|
|
235
268
|
const filtered = tags?.length
|
|
@@ -246,6 +279,7 @@ export function registerTools(server, ctx) {
|
|
|
246
279
|
const entryTags = r.tags ? JSON.parse(r.tags) : [];
|
|
247
280
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
248
281
|
lines.push(`- **${r.title || "(untitled)"}** [${r.kind}/${r.category}] — ${tagStr} — ${r.created_at} — \`${r.id}\``);
|
|
282
|
+
if (r.preview) lines.push(` ${r.preview.replace(/\n+/g, " ").trim()}${r.preview.length >= 120 ? "…" : ""}`);
|
|
249
283
|
}
|
|
250
284
|
|
|
251
285
|
if (effectiveOffset + effectiveLimit < total) {
|
|
@@ -289,11 +323,47 @@ export function registerTools(server, ctx) {
|
|
|
289
323
|
}
|
|
290
324
|
);
|
|
291
325
|
|
|
326
|
+
// ─── submit_feedback (bug/feature reports) ────────────────────────────────
|
|
327
|
+
|
|
328
|
+
server.tool(
|
|
329
|
+
"submit_feedback",
|
|
330
|
+
"Report a bug, request a feature, or suggest an improvement. Feedback is stored in the vault and triaged by the development pipeline.",
|
|
331
|
+
{
|
|
332
|
+
type: z.enum(["bug", "feature", "improvement"]).describe("Type of feedback"),
|
|
333
|
+
title: z.string().describe("Short summary of the feedback"),
|
|
334
|
+
body: z.string().describe("Detailed description"),
|
|
335
|
+
severity: z.enum(["low", "medium", "high"]).optional().describe("Severity level (default: medium)"),
|
|
336
|
+
},
|
|
337
|
+
async ({ type, title, body, severity }) => {
|
|
338
|
+
const vaultErr = ensureVaultExists(config);
|
|
339
|
+
if (vaultErr) return vaultErr;
|
|
340
|
+
|
|
341
|
+
await ensureIndexed();
|
|
342
|
+
|
|
343
|
+
const effectiveSeverity = severity || "medium";
|
|
344
|
+
const entry = await captureAndIndex(
|
|
345
|
+
ctx,
|
|
346
|
+
{
|
|
347
|
+
kind: "feedback",
|
|
348
|
+
title,
|
|
349
|
+
body,
|
|
350
|
+
tags: [type, effectiveSeverity],
|
|
351
|
+
source: "submit_feedback",
|
|
352
|
+
meta: { feedback_type: type, severity: effectiveSeverity, status: "new" },
|
|
353
|
+
},
|
|
354
|
+
indexEntry
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const relPath = entry.filePath ? entry.filePath.replace(config.vaultDir + "/", "") : entry.filePath;
|
|
358
|
+
return ok(`Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`);
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
|
|
292
362
|
// ─── context_status (diagnostics) ──────────────────────────────────────────
|
|
293
363
|
|
|
294
364
|
server.tool(
|
|
295
365
|
"context_status",
|
|
296
|
-
"Show vault health: resolved config, file counts per kind, database size, and any issues. Use to verify setup or troubleshoot.",
|
|
366
|
+
"Show vault health: resolved config, file counts per kind, database size, and any issues. Use to verify setup or troubleshoot. Call this when a user asks about their vault or to debug search issues.",
|
|
297
367
|
{},
|
|
298
368
|
() => {
|
|
299
369
|
const status = gatherVaultStatus(ctx);
|
|
@@ -346,6 +416,17 @@ export function registerTools(server, ctx) {
|
|
|
346
416
|
lines.push(`Auto-reindex will fix this on next search or save.`);
|
|
347
417
|
}
|
|
348
418
|
|
|
419
|
+
// Suggested actions
|
|
420
|
+
const actions = [];
|
|
421
|
+
if (status.stalePaths) actions.push("- Run `context-mcp reindex` to fix stale paths");
|
|
422
|
+
if (status.embeddingStatus?.missing > 0) actions.push("- Run `context-mcp reindex` to generate missing embeddings");
|
|
423
|
+
if (!config.vaultDirExists) actions.push("- Run `context-mcp setup` to create the vault directory");
|
|
424
|
+
if (status.kindCounts.length === 0 && config.vaultDirExists) actions.push("- Use `save_context` to add your first entry");
|
|
425
|
+
|
|
426
|
+
if (actions.length) {
|
|
427
|
+
lines.push("", "### Suggested Actions", ...actions);
|
|
428
|
+
}
|
|
429
|
+
|
|
349
430
|
return ok(lines.join("\n"));
|
|
350
431
|
}
|
|
351
432
|
);
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
7
7
|
"context-mcp": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "src/server/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prepack": "mkdir -p node_modules/@context-vault && rm -rf node_modules/@context-vault/core && cp -rL ../core node_modules/@context-vault/core && rm -rf node_modules/@context-vault/core/node_modules"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"bin/",
|
|
12
15
|
"src/",
|
|
13
16
|
"ui/",
|
|
14
17
|
"README.md",
|
|
15
|
-
"LICENSE"
|
|
16
|
-
"smithery.yaml"
|
|
18
|
+
"LICENSE"
|
|
17
19
|
],
|
|
18
20
|
"license": "MIT",
|
|
19
21
|
"engines": { "node": ">=20" },
|
|
@@ -21,17 +23,8 @@
|
|
|
21
23
|
"repository": { "type": "git", "url": "https://github.com/fellanH/context-mcp.git" },
|
|
22
24
|
"homepage": "https://github.com/fellanH/context-mcp",
|
|
23
25
|
"keywords": ["mcp", "model-context-protocol", "ai", "knowledge-base", "knowledge-management", "vault", "rag", "sqlite", "embeddings", "claude", "cursor", "cline", "windsurf"],
|
|
24
|
-
"
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:watch": "vitest"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"vitest": "^3.0.0"
|
|
30
|
-
},
|
|
26
|
+
"bundledDependencies": ["@context-vault/core"],
|
|
31
27
|
"dependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
34
|
-
"better-sqlite3": "^12.6.2",
|
|
35
|
-
"sqlite-vec": "^0.1.0"
|
|
28
|
+
"@context-vault/core": "^2.3.0"
|
|
36
29
|
}
|
|
37
30
|
}
|
package/src/server/index.js
CHANGED
|
@@ -9,10 +9,10 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
11
11
|
|
|
12
|
-
import { resolveConfig } from "
|
|
13
|
-
import { embed } from "
|
|
14
|
-
import { initDatabase, prepareStatements, insertVec, deleteVec } from "
|
|
15
|
-
import { registerTools } from "
|
|
12
|
+
import { resolveConfig } from "@context-vault/core/core/config";
|
|
13
|
+
import { embed } from "@context-vault/core/index/embed";
|
|
14
|
+
import { initDatabase, prepareStatements, insertVec, deleteVec } from "@context-vault/core/index/db";
|
|
15
|
+
import { registerTools } from "@context-vault/core/server/tools";
|
|
16
16
|
|
|
17
17
|
// ─── Config Resolution ──────────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -80,3 +80,20 @@ process.on("SIGTERM", shutdown);
|
|
|
80
80
|
|
|
81
81
|
const transport = new StdioServerTransport();
|
|
82
82
|
await server.connect(transport);
|
|
83
|
+
|
|
84
|
+
// ─── Non-blocking Update Check ──────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
import("node:child_process").then(({ execSync }) => {
|
|
88
|
+
try {
|
|
89
|
+
const latest = execSync("npm view context-vault version", {
|
|
90
|
+
encoding: "utf-8",
|
|
91
|
+
timeout: 5000,
|
|
92
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
93
|
+
}).trim();
|
|
94
|
+
if (latest && latest !== pkg.version) {
|
|
95
|
+
console.error(`[context-mcp] Update available: v${pkg.version} → v${latest}. Run: context-mcp update`);
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
}).catch(() => {});
|
|
99
|
+
}, 3000);
|
package/ui/serve.js
CHANGED
|
@@ -12,10 +12,10 @@ import { createServer } from "node:http";
|
|
|
12
12
|
import { readFileSync, writeFileSync, existsSync, statSync, readdirSync } from "node:fs";
|
|
13
13
|
import { resolve, dirname, join, basename } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
-
import { resolveConfig } from "
|
|
16
|
-
import { initDatabase } from "
|
|
17
|
-
import { embed } from "
|
|
18
|
-
import { hybridSearch } from "
|
|
15
|
+
import { resolveConfig } from "@context-vault/core/core/config";
|
|
16
|
+
import { initDatabase } from "@context-vault/core/index/db";
|
|
17
|
+
import { embed } from "@context-vault/core/index/embed";
|
|
18
|
+
import { hybridSearch } from "@context-vault/core/retrieve";
|
|
19
19
|
|
|
20
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
21
|
|
|
@@ -348,8 +348,9 @@ async function handleSearch(res, url) {
|
|
|
348
348
|
|
|
349
349
|
if (!query) return jsonResponse(res, { results: [] });
|
|
350
350
|
|
|
351
|
-
const
|
|
352
|
-
|
|
351
|
+
const searchCtx = { db, config, embed };
|
|
352
|
+
const results = await hybridSearch(searchCtx, query, { kindFilter });
|
|
353
|
+
jsonResponse(res, { query, results });
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
// ─── Config Handlers ─────────────────────────────────────────────────────────
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Felix Hellstrom
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|