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.
- aikb-0.0.2/.claude/CLAUDE.md +30 -0
- aikb-0.0.2/.claude/skills/aikb-maintain/SKILL.md +92 -0
- aikb-0.0.2/.claude/skills/aikb-setup/SKILL.md +164 -0
- aikb-0.0.2/.claude/skills/aikb-sync/SKILL.md +133 -0
- aikb-0.0.2/.claude/skills/aikb-sync/scripts/sync_folder.py +115 -0
- aikb-0.0.2/.gitattributes +1 -0
- aikb-0.0.2/.github/workflows/ci.yml +256 -0
- aikb-0.0.2/.gitignore +121 -0
- aikb-0.0.2/LICENSE +21 -0
- aikb-0.0.2/PKG-INFO +160 -0
- aikb-0.0.2/README.md +129 -0
- aikb-0.0.2/aikb/__init__.py +11 -0
- aikb-0.0.2/aikb/base.py +283 -0
- aikb-0.0.2/aikb/mcp_server.py +71 -0
- aikb-0.0.2/misc/docs/Programmatic CRUD for AI project knowledge bases.md +210 -0
- aikb-0.0.2/pyproject.toml +183 -0
- aikb-0.0.2/tests/test_aikb.py +168 -0
|
@@ -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
|