aikb 0.0.2__tar.gz

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.
@@ -0,0 +1,30 @@
1
+ # aikb — Project Instructions
2
+
3
+ ## What this is
4
+
5
+ `aikb` provides programmatic CRUD for AI project knowledge bases (Claude Projects, Gemini Gems) via a `MutableMapping` interface compatible with `dol`.
6
+
7
+ ## Architecture
8
+
9
+ Three-layer design in `aikb/base.py`:
10
+ - **Provider protocol** (`KnowledgeBaseProvider`) — structural typing, no inheritance
11
+ - **Store facade** (`KnowledgeFiles`) — `collections.abc.MutableMapping`
12
+ - **Factory functions** (`LocalFiles`, `ClaudeProject`) — progressive disclosure
13
+
14
+ Optional `aikb/mcp_server.py` exposes CRUD as MCP tools.
15
+
16
+ ## Skills
17
+
18
+ Load the relevant skill when working in these areas:
19
+
20
+ - **Maintaining/extending aikb code**: Read `.claude/skills/aikb-maintain/SKILL.md`
21
+ - **Helping users install/configure aikb**: Read `.claude/skills/aikb-setup/SKILL.md`
22
+ - **Syncing files between platforms**: Read `.claude/skills/aikb-sync/SKILL.md`
23
+
24
+ ## Key conventions
25
+
26
+ - Provider methods raise `KeyError` on missing files
27
+ - `list_files` / `__iter__` yield (generators, not lists)
28
+ - Optional deps (`claudesync`, `fastmcp`) are lazy-imported via `_check_dependency()`
29
+ - All arguments after the first positional are keyword-only
30
+ - Zero core dependencies — extras for `claude`, `mcp`, `dol`
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: aikb-maintain
3
+ description: "Use when maintaining, extending, or debugging the aikb codebase. Covers adding new providers, modifying the store facade, updating tests, and architectural decisions."
4
+ ---
5
+
6
+ # aikb: Codebase Maintenance
7
+
8
+ Use this skill when modifying or extending the aikb package itself.
9
+
10
+ ## Architecture
11
+
12
+ aikb has three layers:
13
+
14
+ 1. **Provider protocol** (`KnowledgeBaseProvider`) — structural typing via `Protocol`
15
+ 2. **Store facade** (`KnowledgeFiles`) — `MutableMapping` wrapping any provider
16
+ 3. **Factory functions** (`LocalFiles`, `ClaudeProject`) — progressive disclosure entry points
17
+
18
+ All core code lives in `aikb/base.py`. The MCP server is in `aikb/mcp_server.py`.
19
+
20
+ ## Key files
21
+
22
+ | File | Purpose |
23
+ |------|---------|
24
+ | `aikb/base.py` | Protocol, KnowledgeFiles, all providers, KnowledgeMall, factories |
25
+ | `aikb/__init__.py` | Public API exports only |
26
+ | `aikb/mcp_server.py` | FastMCP server (gated behind `aikb[mcp]`) |
27
+ | `tests/test_aikb.py` | pytest suite |
28
+ | `pyproject.toml` | Build config, optional deps groups |
29
+
30
+ ## Adding a new provider
31
+
32
+ 1. Create a class in `aikb/base.py` implementing the four protocol methods:
33
+
34
+ ```python
35
+ class NewPlatformProvider:
36
+ def __init__(self, ...):
37
+ ...
38
+
39
+ def list_files(self, project_id: str) -> Iterator[str]:
40
+ ... # yield filenames
41
+
42
+ def read_file(self, project_id: str, filename: str) -> str:
43
+ ... # return content or raise KeyError
44
+
45
+ def upsert_file(self, project_id: str, filename: str, content: str) -> None:
46
+ ...
47
+
48
+ def delete_file(self, project_id: str, filename: str) -> None:
49
+ ... # raise KeyError if not found
50
+ ```
51
+
52
+ 2. Add a factory function below the existing ones:
53
+
54
+ ```python
55
+ def NewPlatform(project_id: str, *, ...) -> KnowledgeFiles:
56
+ return KnowledgeFiles(NewPlatformProvider(...), project_id=project_id)
57
+ ```
58
+
59
+ 3. Export from `aikb/__init__.py`.
60
+ 4. Add to the `_get_store` dispatch in `aikb/mcp_server.py`.
61
+ 5. Add optional dep group in `pyproject.toml` if needed.
62
+ 6. Add tests in `tests/test_aikb.py`.
63
+
64
+ ## Design rules
65
+
66
+ - **Provider methods raise `KeyError`** on missing files, not `FileNotFoundError`.
67
+ - **`list_files` and `__iter__` yield** — never return lists.
68
+ - **Optional deps are lazy-imported** at method-call time, not at module import. Use `_check_dependency()` to give informative install hints.
69
+ - **`functools.cached_property`** for expensive client initialization (e.g., API clients).
70
+ - **No inheritance required** for providers — the `KnowledgeBaseProvider` Protocol uses structural subtyping.
71
+ - All arguments after the first positional MUST be keyword-only.
72
+
73
+ ## Testing
74
+
75
+ ```bash
76
+ pytest tests/ -v # unit tests
77
+ pytest --doctest-modules aikb/ # doctests
78
+ ```
79
+
80
+ - `LocalFilesProvider` tests use the `tmp_path` fixture (no cleanup needed).
81
+ - Provider tests for external platforms use `pytest.importorskip()`.
82
+ - Integration tests requiring real credentials should be marked `@pytest.mark.integration`.
83
+
84
+ ## Extending KnowledgeMall
85
+
86
+ `KnowledgeMall` is a `Mapping[str, KnowledgeFiles]`. To add declarative config-based construction, add a `@classmethod` factory. Keep the constructor signature simple: `KnowledgeMall(dict_or_kwargs)`.
87
+
88
+ ## MCP server
89
+
90
+ The MCP server is a thin dispatch layer. When adding a new platform:
91
+ 1. Add an `elif` branch to `_get_store()` in `mcp_server.py`
92
+ 2. The tool functions themselves don't change — they delegate to `_get_store()`
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: aikb-setup
3
+ description: "Use when helping a user install, configure, or start using aikb. Covers installation, authentication setup, first-use walkthroughs, and troubleshooting."
4
+ ---
5
+
6
+ # aikb: Setup and Usage Guide
7
+
8
+ Use this skill when helping someone install aikb, configure authentication, or get started with their first knowledge base operations.
9
+
10
+ ## Installation
11
+
12
+ ### Basic (local files only, zero extra deps)
13
+
14
+ ```bash
15
+ pip install aikb
16
+ ```
17
+
18
+ ### With Claude Projects support
19
+
20
+ ```bash
21
+ pip install aikb[claude]
22
+ ```
23
+
24
+ ### With MCP server
25
+
26
+ ```bash
27
+ pip install aikb[mcp]
28
+ ```
29
+
30
+ ### Everything
31
+
32
+ ```bash
33
+ pip install aikb[all]
34
+ ```
35
+
36
+ ## First use: Local files
37
+
38
+ The fastest way to try aikb — no configuration needed:
39
+
40
+ ```python
41
+ from aikb import LocalFiles
42
+
43
+ store = LocalFiles('/path/to/knowledge_base')
44
+
45
+ # Write files
46
+ store['context.md'] = '# Project Context\nThis project does...'
47
+ store['api-notes.md'] = '# API Notes\nEndpoint documentation...'
48
+
49
+ # List files
50
+ print(list(store)) # ['api-notes.md', 'context.md']
51
+
52
+ # Read a file
53
+ print(store['context.md'])
54
+
55
+ # Delete a file
56
+ del store['api-notes.md']
57
+ ```
58
+
59
+ Files are stored as UTF-8 text in `{rootdir}/{project_id}/`. The default `project_id` is `'default'`.
60
+
61
+ ## Setting up Claude Projects access
62
+
63
+ ### Step 1: Get your session key
64
+
65
+ 1. Open https://claude.ai in your browser
66
+ 2. Open Developer Tools (F12) → Application → Cookies
67
+ 3. Copy the value of the `sessionKey` cookie (starts with `sk-ant-`)
68
+
69
+ ### Step 2: Configure authentication
70
+
71
+ **Option A — Environment variable (recommended):**
72
+
73
+ ```bash
74
+ export CLAUDE_SESSION_KEY='sk-ant-sid01-...'
75
+ ```
76
+
77
+ Then in Python:
78
+
79
+ ```python
80
+ from aikb import ClaudeProject
81
+ store = ClaudeProject('your-project-uuid')
82
+ ```
83
+
84
+ **Option B — Explicit parameter:**
85
+
86
+ ```python
87
+ store = ClaudeProject('your-project-uuid', session_key='sk-ant-sid01-...')
88
+ ```
89
+
90
+ **Option C — ClaudeSync config:**
91
+
92
+ If you've already configured ClaudeSync (`claudesync auth login`), aikb will use its stored credentials automatically.
93
+
94
+ ### Step 3: Find your project UUID
95
+
96
+ Project UUIDs are visible in the Claude.ai URL when you open a project:
97
+ `https://claude.ai/project/{project-uuid}`
98
+
99
+ ## Setting up the MCP server
100
+
101
+ ### For Claude Desktop
102
+
103
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "aikb": {
109
+ "command": "python",
110
+ "args": ["-m", "aikb.mcp_server"]
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ ### For Claude Code
117
+
118
+ ```bash
119
+ claude mcp add aikb -- python -m aikb.mcp_server
120
+ ```
121
+
122
+ ### Standalone
123
+
124
+ ```bash
125
+ python -m aikb.mcp_server
126
+ ```
127
+
128
+ ## Multi-project setup
129
+
130
+ ```python
131
+ from aikb import LocalFiles, ClaudeProject, KnowledgeMall
132
+
133
+ mall = KnowledgeMall(
134
+ local=LocalFiles('/tmp/kb'),
135
+ project_a=ClaudeProject('uuid-a'),
136
+ project_b=ClaudeProject('uuid-b'),
137
+ )
138
+
139
+ # Access by name
140
+ list(mall['local'])
141
+ mall['project_a']['file.md'] = 'content'
142
+ ```
143
+
144
+ ## Troubleshooting
145
+
146
+ ### `ImportError: 'claudesync' is required`
147
+
148
+ Install the Claude extras: `pip install aikb[claude]`
149
+
150
+ ### `ImportError: 'fastmcp' is required`
151
+
152
+ Install the MCP extras: `pip install aikb[mcp]`
153
+
154
+ ### `KeyError` when reading a file
155
+
156
+ The file doesn't exist in the project. Use `list(store)` to see available files, or `'filename' in store` to check.
157
+
158
+ ### Session key expired (403 errors)
159
+
160
+ Claude.ai session keys expire periodically. Get a fresh one from your browser cookies and update `CLAUDE_SESSION_KEY`.
161
+
162
+ ### `ConnectionError` / `429` rate limiting
163
+
164
+ ClaudeSync communicates with Claude.ai's internal API. Retry after a few seconds. Avoid rapid bulk operations.
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: aikb-sync
3
+ description: "Use when syncing knowledge files between platforms or directories — e.g., pushing local files to Claude Projects, mirroring between projects, or batch-updating a knowledge base from a folder of markdown files."
4
+ ---
5
+
6
+ # aikb: Sync Knowledge Files
7
+
8
+ Use this skill when syncing, mirroring, or batch-managing knowledge files across platforms or directories.
9
+
10
+ ## Sync patterns
11
+
12
+ ### Push local files to Claude Project
13
+
14
+ ```python
15
+ from aikb import LocalFiles, ClaudeProject
16
+
17
+ local = LocalFiles('/path/to/docs')
18
+ remote = ClaudeProject('project-uuid')
19
+
20
+ # Push all files
21
+ remote.update(local)
22
+
23
+ # Push specific files
24
+ for name in ['context.md', 'api.md']:
25
+ remote[name] = local[name]
26
+ ```
27
+
28
+ ### Pull from Claude Project to local
29
+
30
+ ```python
31
+ local.update(remote)
32
+ ```
33
+
34
+ ### Mirror between two projects
35
+
36
+ ```python
37
+ proj_a = ClaudeProject('uuid-a')
38
+ proj_b = ClaudeProject('uuid-b')
39
+ proj_b.update(proj_a)
40
+ ```
41
+
42
+ ### Selective sync with filtering
43
+
44
+ ```python
45
+ # Only markdown files
46
+ for name in local:
47
+ if name.endswith('.md'):
48
+ remote[name] = local[name]
49
+
50
+ # Only files that differ
51
+ for name in local:
52
+ if name not in remote or remote[name] != local[name]:
53
+ remote[name] = local[name]
54
+ ```
55
+
56
+ ### Delete remote files not present locally
57
+
58
+ ```python
59
+ remote_only = set(remote) - set(local)
60
+ for name in remote_only:
61
+ del remote[name]
62
+ ```
63
+
64
+ ## Helper script: sync_folder.py
65
+
66
+ The script at `.claude/skills/aikb-sync/scripts/sync_folder.py` provides a
67
+ ready-made sync operation:
68
+
69
+ ```bash
70
+ # Dry run (show what would change)
71
+ python .claude/skills/aikb-sync/scripts/sync_folder.py \
72
+ /path/to/local/docs project-uuid --dry-run
73
+
74
+ # Actual sync
75
+ python .claude/skills/aikb-sync/scripts/sync_folder.py \
76
+ /path/to/local/docs project-uuid
77
+
78
+ # With explicit session key
79
+ python .claude/skills/aikb-sync/scripts/sync_folder.py \
80
+ /path/to/local/docs project-uuid --session-key 'sk-ant-...'
81
+
82
+ # Only .md files
83
+ python .claude/skills/aikb-sync/scripts/sync_folder.py \
84
+ /path/to/local/docs project-uuid --glob '*.md'
85
+ ```
86
+
87
+ ## Workflow: Keep Claude Project in sync with a repo folder
88
+
89
+ A common pattern is keeping a `docs/` or `knowledge/` folder in your repo
90
+ synced with a Claude Project:
91
+
92
+ ```python
93
+ from pathlib import Path
94
+ from aikb import LocalFiles, ClaudeProject
95
+
96
+ local = LocalFiles('knowledge') # ./knowledge/default/
97
+ remote = ClaudeProject('project-uuid')
98
+
99
+ # Full bidirectional diff
100
+ local_files = set(local)
101
+ remote_files = set(remote)
102
+
103
+ added = local_files - remote_files
104
+ removed = remote_files - local_files
105
+ common = local_files & remote_files
106
+ changed = {f for f in common if local[f] != remote[f]}
107
+
108
+ print(f"To add: {added}")
109
+ print(f"To remove: {removed}")
110
+ print(f"Changed: {changed}")
111
+
112
+ # Apply
113
+ for f in added | changed:
114
+ remote[f] = local[f]
115
+ for f in removed:
116
+ del remote[f]
117
+ ```
118
+
119
+ ## Using KnowledgeMall for named sync pairs
120
+
121
+ ```python
122
+ from aikb import LocalFiles, ClaudeProject, KnowledgeMall
123
+
124
+ mall = KnowledgeMall(
125
+ staging=LocalFiles('/tmp/staging'),
126
+ prod=ClaudeProject('project-uuid'),
127
+ )
128
+
129
+ # Draft locally, review, then push
130
+ mall['staging']['notes.md'] = '# Updated notes\n...'
131
+ print(mall['staging']['notes.md']) # review
132
+ mall['prod']['notes.md'] = mall['staging']['notes.md'] # push
133
+ ```
@@ -0,0 +1,115 @@
1
+ """Sync a local folder to a Claude Project knowledge base.
2
+
3
+ Usage:
4
+ python sync_folder.py /path/to/docs PROJECT_UUID [--dry-run] [--session-key SK] [--glob PATTERN]
5
+
6
+ Examples:
7
+ # Dry run
8
+ python sync_folder.py ./knowledge proj-uuid --dry-run
9
+
10
+ # Actual sync
11
+ python sync_folder.py ./knowledge proj-uuid
12
+
13
+ # Only .md files
14
+ python sync_folder.py ./knowledge proj-uuid --glob '*.md'
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import fnmatch
20
+ import sys
21
+
22
+
23
+ def sync_folder(
24
+ local_dir: str,
25
+ project_id: str,
26
+ *,
27
+ session_key: str | None = None,
28
+ glob_pattern: str | None = None,
29
+ dry_run: bool = False,
30
+ delete_remote_extras: bool = False,
31
+ ):
32
+ """Sync local files to a Claude Project.
33
+
34
+ Returns a dict summarizing what was (or would be) done.
35
+ """
36
+ from aikb import LocalFiles, ClaudeProject
37
+
38
+ local = LocalFiles(local_dir, project_id="default")
39
+ remote = ClaudeProject(project_id, session_key=session_key)
40
+
41
+ local_files = set(local)
42
+ if glob_pattern:
43
+ local_files = {f for f in local_files if fnmatch.fnmatch(f, glob_pattern)}
44
+
45
+ remote_files = set(remote)
46
+
47
+ to_add = local_files - remote_files
48
+ to_remove = remote_files - local_files if delete_remote_extras else set()
49
+ common = local_files & remote_files
50
+ to_update = {f for f in common if local[f] != remote[f]}
51
+ unchanged = common - to_update
52
+
53
+ summary = {
54
+ "add": sorted(to_add),
55
+ "update": sorted(to_update),
56
+ "remove": sorted(to_remove),
57
+ "unchanged": sorted(unchanged),
58
+ }
59
+
60
+ if dry_run:
61
+ print("=== DRY RUN ===")
62
+ for action, files in summary.items():
63
+ if files:
64
+ print(f"\n{action.upper()} ({len(files)}):")
65
+ for f in files:
66
+ print(f" {f}")
67
+ if not any(summary[k] for k in ("add", "update", "remove")):
68
+ print("\nNothing to do — already in sync.")
69
+ return summary
70
+
71
+ for f in to_add | to_update:
72
+ print(f" {'ADD' if f in to_add else 'UPD'} {f}")
73
+ remote[f] = local[f]
74
+
75
+ for f in to_remove:
76
+ print(f" DEL {f}")
77
+ del remote[f]
78
+
79
+ total = len(to_add) + len(to_update) + len(to_remove)
80
+ print(f"\nDone: {total} change(s) applied.")
81
+ return summary
82
+
83
+
84
+ def main():
85
+ import argparse
86
+
87
+ parser = argparse.ArgumentParser(
88
+ description="Sync a local folder to a Claude Project knowledge base."
89
+ )
90
+ parser.add_argument("local_dir", help="Path to local folder")
91
+ parser.add_argument("project_id", help="Claude Project UUID")
92
+ parser.add_argument("--session-key", help="Claude session key")
93
+ parser.add_argument(
94
+ "--glob", dest="glob_pattern", help="Filter files by glob pattern"
95
+ )
96
+ parser.add_argument("--dry-run", action="store_true", help="Show what would change")
97
+ parser.add_argument(
98
+ "--delete-remote-extras",
99
+ action="store_true",
100
+ help="Delete remote files not present locally",
101
+ )
102
+ args = parser.parse_args()
103
+
104
+ sync_folder(
105
+ args.local_dir,
106
+ args.project_id,
107
+ session_key=args.session_key,
108
+ glob_pattern=args.glob_pattern,
109
+ dry_run=args.dry_run,
110
+ delete_remote_extras=args.delete_remote_extras,
111
+ )
112
+
113
+
114
+ if __name__ == "__main__":
115
+ main()
@@ -0,0 +1 @@
1
+ *.ipynb linguist-documentation