RepoPackPy 0.1.9__py3-none-any.whl → 0.2.0__py3-none-any.whl
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.
- repopack/__init__.py +1 -1
- repopack/cli.py +57 -1
- repopack/mcp_server.py +40 -1
- repopack/packer.py +70 -0
- {repopackpy-0.1.9.dist-info → repopackpy-0.2.0.dist-info}/METADATA +51 -4
- repopackpy-0.2.0.dist-info/RECORD +11 -0
- repopackpy-0.1.9.dist-info/RECORD +0 -11
- {repopackpy-0.1.9.dist-info → repopackpy-0.2.0.dist-info}/WHEEL +0 -0
- {repopackpy-0.1.9.dist-info → repopackpy-0.2.0.dist-info}/entry_points.txt +0 -0
- {repopackpy-0.1.9.dist-info → repopackpy-0.2.0.dist-info}/licenses/LICENSE +0 -0
repopack/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.2.0"
|
repopack/cli.py
CHANGED
|
@@ -5,7 +5,7 @@ import typer
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from repopack import __version__
|
|
8
|
-
from repopack.packer import pack_workspace
|
|
8
|
+
from repopack.packer import fix_workspace, pack_workspace, validate_workspace
|
|
9
9
|
from repopack.unpacker import unpack_workspace
|
|
10
10
|
from repopack.mcp_server import run_server
|
|
11
11
|
|
|
@@ -109,6 +109,62 @@ def unpack(
|
|
|
109
109
|
raise typer.Exit(1)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
@app.command()
|
|
113
|
+
def validate(
|
|
114
|
+
json_file: str = typer.Argument(..., help="Packed JSON file to validate."),
|
|
115
|
+
directory: str = typer.Argument(".", help="Workspace directory to compare against."),
|
|
116
|
+
):
|
|
117
|
+
"""Check whether a packed JSON is complete and up to date with the workspace."""
|
|
118
|
+
try:
|
|
119
|
+
report = validate_workspace(json_file, Path(directory))
|
|
120
|
+
s = report["summary"]
|
|
121
|
+
status = "OK" if report["valid"] else "OUTDATED"
|
|
122
|
+
typer.echo(f"Status : {status}")
|
|
123
|
+
typer.echo(f"JSON : {report['json_path']}")
|
|
124
|
+
typer.echo(f"Workspace: {report['workspace']}")
|
|
125
|
+
typer.echo(f" Missing from JSON : {s['missing_from_json']}")
|
|
126
|
+
typer.echo(f" Extra in JSON : {s['extra_in_json']}")
|
|
127
|
+
typer.echo(f" Stale in JSON : {s['stale_in_json']}")
|
|
128
|
+
typer.echo(f" Up to date : {s['up_to_date']}")
|
|
129
|
+
if report["missing_from_json"]:
|
|
130
|
+
typer.echo("\nMissing (in workspace, not in JSON):")
|
|
131
|
+
for p in report["missing_from_json"]:
|
|
132
|
+
typer.echo(f" + {p}")
|
|
133
|
+
if report["extra_in_json"]:
|
|
134
|
+
typer.echo("\nExtra (in JSON, not in workspace):")
|
|
135
|
+
for p in report["extra_in_json"]:
|
|
136
|
+
typer.echo(f" - {p}")
|
|
137
|
+
if report["stale_in_json"]:
|
|
138
|
+
typer.echo("\nStale (content changed since packing):")
|
|
139
|
+
for p in report["stale_in_json"]:
|
|
140
|
+
typer.echo(f" ~ {p}")
|
|
141
|
+
if not report["valid"]:
|
|
142
|
+
raise typer.Exit(1)
|
|
143
|
+
except (ValueError, OSError) as e:
|
|
144
|
+
typer.echo(str(e), err=True)
|
|
145
|
+
raise typer.Exit(1)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.command()
|
|
149
|
+
def fix(
|
|
150
|
+
json_file: str = typer.Argument(..., help="Packed JSON file to update."),
|
|
151
|
+
directory: str = typer.Argument(".", help="Workspace directory to pack from."),
|
|
152
|
+
include_binary: bool = typer.Option(False, "--include-binary", help="Include binary files."),
|
|
153
|
+
):
|
|
154
|
+
"""Re-pack the workspace and update the JSON file with all latest files and folders."""
|
|
155
|
+
try:
|
|
156
|
+
result = fix_workspace(json_file, Path(directory), include_binary=include_binary)
|
|
157
|
+
typer.echo(f"Updated : {result['json_path']}")
|
|
158
|
+
typer.echo(f"Workspace: {result['workspace']}")
|
|
159
|
+
typer.echo(f" Added : {len(result['added'])} files")
|
|
160
|
+
typer.echo(f" Removed : {len(result['removed'])} files")
|
|
161
|
+
typer.echo(f" Refreshed: {len(result['refreshed'])} files")
|
|
162
|
+
typer.echo(f" Total : {result['total_files']} files ({result['total_bytes']} bytes)")
|
|
163
|
+
except (ValueError, OSError) as e:
|
|
164
|
+
typer.echo(str(e), err=True)
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
|
|
167
|
+
|
|
112
168
|
@app.command()
|
|
113
169
|
def serve():
|
|
114
170
|
typer.echo("Starting RepoPack MCP server (stdio)...", err=True)
|
repopack/mcp_server.py
CHANGED
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
from mcp.server.fastmcp import FastMCP
|
|
5
5
|
|
|
6
|
-
from repopack.packer import pack_workspace
|
|
6
|
+
from repopack.packer import fix_workspace, pack_workspace, validate_workspace
|
|
7
7
|
from repopack.unpacker import unpack_workspace
|
|
8
8
|
|
|
9
9
|
mcp = FastMCP("RepoPack")
|
|
@@ -49,6 +49,45 @@ def import_workspace(
|
|
|
49
49
|
return f"Extracted {summary['extracted']} files to {summary['destination']} ({summary['skipped']} skipped)"
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
@mcp.tool()
|
|
53
|
+
def validate_workspace_tool(
|
|
54
|
+
json_path: str,
|
|
55
|
+
workspace_path: str = ".",
|
|
56
|
+
) -> str:
|
|
57
|
+
report = validate_workspace(json_path, Path(workspace_path))
|
|
58
|
+
s = report["summary"]
|
|
59
|
+
lines = [
|
|
60
|
+
f"Status : {'OK' if report['valid'] else 'OUTDATED'}",
|
|
61
|
+
f"Missing from JSON : {s['missing_from_json']}",
|
|
62
|
+
f"Extra in JSON : {s['extra_in_json']}",
|
|
63
|
+
f"Stale in JSON : {s['stale_in_json']}",
|
|
64
|
+
f"Up to date : {s['up_to_date']}",
|
|
65
|
+
]
|
|
66
|
+
for p in report["missing_from_json"]:
|
|
67
|
+
lines.append(f" + {p}")
|
|
68
|
+
for p in report["extra_in_json"]:
|
|
69
|
+
lines.append(f" - {p}")
|
|
70
|
+
for p in report["stale_in_json"]:
|
|
71
|
+
lines.append(f" ~ {p}")
|
|
72
|
+
return "\n".join(lines)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@mcp.tool()
|
|
76
|
+
def fix_workspace_tool(
|
|
77
|
+
json_path: str,
|
|
78
|
+
workspace_path: str = ".",
|
|
79
|
+
include_binary: bool = False,
|
|
80
|
+
) -> str:
|
|
81
|
+
result = fix_workspace(json_path, Path(workspace_path), include_binary=include_binary)
|
|
82
|
+
return (
|
|
83
|
+
f"Updated {result['json_path']}: "
|
|
84
|
+
f"+{len(result['added'])} added, "
|
|
85
|
+
f"-{len(result['removed'])} removed, "
|
|
86
|
+
f"~{len(result['refreshed'])} refreshed. "
|
|
87
|
+
f"Total: {result['total_files']} files ({result['total_bytes']} bytes)."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
52
91
|
def run_server():
|
|
53
92
|
mcp.run(transport="stdio")
|
|
54
93
|
|
repopack/packer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import datetime
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
@@ -109,3 +110,72 @@ def pack_workspace(
|
|
|
109
110
|
},
|
|
110
111
|
"files": files,
|
|
111
112
|
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def validate_workspace(json_path: str, directory: Path) -> dict:
|
|
116
|
+
"""Compare a packed JSON against the live workspace and report differences."""
|
|
117
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
118
|
+
data = json.load(f)
|
|
119
|
+
|
|
120
|
+
if "files" not in data:
|
|
121
|
+
raise ValueError("Malformed JSON: missing 'files' key.")
|
|
122
|
+
|
|
123
|
+
packed_paths = {entry["path"] for entry in data["files"]}
|
|
124
|
+
|
|
125
|
+
live = pack_workspace(directory, dry_run=True)
|
|
126
|
+
live_files = {f["path"]: f for f in live["files"]}
|
|
127
|
+
live_paths = set(live_files)
|
|
128
|
+
|
|
129
|
+
missing = sorted(live_paths - packed_paths) # in workspace, not in JSON
|
|
130
|
+
extra = sorted(packed_paths - live_paths) # in JSON, not in workspace
|
|
131
|
+
common = packed_paths & live_paths
|
|
132
|
+
|
|
133
|
+
stale: list[str] = []
|
|
134
|
+
for path in sorted(common):
|
|
135
|
+
live_size = live_files[path]["size"]
|
|
136
|
+
disk_path = directory.resolve() / path
|
|
137
|
+
try:
|
|
138
|
+
packed_entry = next(e for e in data["files"] if e["path"] == path)
|
|
139
|
+
packed_len = len(packed_entry.get("content", ""))
|
|
140
|
+
# Compare mtime-derived size vs packed content length as a quick staleness check.
|
|
141
|
+
# For utf-8 files packed content length approximates byte size; good enough to flag.
|
|
142
|
+
if abs(live_size - packed_len) > 0:
|
|
143
|
+
stale.append(path)
|
|
144
|
+
except (StopIteration, OSError):
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
up_to_date = len(common) - len(stale)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
"json_path": json_path,
|
|
151
|
+
"workspace": str(directory.resolve()),
|
|
152
|
+
"summary": {
|
|
153
|
+
"missing_from_json": len(missing),
|
|
154
|
+
"extra_in_json": len(extra),
|
|
155
|
+
"stale_in_json": len(stale),
|
|
156
|
+
"up_to_date": up_to_date,
|
|
157
|
+
},
|
|
158
|
+
"missing_from_json": missing,
|
|
159
|
+
"extra_in_json": extra,
|
|
160
|
+
"stale_in_json": stale,
|
|
161
|
+
"valid": len(missing) == 0 and len(extra) == 0 and len(stale) == 0,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def fix_workspace(json_path: str, directory: Path, include_binary: bool = False) -> dict:
|
|
166
|
+
"""Re-pack the live workspace and overwrite the JSON file with the latest content."""
|
|
167
|
+
report = validate_workspace(json_path, directory)
|
|
168
|
+
|
|
169
|
+
result = pack_workspace(directory, include_binary=include_binary)
|
|
170
|
+
with open(json_path, "w", encoding="utf-8") as f:
|
|
171
|
+
json.dump(result, f, indent=2)
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
"json_path": json_path,
|
|
175
|
+
"workspace": str(directory.resolve()),
|
|
176
|
+
"added": report["missing_from_json"],
|
|
177
|
+
"removed": report["extra_in_json"],
|
|
178
|
+
"refreshed": report["stale_in_json"],
|
|
179
|
+
"total_files": result["metadata"]["total_files"],
|
|
180
|
+
"total_bytes": result["metadata"]["total_bytes"],
|
|
181
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: RepoPackPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Pack/unpack any source workspace into portable JSON, with MCP server support.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -71,8 +71,10 @@ pip install ".[dev]"
|
|
|
71
71
|
## CLI Usage
|
|
72
72
|
|
|
73
73
|
```
|
|
74
|
-
repopack pack
|
|
75
|
-
repopack unpack
|
|
74
|
+
repopack pack [DIRECTORY] [-o output.json] [--include-binary] [--custom-ignore ".next,dist"] [--dry-run]
|
|
75
|
+
repopack unpack <input.json> [-t TARGET_DIR] [--force] [--dry-run]
|
|
76
|
+
repopack validate <input.json> [DIRECTORY]
|
|
77
|
+
repopack fix <input.json> [DIRECTORY] [--include-binary]
|
|
76
78
|
repopack serve
|
|
77
79
|
```
|
|
78
80
|
|
|
@@ -87,6 +89,8 @@ repopack serve
|
|
|
87
89
|
| `unpack` | `-t / --target` | Destination directory (default: current directory) |
|
|
88
90
|
| `unpack` | `--force` | Overwrite existing files |
|
|
89
91
|
| `unpack` | `--dry-run` | List files that would be extracted or skipped without writing anything |
|
|
92
|
+
| `validate` | _(none)_ | Compare JSON against the live workspace; exit 1 if outdated |
|
|
93
|
+
| `fix` | `--include-binary` | Re-pack workspace into the JSON file, adding/removing/refreshing as needed |
|
|
90
94
|
|
|
91
95
|
### Examples
|
|
92
96
|
|
|
@@ -108,6 +112,12 @@ repopack unpack workspace.json -t ./restored --dry-run
|
|
|
108
112
|
|
|
109
113
|
# Unpack and overwrite existing files
|
|
110
114
|
repopack unpack workspace.json -t ./restored --force
|
|
115
|
+
|
|
116
|
+
# Validate: check if workspace.json is complete and up to date
|
|
117
|
+
repopack validate workspace.json .
|
|
118
|
+
|
|
119
|
+
# Fix: update workspace.json with all latest files and folders
|
|
120
|
+
repopack fix workspace.json .
|
|
111
121
|
```
|
|
112
122
|
|
|
113
123
|
#### Sample `--dry-run` output
|
|
@@ -132,6 +142,41 @@ Dry run — destination: /home/user/restored
|
|
|
132
142
|
...
|
|
133
143
|
```
|
|
134
144
|
|
|
145
|
+
#### Sample `validate` output
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Status : OUTDATED
|
|
149
|
+
JSON : workspace.json
|
|
150
|
+
Workspace: /home/user/my-app
|
|
151
|
+
Missing from JSON : 2
|
|
152
|
+
Extra in JSON : 1
|
|
153
|
+
Stale in JSON : 3
|
|
154
|
+
Up to date : 36
|
|
155
|
+
|
|
156
|
+
Missing (in workspace, not in JSON):
|
|
157
|
+
+ src/NewComponent.tsx
|
|
158
|
+
+ src/utils/helper.ts
|
|
159
|
+
|
|
160
|
+
Extra (in JSON, not in workspace):
|
|
161
|
+
- src/OldComponent.tsx
|
|
162
|
+
|
|
163
|
+
Stale (content changed since packing):
|
|
164
|
+
~ README.md
|
|
165
|
+
~ src/App.tsx
|
|
166
|
+
~ package.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Sample `fix` output
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Updated : workspace.json
|
|
173
|
+
Workspace: /home/user/my-app
|
|
174
|
+
Added : 2 files
|
|
175
|
+
Removed : 1 files
|
|
176
|
+
Refreshed: 3 files
|
|
177
|
+
Total : 42 files (189440 bytes)
|
|
178
|
+
```
|
|
179
|
+
|
|
135
180
|
## JSON Payload Schema
|
|
136
181
|
|
|
137
182
|
```json
|
|
@@ -159,8 +204,10 @@ When running `repopack serve`, two tools are registered via FastMCP:
|
|
|
159
204
|
|---|---|---|
|
|
160
205
|
| `export_workspace` | `workspace_path, output_json_path, include_binary, dry_run` | Pack a directory into JSON |
|
|
161
206
|
| `import_workspace` | `json_input, destination_path, overwrite, dry_run` | Unpack JSON into a directory |
|
|
207
|
+
| `validate_workspace_tool` | `json_path, workspace_path` | Report missing, extra, and stale files vs the live workspace |
|
|
208
|
+
| `fix_workspace_tool` | `json_path, workspace_path, include_binary` | Re-pack and overwrite the JSON with all latest files and folders |
|
|
162
209
|
|
|
163
|
-
Set `dry_run=true` on
|
|
210
|
+
Set `dry_run=true` on `export_workspace` or `import_workspace` to get a plain-text report without reading file content or writing to disk.
|
|
164
211
|
|
|
165
212
|
## Security
|
|
166
213
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
repopack/__init__.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
|
|
2
|
+
repopack/cli.py,sha256=aoegt3XazDlue_Cj7fSLPc4oloQ-eG4ws9HlgybY89I,6979
|
|
3
|
+
repopack/mcp_server.py,sha256=zIwDCohU8G3qXsnSsC2tqNneLkVMAqs993DDD4uALE4,3246
|
|
4
|
+
repopack/packer.py,sha256=35HHwUSzvNYIxjArzJ-OdYidqSGH8YMs9uQxbDHZ3AE,5841
|
|
5
|
+
repopack/unpacker.py,sha256=pxToRzBMVGnh_gsI3ChXQPdkTf-GBkghTbYHG4ntXJ8,2423
|
|
6
|
+
repopack/utils.py,sha256=UnKnSGl_StMlE4OQI1zCNUl--UPV6VFzW3UGTfsi6JI,4807
|
|
7
|
+
repopackpy-0.2.0.dist-info/METADATA,sha256=8jfT5_mjzPe-aQBDiRRCFNVxhV6J2jnazfT7rSnD4cY,8493
|
|
8
|
+
repopackpy-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
9
|
+
repopackpy-0.2.0.dist-info/entry_points.txt,sha256=6_yKH4CqxdMrFaHwQH-bKM1XW0lppiBJ3ioWJ8RFlTs,47
|
|
10
|
+
repopackpy-0.2.0.dist-info/licenses/LICENSE,sha256=Z4Oa85qoUpnHkq4Pa4-LOGiCT5cvJaX5_aP0K8OXBNQ,1069
|
|
11
|
+
repopackpy-0.2.0.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
repopack/__init__.py,sha256=XIaxbMbyiP-L3kguR1GhxirFblTXiHR1lMfDVITvHUI,22
|
|
2
|
-
repopack/cli.py,sha256=oUmkddm7wPeFcnBW5jqZEhnH-U0GgnLxCYFj7jZuvVg,4352
|
|
3
|
-
repopack/mcp_server.py,sha256=WT3r5ASrTqOcZGtoQnVgZvEWcQK_7QsxR3yE4M3QD4c,1965
|
|
4
|
-
repopack/packer.py,sha256=lNwCtbPCKgQTRZW59tn8S9ZkHf5YtDEyY0ITztkY1xs,3196
|
|
5
|
-
repopack/unpacker.py,sha256=pxToRzBMVGnh_gsI3ChXQPdkTf-GBkghTbYHG4ntXJ8,2423
|
|
6
|
-
repopack/utils.py,sha256=UnKnSGl_StMlE4OQI1zCNUl--UPV6VFzW3UGTfsi6JI,4807
|
|
7
|
-
repopackpy-0.1.9.dist-info/METADATA,sha256=-oaP7IprQVZIQmkB838i3vAvs_nhj-tNnm3Et5KA1p4,7069
|
|
8
|
-
repopackpy-0.1.9.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
9
|
-
repopackpy-0.1.9.dist-info/entry_points.txt,sha256=6_yKH4CqxdMrFaHwQH-bKM1XW0lppiBJ3ioWJ8RFlTs,47
|
|
10
|
-
repopackpy-0.1.9.dist-info/licenses/LICENSE,sha256=Z4Oa85qoUpnHkq4Pa4-LOGiCT5cvJaX5_aP0K8OXBNQ,1069
|
|
11
|
-
repopackpy-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|