@hebbianvault/mcp 0.2.0 → 0.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/README.md +35 -1
- package/dist/absorb/cli.d.ts +25 -0
- package/dist/absorb/cli.d.ts.map +1 -0
- package/dist/absorb/cli.js +140 -0
- package/dist/absorb/cli.js.map +1 -0
- package/dist/absorb/importers.d.ts +53 -0
- package/dist/absorb/importers.d.ts.map +1 -0
- package/dist/absorb/importers.js +132 -0
- package/dist/absorb/importers.js.map +1 -0
- package/dist/absorb/secrets.d.ts +34 -0
- package/dist/absorb/secrets.d.ts.map +1 -0
- package/dist/absorb/secrets.js +93 -0
- package/dist/absorb/secrets.js.map +1 -0
- package/dist/client.d.ts +4 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +44 -22
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -9
- package/dist/index.js.map +1 -1
- package/dist/package_info.d.ts +2 -0
- package/dist/package_info.d.ts.map +1 -0
- package/dist/package_info.js +6 -0
- package/dist/package_info.js.map +1 -0
- package/dist/server_info.d.ts +3 -0
- package/dist/server_info.d.ts.map +1 -0
- package/dist/server_info.js +4 -0
- package/dist/server_info.js.map +1 -0
- package/dist/tools/ask.d.ts.map +1 -1
- package/dist/tools/ask.js +4 -2
- package/dist/tools/ask.js.map +1 -1
- package/dist/tools/context.d.ts +25 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +84 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/graph_helpers.d.ts +1 -0
- package/dist/tools/graph_helpers.d.ts.map +1 -1
- package/dist/tools/graph_helpers.js +26 -2
- package/dist/tools/graph_helpers.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/provenance.d.ts.map +1 -1
- package/dist/tools/provenance.js +5 -3
- package/dist/tools/provenance.js.map +1 -1
- package/dist/tools/read_node.d.ts.map +1 -1
- package/dist/tools/read_node.js +4 -2
- package/dist/tools/read_node.js.map +1 -1
- package/dist/tools/recent_activity.d.ts.map +1 -1
- package/dist/tools/recent_activity.js +4 -2
- package/dist/tools/recent_activity.js.map +1 -1
- package/dist/tools/salience.d.ts.map +1 -1
- package/dist/tools/salience.js +4 -2
- package/dist/tools/salience.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +4 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/traverse.d.ts.map +1 -1
- package/dist/tools/traverse.js +5 -3
- package/dist/tools/traverse.js.map +1 -1
- package/dist/tools/untrusted_content.d.ts +20 -0
- package/dist/tools/untrusted_content.d.ts.map +1 -0
- package/dist/tools/untrusted_content.js +59 -0
- package/dist/tools/untrusted_content.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ Write `~/.config/hebbian/mcp-tenant.json`:
|
|
|
78
78
|
| `HEBBIAN_API_TOKEN` | Your token (required). `HEBBIAN_TOKEN` is also accepted. |
|
|
79
79
|
| `HEBBIAN_API_URL` | Override the API base URL (Enterprise self-host). Defaults to the Hebbian SaaS API. |
|
|
80
80
|
| `HEBBIAN_TENANT` | Optional workspace slug — only needed if your account belongs to more than one workspace. |
|
|
81
|
+
| `HEBBIAN_GRAPH_PAGINATION` | Opt in to paginated workspace graph retrieval (`1`, `true`, `yes`, or `on`). |
|
|
81
82
|
|
|
82
83
|
## Token scope
|
|
83
84
|
|
|
@@ -90,6 +91,7 @@ Your token decides what the adapter can see and do — a personal-workspace toke
|
|
|
90
91
|
| `hebbian_read_node` | Read a single node by UUID (content, metadata, connected edges) |
|
|
91
92
|
| `hebbian_search` | Find nodes in your workspace matching a query |
|
|
92
93
|
| `hebbian_ask` | Ask a question and get an answer backed by source quotes |
|
|
94
|
+
| `hebbian_context` | Describe a task and a token budget, get back a salience-ranked context pack that fits |
|
|
93
95
|
| `hebbian_capture` | Write a note into your workspace |
|
|
94
96
|
| `hebbian_traverse` | Explore nodes connected to a starting node |
|
|
95
97
|
| `hebbian_provenance` | See where a node's knowledge came from |
|
|
@@ -98,6 +100,38 @@ Your token decides what the adapter can see and do — a personal-workspace toke
|
|
|
98
100
|
|
|
99
101
|
Results only ever include what your token is allowed to see.
|
|
100
102
|
|
|
103
|
+
## Onboard an agent you already use
|
|
104
|
+
|
|
105
|
+
Every agent you already run carries its own context: a Claude Code memory directory, CLAUDE.md files, a folder of markdown notes. The `absorb` command brings that accumulated context into Hebbian, so an agent you have used for months arrives with what it already knows instead of a blank slate.
|
|
106
|
+
|
|
107
|
+
Absorbed files land as seeds in your review lane (they do not write straight into the workspace), attributed to the agent principal you name. You review and promote them like any other proposed knowledge.
|
|
108
|
+
|
|
109
|
+
First, register the agent and issue it a token from your Hebbian integrations page (AI Tools tab), and note its agent id. Then point `absorb` at the store:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# A Claude Code memory directory (MEMORY.md index + CLAUDE.md files + notes)
|
|
113
|
+
HEBBIAN_API_TOKEN=your_agent_token \
|
|
114
|
+
npx -y @hebbianvault/mcp absorb claude-code ~/.claude/projects/my-project --agent <agent_id>
|
|
115
|
+
|
|
116
|
+
# Any directory of markdown files
|
|
117
|
+
HEBBIAN_API_TOKEN=your_agent_token \
|
|
118
|
+
npx -y @hebbianvault/mcp absorb markdown ./docs --agent <agent_id>
|
|
119
|
+
|
|
120
|
+
# Walk and report what would be uploaded, without uploading
|
|
121
|
+
npx -y @hebbianvault/mcp absorb claude-code ~/.claude/projects/my-project --dry-run
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Each `*.md` file becomes one seed. The title is the file's first heading (or its name), and the source identifier is its path relative to the directory you pointed at. Re-running `absorb` on the same directory is safe: a file already absorbed for that agent is reported as a duplicate, not uploaded twice.
|
|
125
|
+
|
|
126
|
+
### What never leaves your machine
|
|
127
|
+
|
|
128
|
+
`absorb` excludes secrets before anything is sent:
|
|
129
|
+
|
|
130
|
+
- Files whose names look like credentials are skipped entirely: `.env*`, anything with `credentials`, `secret`, or `token` in the name, plus `.pem`, `.key`, and SSH key files.
|
|
131
|
+
- Token-shaped strings inside the files it does read are redacted before upload: `sk-...`, `ghp_...`, `hbn_...`, Slack and AWS keys, JWTs, long hex and base64 blobs, and Bearer header values.
|
|
132
|
+
|
|
133
|
+
Every run prints a summary of how many files were skipped and how many strings were redacted, so you can see exactly what was filtered.
|
|
134
|
+
|
|
101
135
|
## Security
|
|
102
136
|
|
|
103
137
|
- Tokens are bearer credentials — treat like passwords.
|
|
@@ -107,7 +141,7 @@ Results only ever include what your token is allowed to see.
|
|
|
107
141
|
|
|
108
142
|
## Python
|
|
109
143
|
|
|
110
|
-
A Python sibling package is available as `hebbianvault-mcp` on PyPI. Same
|
|
144
|
+
A Python sibling package is available as `hebbianvault-mcp` on PyPI. Same 9 tools, same configuration.
|
|
111
145
|
|
|
112
146
|
## Development
|
|
113
147
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/cli.ts
|
|
3
|
+
*
|
|
4
|
+
* The `absorb` command (ADR-055 §4). Onboard an agent you already use by
|
|
5
|
+
* uploading its existing context store as review-lane seeds.
|
|
6
|
+
*
|
|
7
|
+
* hebbian-mcp absorb claude-code <dir> --agent <agent_id>
|
|
8
|
+
* hebbian-mcp absorb markdown <dir> --agent <agent_id>
|
|
9
|
+
*
|
|
10
|
+
* Reads *.md files locally (Claude Code memory dir: MEMORY.md index + CLAUDE.md
|
|
11
|
+
* files; generic markdown: any *.md), skips credential-named files, redacts
|
|
12
|
+
* token-shaped strings, then batches the items to
|
|
13
|
+
* POST /v1/agents/{agent_id}/absorb with the agent token from the existing
|
|
14
|
+
* config/env convention (config.ts).
|
|
15
|
+
*
|
|
16
|
+
* --dry-run walks and reports without uploading. The auth token + API URL come
|
|
17
|
+
* from HEBBIAN_API_TOKEN / config file exactly like the MCP server.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Run the absorb command. Returns the process exit code (0 ok, non-zero error).
|
|
21
|
+
* Reads the store, prints the skipped/redacted summary, then (unless --dry-run)
|
|
22
|
+
* uploads in batches and prints the per-batch tally.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runAbsorb(argv: string[]): Promise<number>;
|
|
25
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/absorb/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA6FH;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsE/D"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/cli.ts
|
|
3
|
+
*
|
|
4
|
+
* The `absorb` command (ADR-055 §4). Onboard an agent you already use by
|
|
5
|
+
* uploading its existing context store as review-lane seeds.
|
|
6
|
+
*
|
|
7
|
+
* hebbian-mcp absorb claude-code <dir> --agent <agent_id>
|
|
8
|
+
* hebbian-mcp absorb markdown <dir> --agent <agent_id>
|
|
9
|
+
*
|
|
10
|
+
* Reads *.md files locally (Claude Code memory dir: MEMORY.md index + CLAUDE.md
|
|
11
|
+
* files; generic markdown: any *.md), skips credential-named files, redacts
|
|
12
|
+
* token-shaped strings, then batches the items to
|
|
13
|
+
* POST /v1/agents/{agent_id}/absorb with the agent token from the existing
|
|
14
|
+
* config/env convention (config.ts).
|
|
15
|
+
*
|
|
16
|
+
* --dry-run walks and reports without uploading. The auth token + API URL come
|
|
17
|
+
* from HEBBIAN_API_TOKEN / config file exactly like the MCP server.
|
|
18
|
+
*/
|
|
19
|
+
import { HebbianClient } from "../client.js";
|
|
20
|
+
import { loadConfig } from "../config.js";
|
|
21
|
+
import { isSupportedStore, scanDirectory, supportedStores, } from "./importers.js";
|
|
22
|
+
/** Server-side batch cap (ADR-055 §2). Keep in sync with MAX_BATCH_ITEMS. */
|
|
23
|
+
const BATCH_SIZE = 200;
|
|
24
|
+
function parseFlags(argv) {
|
|
25
|
+
const positionals = [];
|
|
26
|
+
let agentId;
|
|
27
|
+
let dryRun = false;
|
|
28
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === "--agent" || a === "--agent-id") {
|
|
31
|
+
agentId = argv[i + 1];
|
|
32
|
+
i += 1;
|
|
33
|
+
}
|
|
34
|
+
else if (a.startsWith("--agent=")) {
|
|
35
|
+
agentId = a.slice("--agent=".length);
|
|
36
|
+
}
|
|
37
|
+
else if (a === "--dry-run") {
|
|
38
|
+
dryRun = true;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
positionals.push(a);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { agentId, dryRun, positionals };
|
|
45
|
+
}
|
|
46
|
+
const USAGE = [
|
|
47
|
+
"Usage: hebbian-mcp absorb <store> <dir> --agent <agent_id> [--dry-run]",
|
|
48
|
+
"",
|
|
49
|
+
` store one of: ${supportedStores().join(", ")}`,
|
|
50
|
+
" dir path to the context store directory",
|
|
51
|
+
" --agent the agent principal UUID to absorb into (the agent you already use)",
|
|
52
|
+
" --dry-run walk and report; do not upload",
|
|
53
|
+
"",
|
|
54
|
+
"Auth: set HEBBIAN_API_TOKEN to the agent's token (or use the config file).",
|
|
55
|
+
"Reads *.md files, skips credential-named files, redacts token-shaped strings,",
|
|
56
|
+
"then uploads each file as a review-lane seed attributed to the agent.",
|
|
57
|
+
].join("\n");
|
|
58
|
+
/** Resolve + validate args. Returns null (after printing usage) on a usage error. */
|
|
59
|
+
function resolveArgs(argv) {
|
|
60
|
+
const { agentId, dryRun, positionals } = parseFlags(argv);
|
|
61
|
+
const [store, dir] = positionals;
|
|
62
|
+
if (!store || !dir) {
|
|
63
|
+
process.stderr.write(`${USAGE}\n`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!isSupportedStore(store)) {
|
|
67
|
+
process.stderr.write(`absorb: unsupported store "${store}". Supported: ${supportedStores().join(", ")}\n`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (!dryRun && !agentId) {
|
|
71
|
+
process.stderr.write("absorb: --agent <agent_id> is required (or use --dry-run)\n");
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return { store, dir, agentId: agentId ?? "", dryRun };
|
|
75
|
+
}
|
|
76
|
+
function chunk(arr, size) {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (let i = 0; i < arr.length; i += size)
|
|
79
|
+
out.push(arr.slice(i, i + size));
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Run the absorb command. Returns the process exit code (0 ok, non-zero error).
|
|
84
|
+
* Reads the store, prints the skipped/redacted summary, then (unless --dry-run)
|
|
85
|
+
* uploads in batches and prints the per-batch tally.
|
|
86
|
+
*/
|
|
87
|
+
export async function runAbsorb(argv) {
|
|
88
|
+
const args = resolveArgs(argv);
|
|
89
|
+
if (args === null)
|
|
90
|
+
return 2;
|
|
91
|
+
const scan = scanDirectory(args.dir, args.store);
|
|
92
|
+
// Honest summary line (ADR-055 §4): how many files, skipped secrets, redactions.
|
|
93
|
+
process.stderr.write(`[absorb] store=${args.store} dir=${args.dir}: ` +
|
|
94
|
+
`${scan.items.length} file(s) to absorb, ` +
|
|
95
|
+
`skipped ${scan.skippedSecretFiles.length} credential-named file(s), ` +
|
|
96
|
+
`redacted ${scan.redactedSecrets} token-shaped string(s) across ${scan.redactedItems} file(s)\n`);
|
|
97
|
+
if (scan.skippedSecretFiles.length > 0) {
|
|
98
|
+
for (const f of scan.skippedSecretFiles) {
|
|
99
|
+
process.stderr.write(`[absorb] skipped (looks like secrets): ${f}\n`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (scan.items.length === 0) {
|
|
103
|
+
process.stderr.write("[absorb] nothing to absorb.\n");
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (args.dryRun) {
|
|
107
|
+
process.stderr.write(`[absorb] dry-run: would upload ${scan.items.length} item(s) to ` +
|
|
108
|
+
`/v1/agents/${args.agentId || "<agent_id>"}/absorb in ` +
|
|
109
|
+
`${chunk(scan.items, BATCH_SIZE).length} batch(es). No upload performed.\n`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const config = loadConfig();
|
|
113
|
+
const client = new HebbianClient(config.apiUrl, config.token, config.tenant, config.graphPagination);
|
|
114
|
+
let accepted = 0;
|
|
115
|
+
let duplicates = 0;
|
|
116
|
+
let errors = 0;
|
|
117
|
+
const batches = chunk(scan.items, BATCH_SIZE);
|
|
118
|
+
for (let i = 0; i < batches.length; i += 1) {
|
|
119
|
+
const batch = batches[i];
|
|
120
|
+
try {
|
|
121
|
+
const resp = (await client.post(`/v1/agents/${args.agentId}/absorb`, {
|
|
122
|
+
items: batch,
|
|
123
|
+
}));
|
|
124
|
+
accepted += resp.accepted ?? 0;
|
|
125
|
+
duplicates += resp.duplicates ?? 0;
|
|
126
|
+
errors += resp.errors ?? 0;
|
|
127
|
+
process.stderr.write(`[absorb] batch ${i + 1}/${batches.length}: ` +
|
|
128
|
+
`accepted ${resp.accepted ?? 0}, duplicate ${resp.duplicates ?? 0}, error ${resp.errors ?? 0}\n`);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
132
|
+
process.stderr.write(`[absorb] batch ${i + 1}/${batches.length} failed: ${message}\n`);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
process.stderr.write(`[absorb] done: ${accepted} new seed(s), ${duplicates} already absorbed, ${errors} error(s). ` +
|
|
137
|
+
"New seeds await review in the Hebbian review lane.\n");
|
|
138
|
+
return errors > 0 ? 1 : 0;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/absorb/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/importers.ts
|
|
3
|
+
*
|
|
4
|
+
* Local importers for the absorb command (ADR-055 §4). An importer walks a
|
|
5
|
+
* directory, reads the files relevant to one store kind, and turns each into an
|
|
6
|
+
* AbsorbItem. It NEVER posts anything — the CLI batches the items to the API.
|
|
7
|
+
*
|
|
8
|
+
* Two importers ship in this slice:
|
|
9
|
+
* claude-code — a Claude Code memory directory: every *.md file, including
|
|
10
|
+
* the MEMORY.md index and any CLAUDE.md files.
|
|
11
|
+
* markdown — generic catch-all: walk a directory tree for every *.md file.
|
|
12
|
+
*
|
|
13
|
+
* Both reuse the same secret guards (secrets.ts): a credential-named file is
|
|
14
|
+
* skipped, and token-shaped strings are redacted from the content before it is
|
|
15
|
+
* handed back. Each importer reports skipped/redacted counts so the CLI can
|
|
16
|
+
* print an honest summary.
|
|
17
|
+
*/
|
|
18
|
+
/** One memory item ready to absorb. Matches the API's AbsorbItem shape. */
|
|
19
|
+
export interface AbsorbItem {
|
|
20
|
+
store_kind: string;
|
|
21
|
+
/** Stable identifier within the store — the file's path relative to the root. */
|
|
22
|
+
source_id: string;
|
|
23
|
+
/** First markdown heading, else the filename. */
|
|
24
|
+
title: string;
|
|
25
|
+
/** Redacted file content. */
|
|
26
|
+
content: string;
|
|
27
|
+
/** Filesystem birthtime (ISO-8601), best-effort. */
|
|
28
|
+
created_at?: string;
|
|
29
|
+
/** Filesystem mtime (ISO-8601). */
|
|
30
|
+
updated_at?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Outcome of walking a directory: items to upload + what was skipped/redacted. */
|
|
33
|
+
export interface ScanResult {
|
|
34
|
+
items: AbsorbItem[];
|
|
35
|
+
skippedSecretFiles: string[];
|
|
36
|
+
redactedItems: number;
|
|
37
|
+
redactedSecrets: number;
|
|
38
|
+
}
|
|
39
|
+
declare const SUPPORTED_STORES: readonly ["claude-code", "markdown"];
|
|
40
|
+
export type StoreKind = (typeof SUPPORTED_STORES)[number];
|
|
41
|
+
export declare function isSupportedStore(kind: string): kind is StoreKind;
|
|
42
|
+
export declare function supportedStores(): readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* Walk a directory and build absorb items for one store kind.
|
|
45
|
+
*
|
|
46
|
+
* Shared by both importers — they differ only in the store_kind tag and (for
|
|
47
|
+
* documentation) the expectation of what lives in the directory; the file
|
|
48
|
+
* selection (every *.md, including MEMORY.md and CLAUDE.md) is identical, which
|
|
49
|
+
* is exactly what a Claude Code memory directory contains.
|
|
50
|
+
*/
|
|
51
|
+
export declare function scanDirectory(root: string, storeKind: string): ScanResult;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=importers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importers.d.ts","sourceRoot":"","sources":["../../src/absorb/importers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAeD,QAAA,MAAM,gBAAgB,sCAAuC,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,SAAS,CAEhE;AAED,wBAAgB,eAAe,IAAI,SAAS,MAAM,EAAE,CAEnD;AA4CD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAwCzE"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/importers.ts
|
|
3
|
+
*
|
|
4
|
+
* Local importers for the absorb command (ADR-055 §4). An importer walks a
|
|
5
|
+
* directory, reads the files relevant to one store kind, and turns each into an
|
|
6
|
+
* AbsorbItem. It NEVER posts anything — the CLI batches the items to the API.
|
|
7
|
+
*
|
|
8
|
+
* Two importers ship in this slice:
|
|
9
|
+
* claude-code — a Claude Code memory directory: every *.md file, including
|
|
10
|
+
* the MEMORY.md index and any CLAUDE.md files.
|
|
11
|
+
* markdown — generic catch-all: walk a directory tree for every *.md file.
|
|
12
|
+
*
|
|
13
|
+
* Both reuse the same secret guards (secrets.ts): a credential-named file is
|
|
14
|
+
* skipped, and token-shaped strings are redacted from the content before it is
|
|
15
|
+
* handed back. Each importer reports skipped/redacted counts so the CLI can
|
|
16
|
+
* print an honest summary.
|
|
17
|
+
*/
|
|
18
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
19
|
+
import { join, relative, sep } from "node:path";
|
|
20
|
+
import { redactSecrets, shouldSkipFile } from "./secrets.js";
|
|
21
|
+
/** Directories never descended into (noise + secret stores). */
|
|
22
|
+
const SKIP_DIRS = new Set([
|
|
23
|
+
"node_modules",
|
|
24
|
+
".git",
|
|
25
|
+
".venv",
|
|
26
|
+
"venv",
|
|
27
|
+
"__pycache__",
|
|
28
|
+
"dist",
|
|
29
|
+
"build",
|
|
30
|
+
".next",
|
|
31
|
+
".cache",
|
|
32
|
+
]);
|
|
33
|
+
const SUPPORTED_STORES = ["claude-code", "markdown"];
|
|
34
|
+
export function isSupportedStore(kind) {
|
|
35
|
+
return SUPPORTED_STORES.includes(kind);
|
|
36
|
+
}
|
|
37
|
+
export function supportedStores() {
|
|
38
|
+
return SUPPORTED_STORES;
|
|
39
|
+
}
|
|
40
|
+
/** Recursively collect every *.md file under root (relative paths). */
|
|
41
|
+
function collectMarkdownFiles(root) {
|
|
42
|
+
const out = [];
|
|
43
|
+
const walk = (dir) => {
|
|
44
|
+
let entries;
|
|
45
|
+
try {
|
|
46
|
+
entries = readdirSync(dir);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return; // unreadable dir — skip quietly
|
|
50
|
+
}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const abs = join(dir, entry);
|
|
53
|
+
let st;
|
|
54
|
+
try {
|
|
55
|
+
st = statSync(abs);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (st.isDirectory()) {
|
|
61
|
+
if (!SKIP_DIRS.has(entry))
|
|
62
|
+
walk(abs);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (st.isFile() && entry.toLowerCase().endsWith(".md")) {
|
|
66
|
+
out.push(relative(root, abs));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
walk(root);
|
|
71
|
+
return out.sort();
|
|
72
|
+
}
|
|
73
|
+
/** First markdown heading in the content, else the basename without extension. */
|
|
74
|
+
function deriveTitle(content, relPath) {
|
|
75
|
+
for (const raw of content.split("\n")) {
|
|
76
|
+
const line = raw.trim();
|
|
77
|
+
const m = /^#{1,6}\s+(.+?)\s*#*$/.exec(line);
|
|
78
|
+
if (m && m[1])
|
|
79
|
+
return m[1].trim();
|
|
80
|
+
}
|
|
81
|
+
const base = relPath.split(/[\\/]/).pop() ?? relPath;
|
|
82
|
+
return base.replace(/\.md$/i, "");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Walk a directory and build absorb items for one store kind.
|
|
86
|
+
*
|
|
87
|
+
* Shared by both importers — they differ only in the store_kind tag and (for
|
|
88
|
+
* documentation) the expectation of what lives in the directory; the file
|
|
89
|
+
* selection (every *.md, including MEMORY.md and CLAUDE.md) is identical, which
|
|
90
|
+
* is exactly what a Claude Code memory directory contains.
|
|
91
|
+
*/
|
|
92
|
+
export function scanDirectory(root, storeKind) {
|
|
93
|
+
const rel = collectMarkdownFiles(root);
|
|
94
|
+
const items = [];
|
|
95
|
+
const skippedSecretFiles = [];
|
|
96
|
+
let redactedItems = 0;
|
|
97
|
+
let redactedSecrets = 0;
|
|
98
|
+
for (const relPath of rel) {
|
|
99
|
+
const base = relPath.split(/[\\/]/).pop() ?? relPath;
|
|
100
|
+
if (shouldSkipFile(base)) {
|
|
101
|
+
skippedSecretFiles.push(relPath);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const abs = join(root, relPath);
|
|
105
|
+
let raw;
|
|
106
|
+
let st;
|
|
107
|
+
try {
|
|
108
|
+
raw = readFileSync(abs, "utf-8");
|
|
109
|
+
st = statSync(abs);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const { content, redactedCount } = redactSecrets(raw);
|
|
115
|
+
if (redactedCount > 0) {
|
|
116
|
+
redactedItems += 1;
|
|
117
|
+
redactedSecrets += redactedCount;
|
|
118
|
+
}
|
|
119
|
+
// Normalise source_id to forward slashes so it is stable across OSes.
|
|
120
|
+
const sourceId = relPath.split(sep).join("/");
|
|
121
|
+
items.push({
|
|
122
|
+
store_kind: storeKind,
|
|
123
|
+
source_id: sourceId,
|
|
124
|
+
title: deriveTitle(content, relPath),
|
|
125
|
+
content,
|
|
126
|
+
created_at: st.birthtime?.toISOString?.(),
|
|
127
|
+
updated_at: st.mtime?.toISOString?.(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { items, skippedSecretFiles, redactedItems, redactedSecrets };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=importers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"importers.js","sourceRoot":"","sources":["../../src/absorb/importers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/secrets.ts
|
|
3
|
+
*
|
|
4
|
+
* Secret exclusion + redaction for the absorb importers (ADR-055 §4).
|
|
5
|
+
*
|
|
6
|
+
* Two layers, both non-negotiable:
|
|
7
|
+
* 1. File exclusion — never read a file whose name looks like it holds
|
|
8
|
+
* credentials (.env*, *credentials*, *secret*, *token*-named files).
|
|
9
|
+
* 2. Content redaction — even in a file we DO read, replace token-shaped
|
|
10
|
+
* strings (long base64/hex, sk-..., ghp_..., hbn_..., AWS keys, JWTs)
|
|
11
|
+
* with a [REDACTED] marker before the content ever leaves the machine.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here talks to the network. The importer calls shouldSkipFile() per
|
|
14
|
+
* file and redactSecrets() on every body it is about to upload.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* True when a file should be skipped entirely because its NAME indicates it
|
|
18
|
+
* holds credentials. Matches on the basename only (path is ignored), case-
|
|
19
|
+
* insensitively.
|
|
20
|
+
*/
|
|
21
|
+
export declare function shouldSkipFile(filename: string): boolean;
|
|
22
|
+
/** Result of redacting a string: the cleaned text + how many secrets were hit. */
|
|
23
|
+
export interface RedactionResult {
|
|
24
|
+
content: string;
|
|
25
|
+
redactedCount: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Redact token-shaped strings from content. Returns the cleaned content and the
|
|
29
|
+
* number of distinct matches replaced. Idempotent on already-clean text
|
|
30
|
+
* (redactedCount === 0). The Bearer rule keeps the leading "Bearer " word so the
|
|
31
|
+
* surrounding prose still reads sensibly.
|
|
32
|
+
*/
|
|
33
|
+
export declare function redactSecrets(content: string): RedactionResult;
|
|
34
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/absorb/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqDH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAa9D"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/absorb/secrets.ts
|
|
3
|
+
*
|
|
4
|
+
* Secret exclusion + redaction for the absorb importers (ADR-055 §4).
|
|
5
|
+
*
|
|
6
|
+
* Two layers, both non-negotiable:
|
|
7
|
+
* 1. File exclusion — never read a file whose name looks like it holds
|
|
8
|
+
* credentials (.env*, *credentials*, *secret*, *token*-named files).
|
|
9
|
+
* 2. Content redaction — even in a file we DO read, replace token-shaped
|
|
10
|
+
* strings (long base64/hex, sk-..., ghp_..., hbn_..., AWS keys, JWTs)
|
|
11
|
+
* with a [REDACTED] marker before the content ever leaves the machine.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here talks to the network. The importer calls shouldSkipFile() per
|
|
14
|
+
* file and redactSecrets() on every body it is about to upload.
|
|
15
|
+
*/
|
|
16
|
+
/** A file basename matches one of these → skip it entirely. */
|
|
17
|
+
const SKIP_NAME_PATTERNS = [
|
|
18
|
+
/^\.env(\..*)?$/i, // .env, .env.local, .env.production, …
|
|
19
|
+
/credentials/i, // aws credentials, gcloud credentials, *credentials*.json
|
|
20
|
+
/secret/i, // secrets.yaml, my-secret.md, *secret*
|
|
21
|
+
/(^|[._-])tokens?([._-]|$)/i, // token.txt, api-tokens.json, auth_token.md
|
|
22
|
+
/\.pem$/i, // private keys
|
|
23
|
+
/\.key$/i,
|
|
24
|
+
/id_rsa/i, // ssh keys
|
|
25
|
+
/\.p12$/i,
|
|
26
|
+
/\.pfx$/i,
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Token-shaped substrings to redact from content. Ordered specific → generic so
|
|
30
|
+
* a prefixed key (sk-…, ghp_…) is matched by its own rule before the generic
|
|
31
|
+
* long-base64 catch-all. Each match is replaced with [REDACTED].
|
|
32
|
+
*/
|
|
33
|
+
const REDACT_PATTERNS = [
|
|
34
|
+
// OpenAI / Anthropic style: sk-… , sk-ant-…
|
|
35
|
+
/\bsk-[A-Za-z0-9_-]{16,}\b/g,
|
|
36
|
+
// GitHub tokens: ghp_, gho_, ghu_, ghs_, ghr_, github_pat_
|
|
37
|
+
/\bgh[opusr]_[A-Za-z0-9]{16,}\b/g,
|
|
38
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
39
|
+
// Hebbian tokens.
|
|
40
|
+
/\bhbn_[A-Za-z0-9_-]{16,}\b/g,
|
|
41
|
+
// Slack tokens: xoxb-, xoxp-, xapp-…
|
|
42
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
43
|
+
// AWS access key id.
|
|
44
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
|
45
|
+
// Google API key.
|
|
46
|
+
/\bAIza[A-Za-z0-9_-]{30,}\b/g,
|
|
47
|
+
// JWT (three base64url segments).
|
|
48
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
|
|
49
|
+
// Bearer headers — redact the value, keep the word.
|
|
50
|
+
/\b([Bb]earer\s+)[A-Za-z0-9._-]{20,}\b/g,
|
|
51
|
+
// URL-embedded credentials: scheme://user:password@host (postgres://, redis://,
|
|
52
|
+
// amqp://, mongodb://, …). Redact the password, keep scheme/user/host so the
|
|
53
|
+
// surrounding prose still reads sensibly.
|
|
54
|
+
/\b([a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\/\s]*:)[^@\s]{3,}(?=@)/g,
|
|
55
|
+
// Supabase secret / personal-access keys.
|
|
56
|
+
/\bsb_secret_[A-Za-z0-9_-]{10,}\b/g,
|
|
57
|
+
/\bsbp_[A-Za-z0-9]{20,}\b/g,
|
|
58
|
+
// Generic long hex (>= 40 chars) — sha-like / hex secrets.
|
|
59
|
+
/\b[0-9a-fA-F]{40,}\b/g,
|
|
60
|
+
// Generic long base64-ish blob (>= 40 chars). Last so prefixed keys win first.
|
|
61
|
+
/\b[A-Za-z0-9+/]{40,}={0,2}\b/g,
|
|
62
|
+
];
|
|
63
|
+
const REDACTION_MARKER = "[REDACTED]";
|
|
64
|
+
/**
|
|
65
|
+
* True when a file should be skipped entirely because its NAME indicates it
|
|
66
|
+
* holds credentials. Matches on the basename only (path is ignored), case-
|
|
67
|
+
* insensitively.
|
|
68
|
+
*/
|
|
69
|
+
export function shouldSkipFile(filename) {
|
|
70
|
+
const base = filename.split(/[\\/]/).pop() ?? filename;
|
|
71
|
+
return SKIP_NAME_PATTERNS.some((re) => re.test(base));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Redact token-shaped strings from content. Returns the cleaned content and the
|
|
75
|
+
* number of distinct matches replaced. Idempotent on already-clean text
|
|
76
|
+
* (redactedCount === 0). The Bearer rule keeps the leading "Bearer " word so the
|
|
77
|
+
* surrounding prose still reads sensibly.
|
|
78
|
+
*/
|
|
79
|
+
export function redactSecrets(content) {
|
|
80
|
+
let redactedCount = 0;
|
|
81
|
+
let out = content;
|
|
82
|
+
for (const re of REDACT_PATTERNS) {
|
|
83
|
+
out = out.replace(re, (match, ...groups) => {
|
|
84
|
+
redactedCount += 1;
|
|
85
|
+
// Rules with a capture group ("Bearer ", "scheme://user:") keep the prefix
|
|
86
|
+
// so the surrounding prose still reads sensibly; only the secret is replaced.
|
|
87
|
+
const prefix = typeof groups[0] === "string" ? groups[0] : "";
|
|
88
|
+
return `${prefix}${REDACTION_MARKER}`;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return { content: out, redactedCount };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/absorb/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+DAA+D;AAC/D,MAAM,kBAAkB,GAAa;IACnC,iBAAiB,EAAE,uCAAuC;IAC1D,cAAc,EAAE,0DAA0D;IAC1E,SAAS,EAAE,uCAAuC;IAClD,4BAA4B,EAAE,4CAA4C;IAC1E,SAAS,EAAE,eAAe;IAC1B,SAAS;IACT,SAAS,EAAE,WAAW;IACtB,SAAS;IACT,SAAS;CACV,CAAC;AAEF;;;;GAIG;AACH,MAAM,eAAe,GAAa;IAChC,4CAA4C;IAC5C,4BAA4B;IAC5B,2DAA2D;IAC3D,iCAAiC;IACjC,mCAAmC;IACnC,kBAAkB;IAClB,6BAA6B;IAC7B,qCAAqC;IACrC,mCAAmC;IACnC,qBAAqB;IACrB,gCAAgC;IAChC,kBAAkB;IAClB,6BAA6B;IAC7B,kCAAkC;IAClC,iEAAiE;IACjE,oDAAoD;IACpD,wCAAwC;IACxC,gFAAgF;IAChF,6EAA6E;IAC7E,0CAA0C;IAC1C,6DAA6D;IAC7D,0CAA0C;IAC1C,mCAAmC;IACnC,2BAA2B;IAC3B,2DAA2D;IAC3D,uBAAuB;IACvB,+EAA+E;IAC/E,+BAA+B;CAChC,CAAC;AAEF,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAEtC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,QAAQ,CAAC;IACvD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACxD,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,GAAG,GAAG,OAAO,CAAC;IAClB,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC;QACjC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,MAAM,EAAE,EAAE;YACzC,aAAa,IAAI,CAAC,CAAC;YACnB,2EAA2E;YAC3E,8EAA8E;YAC9E,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO,GAAG,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC"}
|
package/dist/client.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ export declare class HebbianClient {
|
|
|
21
21
|
private readonly apiUrl;
|
|
22
22
|
private readonly token;
|
|
23
23
|
private readonly tenant?;
|
|
24
|
-
|
|
24
|
+
/** Whether graph-derived tools should request UUID-keyset paginated graphs. */
|
|
25
|
+
readonly graphPagination: boolean;
|
|
26
|
+
constructor(apiUrl: string, token: string, tenant?: string, graphPagination?: boolean);
|
|
25
27
|
/**
|
|
26
28
|
* Execute a GET request against the Hebbian API.
|
|
27
29
|
* @param path Path relative to apiUrl (must start with "/")
|
|
@@ -35,9 +37,8 @@ export declare class HebbianClient {
|
|
|
35
37
|
*/
|
|
36
38
|
post(path: string, body: Record<string, unknown>): Promise<unknown>;
|
|
37
39
|
/** Build an absolute URL from path + optional query params. */
|
|
38
|
-
private buildUrl;
|
|
39
|
-
/** Standard request headers including bearer auth. */
|
|
40
40
|
private headers;
|
|
41
|
+
private buildUrl;
|
|
41
42
|
/**
|
|
42
43
|
* Parse and validate a fetch response.
|
|
43
44
|
* Throws HebbianApiError on non-2xx status codes.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,2EAA2E;AAC3E,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,UAAU,EAAE,MAAM;aAClB,SAAS,EAAE,MAAM;aAEjB,MAAM,CAAC,EAAE,OAAO;gBAHhB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjC,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,OAAO,YAAA;IAMlC,0DAA0D;IAC1D,WAAW,IAAI,MAAM;CAsBtB;AA6BD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,+EAA+E;IAC/E,SAAgB,eAAe,EAAE,OAAO,CAAC;gBAE7B,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,UAAQ;IAQnF;;;;OAIG;IACG,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC5D,OAAO,CAAC,OAAO,CAAC;IASnB;;;;OAIG;IACG,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC;IAanB,+DAA+D;IAC/D,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,QAAQ;IAehB;;;OAGG;YACW,cAAc;CAiC7B"}
|