opkg 0.7.0 → 0.7.1
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 +33 -33
- package/dist/commands/install.js +12 -7
- package/dist/commands/install.js.map +1 -1
- package/dist/constants/index.js +21 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/core/add/add-to-source-pipeline.js +1 -1
- package/dist/core/add/add-to-source-pipeline.js.map +1 -1
- package/dist/core/directory.js +18 -18
- package/dist/core/directory.js.map +1 -1
- package/dist/core/flows/platform-converter.js +2 -3
- package/dist/core/flows/platform-converter.js.map +1 -1
- package/dist/core/install/bulk-install-pipeline.js +2 -1
- package/dist/core/install/bulk-install-pipeline.js.map +1 -1
- package/dist/core/install/flow-based-installer.js +28 -10
- package/dist/core/install/flow-based-installer.js.map +1 -1
- package/dist/core/install/git-package-loader.js +23 -5
- package/dist/core/install/git-package-loader.js.map +1 -1
- package/dist/core/install/marketplace-handler.js +67 -26
- package/dist/core/install/marketplace-handler.js.map +1 -1
- package/dist/core/install/path-install-pipeline.js +9 -5
- package/dist/core/install/path-install-pipeline.js.map +1 -1
- package/dist/core/install/path-package-loader.js +15 -9
- package/dist/core/install/path-package-loader.js.map +1 -1
- package/dist/core/install/plugin-detector.js +4 -6
- package/dist/core/install/plugin-detector.js.map +1 -1
- package/dist/core/install/plugin-transformer.js +25 -15
- package/dist/core/install/plugin-transformer.js.map +1 -1
- package/dist/core/package.js +1 -1
- package/dist/core/package.js.map +1 -1
- package/dist/core/remove/remove-from-source-pipeline.js +1 -1
- package/dist/core/remove/remove-from-source-pipeline.js.map +1 -1
- package/dist/utils/git-cache.js +196 -0
- package/dist/utils/git-cache.js.map +1 -0
- package/dist/utils/git-clone.js +123 -43
- package/dist/utils/git-clone.js.map +1 -1
- package/dist/utils/git-url-parser.js +113 -0
- package/dist/utils/git-url-parser.js.map +1 -0
- package/dist/utils/index-based-installer.js +2 -2
- package/dist/utils/index-based-installer.js.map +1 -1
- package/dist/utils/package-input.js +5 -4
- package/dist/utils/package-input.js.map +1 -1
- package/dist/utils/package-name.js +54 -16
- package/dist/utils/package-name.js.map +1 -1
- package/dist/utils/platform-file.js +2 -4
- package/dist/utils/platform-file.js.map +1 -1
- package/dist/utils/plugin-naming.js +111 -0
- package/dist/utils/plugin-naming.js.map +1 -0
- package/dist/utils/tarball.js +2 -2
- package/dist/utils/tarball.js.map +1 -1
- package/package.json +1 -1
- package/platforms.jsonc +1 -1
- package/specs/install/README.md +2 -1
- package/specs/install/git-cache.md +403 -0
- package/specs/install/git-sources.md +111 -23
- package/specs/new/README.md +3 -1
- package/specs/set/README.md +5 -2
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Git Cache Architecture
|
|
2
|
+
|
|
3
|
+
This document specifies the structured Git cache system used for cloning and caching Git repositories.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview
|
|
8
|
+
|
|
9
|
+
The Git cache provides:
|
|
10
|
+
- **Deterministic paths**: Same repository/commit always uses same location
|
|
11
|
+
- **Automatic reuse**: Detects and reuses existing cached commits
|
|
12
|
+
- **Persistent storage**: Cache survives reboots (unlike temp directories)
|
|
13
|
+
- **Space efficiency**: Uses shallow clones (`--depth 1`)
|
|
14
|
+
- **Metadata tracking**: Stores repository and commit information for debugging
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Cache structure
|
|
19
|
+
|
|
20
|
+
### 2.1 Base directory
|
|
21
|
+
|
|
22
|
+
All Git clones are cached at:
|
|
23
|
+
```
|
|
24
|
+
~/.openpackage/cache/git/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2.2 Directory hierarchy
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
~/.openpackage/cache/git/
|
|
31
|
+
├── <url-hash-12>/ # Repository (by URL hash)
|
|
32
|
+
│ ├── .opkg-repo.json # Repository metadata
|
|
33
|
+
│ ├── <commit-sha-7>/ # Commit checkout
|
|
34
|
+
│ │ ├── .git/ # Git repository data
|
|
35
|
+
│ │ ├── .opkg-commit.json # Commit metadata
|
|
36
|
+
│ │ └── <repository files>
|
|
37
|
+
│ └── <commit-sha-7>/ # Another commit
|
|
38
|
+
│ └── ...
|
|
39
|
+
└── <url-hash-12>/ # Another repository
|
|
40
|
+
└── ...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Path components**:
|
|
44
|
+
- `<url-hash-12>`: 12 hex characters (48 bits) - hash of normalized Git URL
|
|
45
|
+
- `<commit-sha-7>`: 7 hex characters - first 7 chars of resolved commit SHA
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. URL hash generation
|
|
50
|
+
|
|
51
|
+
### 3.1 Normalization
|
|
52
|
+
|
|
53
|
+
URLs are normalized before hashing to ensure consistency:
|
|
54
|
+
|
|
55
|
+
**Transformations**:
|
|
56
|
+
1. Convert to lowercase
|
|
57
|
+
2. Remove `.git` suffix
|
|
58
|
+
3. Normalize GitHub SSH to HTTPS format:
|
|
59
|
+
- `git@github.com:owner/repo.git` → `https://github.com/owner/repo`
|
|
60
|
+
4. Normalize other SSH formats:
|
|
61
|
+
- `git@host:owner/repo.git` → `https://host/owner/repo`
|
|
62
|
+
5. Remove trailing slashes
|
|
63
|
+
|
|
64
|
+
**Examples**:
|
|
65
|
+
| Original URL | Normalized URL |
|
|
66
|
+
|--------------|----------------|
|
|
67
|
+
| `https://github.com/User/Repo.git` | `https://github.com/user/repo` |
|
|
68
|
+
| `git@github.com:anthropics/claude-code.git` | `https://github.com/anthropics/claude-code` |
|
|
69
|
+
| `https://gitlab.com/group/project.git` | `https://gitlab.com/group/project` |
|
|
70
|
+
|
|
71
|
+
### 3.2 Hash computation
|
|
72
|
+
|
|
73
|
+
- Algorithm: SHA-256
|
|
74
|
+
- Length: First 12 hex characters (48 bits)
|
|
75
|
+
- Collision probability: ~10^-10 with 1000 repos
|
|
76
|
+
- Deterministic: Same normalized URL always produces same hash
|
|
77
|
+
|
|
78
|
+
**Implementation**:
|
|
79
|
+
```typescript
|
|
80
|
+
function computeGitUrlHash(url: string): string {
|
|
81
|
+
const normalized = normalizeGitUrl(url);
|
|
82
|
+
const hash = createHash('sha256').update(normalized).digest('hex');
|
|
83
|
+
return hash.substring(0, 12);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 4. Commit SHA resolution
|
|
90
|
+
|
|
91
|
+
### 4.1 Resolution process
|
|
92
|
+
|
|
93
|
+
When cloning a repository:
|
|
94
|
+
1. Clone repository to temporary location within cache
|
|
95
|
+
2. Resolve current HEAD to full commit SHA (40 chars)
|
|
96
|
+
3. Truncate to 7 characters for directory name
|
|
97
|
+
4. Move to final location: `<url-hash>/<commit-sha-7>/`
|
|
98
|
+
|
|
99
|
+
### 4.2 Ref handling
|
|
100
|
+
|
|
101
|
+
**Branch or tag specified**:
|
|
102
|
+
- Clone with `--branch <ref>`
|
|
103
|
+
- Resolve to commit SHA after clone
|
|
104
|
+
- Directory named by commit SHA (not branch name)
|
|
105
|
+
|
|
106
|
+
**Commit SHA specified**:
|
|
107
|
+
- Clone default branch
|
|
108
|
+
- Fetch and checkout specific commit
|
|
109
|
+
- Directory named by commit SHA
|
|
110
|
+
|
|
111
|
+
**No ref specified**:
|
|
112
|
+
- Clone default branch
|
|
113
|
+
- Resolve HEAD to commit SHA
|
|
114
|
+
- Directory named by commit SHA
|
|
115
|
+
|
|
116
|
+
**Result**: Different branches/tags pointing to same commit → same cache location
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 5. Metadata files
|
|
121
|
+
|
|
122
|
+
### 5.1 Repository metadata
|
|
123
|
+
|
|
124
|
+
**Location**: `~/.openpackage/cache/git/<url-hash>/.opkg-repo.json`
|
|
125
|
+
|
|
126
|
+
**Schema**:
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"url": "https://github.com/anthropics/claude-code.git",
|
|
130
|
+
"normalized": "https://github.com/anthropics/claude-code",
|
|
131
|
+
"lastFetched": "2025-01-13T10:30:00Z"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Fields**:
|
|
136
|
+
- `url`: Original Git URL (as provided by user)
|
|
137
|
+
- `normalized`: Normalized URL used for hashing
|
|
138
|
+
- `lastFetched`: Timestamp of most recent fetch operation
|
|
139
|
+
|
|
140
|
+
### 5.2 Commit metadata
|
|
141
|
+
|
|
142
|
+
**Location**: `~/.openpackage/cache/git/<url-hash>/<commit-sha-7>/.opkg-commit.json`
|
|
143
|
+
|
|
144
|
+
**Schema**:
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"url": "https://github.com/anthropics/claude-code.git",
|
|
148
|
+
"commit": "abc1234567890abcdef",
|
|
149
|
+
"ref": "main",
|
|
150
|
+
"subdirectory": "plugins/commit-commands",
|
|
151
|
+
"clonedAt": "2025-01-13T10:30:00Z",
|
|
152
|
+
"lastAccessed": "2025-01-13T12:00:00Z"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Fields**:
|
|
157
|
+
- `url`: Git URL for this commit
|
|
158
|
+
- `commit`: Full commit SHA (40 chars)
|
|
159
|
+
- `ref`: Branch/tag name if specified (optional)
|
|
160
|
+
- `subdirectory`: Subdirectory path if specified (optional)
|
|
161
|
+
- `clonedAt`: Timestamp when commit was first cloned
|
|
162
|
+
- `lastAccessed`: Timestamp of most recent access
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 6. Clone operations
|
|
167
|
+
|
|
168
|
+
### 6.1 Initial clone
|
|
169
|
+
|
|
170
|
+
When cloning a new commit:
|
|
171
|
+
1. Generate cache paths:
|
|
172
|
+
- `repoDir = ~/.openpackage/cache/git/<url-hash>/`
|
|
173
|
+
- `tempPath = <repoDir>/.temp-clone`
|
|
174
|
+
2. Create repo directory if needed
|
|
175
|
+
3. Write `.opkg-repo.json` metadata
|
|
176
|
+
4. Clone to temporary path:
|
|
177
|
+
- `git clone --depth 1 [--branch <ref>] <url> <tempPath>`
|
|
178
|
+
5. Resolve commit SHA from HEAD
|
|
179
|
+
6. Check if commit already cached (rare race condition)
|
|
180
|
+
7. Move temp path to final location: `<repoDir>/<commit-sha-7>/`
|
|
181
|
+
8. Write `.opkg-commit.json` metadata
|
|
182
|
+
|
|
183
|
+
### 6.2 Cache hit
|
|
184
|
+
|
|
185
|
+
When requested commit is already cached:
|
|
186
|
+
1. Check if directory exists: `~/.openpackage/cache/git/<url-hash>/<commit-sha-7>/`
|
|
187
|
+
2. Read and validate `.opkg-commit.json`
|
|
188
|
+
3. Update `lastAccessed` timestamp
|
|
189
|
+
4. Return path to cached commit
|
|
190
|
+
5. Skip clone entirely
|
|
191
|
+
|
|
192
|
+
### 6.3 Subdirectory handling
|
|
193
|
+
|
|
194
|
+
Subdirectories are accessed within the cloned repository:
|
|
195
|
+
- Clone full repository to cache
|
|
196
|
+
- Return path to subdirectory: `<commit-dir>/<subdirectory>/`
|
|
197
|
+
- Validate subdirectory exists before returning
|
|
198
|
+
|
|
199
|
+
**Example**:
|
|
200
|
+
```
|
|
201
|
+
Cache: ~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/
|
|
202
|
+
Subdirectory: plugins/commit-commands
|
|
203
|
+
Return: ~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/plugins/commit-commands/
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 7. Shallow clone details
|
|
209
|
+
|
|
210
|
+
### 7.1 Clone depth
|
|
211
|
+
|
|
212
|
+
All clones use `--depth 1` (shallow clone):
|
|
213
|
+
- Fetches only the latest commit
|
|
214
|
+
- Significantly smaller repository size
|
|
215
|
+
- Faster clone times
|
|
216
|
+
- Sufficient for package installation
|
|
217
|
+
|
|
218
|
+
**Comparison**:
|
|
219
|
+
| Clone Type | Size | Time |
|
|
220
|
+
|------------|------|------|
|
|
221
|
+
| Full clone | ~100MB | 10s |
|
|
222
|
+
| Shallow clone (`--depth 1`) | ~10MB | 2s |
|
|
223
|
+
|
|
224
|
+
### 7.2 Limitations
|
|
225
|
+
|
|
226
|
+
Shallow clones have limited Git history:
|
|
227
|
+
- Cannot `git log` beyond fetched commit
|
|
228
|
+
- Cannot easily determine branch history
|
|
229
|
+
- May need additional fetch for some operations
|
|
230
|
+
|
|
231
|
+
**Acceptable for OpenPackage** because:
|
|
232
|
+
- Only need files at specific commit
|
|
233
|
+
- Not performing Git operations after clone
|
|
234
|
+
- Can re-clone if full history needed
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 8. Cache management
|
|
239
|
+
|
|
240
|
+
### 8.1 Current behavior
|
|
241
|
+
|
|
242
|
+
- Cache grows indefinitely (no automatic cleanup)
|
|
243
|
+
- Multiple commits of same repository stored separately
|
|
244
|
+
- No size limits enforced
|
|
245
|
+
|
|
246
|
+
### 8.2 Future enhancements
|
|
247
|
+
|
|
248
|
+
**Cache cleanup**:
|
|
249
|
+
- Remove commits not accessed in N days
|
|
250
|
+
- Remove commits not referenced in any workspace
|
|
251
|
+
- Size-based cleanup (when cache exceeds threshold)
|
|
252
|
+
|
|
253
|
+
**Cache commands** (not yet implemented):
|
|
254
|
+
```bash
|
|
255
|
+
opkg cache list # List all cached repositories
|
|
256
|
+
opkg cache show <url> # Show commits for specific repository
|
|
257
|
+
opkg cache clean # Clean old/unused cache entries
|
|
258
|
+
opkg cache clean --all # Remove entire cache
|
|
259
|
+
opkg cache clean <url> # Remove specific repository
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Cache statistics**:
|
|
263
|
+
- Total cache size
|
|
264
|
+
- Number of repositories
|
|
265
|
+
- Number of commits
|
|
266
|
+
- Last accessed timestamps
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 9. Error handling
|
|
271
|
+
|
|
272
|
+
### 9.1 Clone failures
|
|
273
|
+
|
|
274
|
+
If clone operation fails:
|
|
275
|
+
1. Clean up temporary clone directory
|
|
276
|
+
2. Preserve existing cache entries
|
|
277
|
+
3. Return error to caller
|
|
278
|
+
4. Do not write metadata files
|
|
279
|
+
|
|
280
|
+
### 9.2 Corrupted cache
|
|
281
|
+
|
|
282
|
+
If cache directory is corrupted:
|
|
283
|
+
- Next clone attempt will detect missing/invalid metadata
|
|
284
|
+
- Re-clone automatically
|
|
285
|
+
- Corrupted entries can be manually deleted
|
|
286
|
+
|
|
287
|
+
### 9.3 Disk space
|
|
288
|
+
|
|
289
|
+
If disk is full:
|
|
290
|
+
- Clone fails with disk space error
|
|
291
|
+
- User must free space or clean cache
|
|
292
|
+
- No automatic cleanup on low disk space
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 10. Benefits and trade-offs
|
|
297
|
+
|
|
298
|
+
### 10.1 Benefits
|
|
299
|
+
|
|
300
|
+
✅ **Deterministic**: Same repo/commit → same path
|
|
301
|
+
✅ **Fast reinstalls**: Reuses existing cache
|
|
302
|
+
✅ **Debuggable**: Metadata files show what's cached
|
|
303
|
+
✅ **Persistent**: Survives reboots
|
|
304
|
+
✅ **Space efficient**: Shallow clones
|
|
305
|
+
✅ **Collision resistant**: 48-bit hash
|
|
306
|
+
|
|
307
|
+
### 10.2 Trade-offs
|
|
308
|
+
|
|
309
|
+
⚠️ **Disk usage**: Cache grows over time
|
|
310
|
+
⚠️ **No automatic cleanup**: User must manage cache
|
|
311
|
+
⚠️ **Shallow history**: Limited Git operations
|
|
312
|
+
⚠️ **Branch updates**: No automatic update detection
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 11. Implementation notes
|
|
317
|
+
|
|
318
|
+
### 11.1 URL parsing
|
|
319
|
+
|
|
320
|
+
Supports multiple Git URL formats:
|
|
321
|
+
- HTTPS: `https://github.com/owner/repo.git`
|
|
322
|
+
- SSH: `git@github.com:owner/repo.git`
|
|
323
|
+
- SSH with protocol: `ssh://git@github.com:owner/repo.git`
|
|
324
|
+
- Git protocol: `git://github.com/owner/repo.git`
|
|
325
|
+
|
|
326
|
+
All formats are normalized to HTTPS for hashing.
|
|
327
|
+
|
|
328
|
+
### 11.2 Path safety
|
|
329
|
+
|
|
330
|
+
- All paths use filesystem-safe characters
|
|
331
|
+
- Commit SHAs are hex (safe for all filesystems)
|
|
332
|
+
- URL hashes are hex (safe for all filesystems)
|
|
333
|
+
- No special character handling needed
|
|
334
|
+
|
|
335
|
+
### 11.3 Concurrency
|
|
336
|
+
|
|
337
|
+
**Current implementation**:
|
|
338
|
+
- No file locking or coordination
|
|
339
|
+
- Rare race condition possible (same commit cloned simultaneously)
|
|
340
|
+
- Race is detected and handled (one clone succeeds, others reuse)
|
|
341
|
+
|
|
342
|
+
**Future consideration**:
|
|
343
|
+
- File locking for clone operations
|
|
344
|
+
- Atomic directory moves
|
|
345
|
+
- Retry logic for transient failures
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 12. Related specifications
|
|
350
|
+
|
|
351
|
+
- [Git Sources](./git-sources.md): Git install behavior
|
|
352
|
+
- [Install Behavior](./install-behavior.md): Overall install flow
|
|
353
|
+
- [Claude Code Plugin Support](./install-behavior.md#9-claude-code-plugin-support): Plugin installation
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## 13. Examples
|
|
358
|
+
|
|
359
|
+
### 13.1 GitHub plugin marketplace
|
|
360
|
+
|
|
361
|
+
**Command**:
|
|
362
|
+
```bash
|
|
363
|
+
opkg install github:anthropics/claude-code#subdirectory=plugins/commit-commands
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Process**:
|
|
367
|
+
1. Normalize URL: `https://github.com/anthropics/claude-code`
|
|
368
|
+
2. Compute hash: `a1b2c3d4e5f6`
|
|
369
|
+
3. Clone to: `~/.openpackage/cache/git/a1b2c3d4e5f6/.temp-clone`
|
|
370
|
+
4. Resolve commit: `abc1234567890abcdef`
|
|
371
|
+
5. Move to: `~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/`
|
|
372
|
+
6. Return path: `~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/plugins/commit-commands/`
|
|
373
|
+
|
|
374
|
+
### 13.2 GitLab project
|
|
375
|
+
|
|
376
|
+
**Command**:
|
|
377
|
+
```bash
|
|
378
|
+
opkg install git:https://gitlab.com/company/project.git#v1.0.0
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Process**:
|
|
382
|
+
1. Normalize URL: `https://gitlab.com/company/project`
|
|
383
|
+
2. Compute hash: `x9y8z7w6v5u4`
|
|
384
|
+
3. Clone with tag: `git clone --depth 1 --branch v1.0.0 <url>`
|
|
385
|
+
4. Resolve commit: `def5678901234abcdef`
|
|
386
|
+
5. Move to: `~/.openpackage/cache/git/x9y8z7w6v5u4/def5678/`
|
|
387
|
+
6. Return path: `~/.openpackage/cache/git/x9y8z7w6v5u4/def5678/`
|
|
388
|
+
|
|
389
|
+
### 13.3 Cache reuse
|
|
390
|
+
|
|
391
|
+
**First install**:
|
|
392
|
+
```bash
|
|
393
|
+
opkg install github:user/plugin
|
|
394
|
+
# Clones to: ~/.openpackage/cache/git/k4l5m6n7o8p9/ghi9012/
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Second install** (same commit):
|
|
398
|
+
```bash
|
|
399
|
+
opkg install github:user/plugin
|
|
400
|
+
# Detects existing cache
|
|
401
|
+
# Reuses: ~/.openpackage/cache/git/k4l5m6n7o8p9/ghi9012/
|
|
402
|
+
# No clone needed
|
|
403
|
+
```
|
|
@@ -70,9 +70,16 @@ Rules:
|
|
|
70
70
|
|
|
71
71
|
### 3.1 Basic git install
|
|
72
72
|
|
|
73
|
-
- `install` clones the repository to a
|
|
73
|
+
- `install` clones the repository to a **structured cache** at `~/.openpackage/cache/git/` using the system `git` executable.
|
|
74
|
+
- **Cache structure**: `~/.openpackage/cache/git/<url-hash-12>/<commit-sha-7>/`
|
|
75
|
+
- `<url-hash-12>`: 12-character hash of normalized Git URL
|
|
76
|
+
- `<commit-sha-7>`: First 7 characters of resolved commit SHA
|
|
77
|
+
- **Clone behavior**:
|
|
78
|
+
- Uses shallow clones (`--depth 1`) for space efficiency.
|
|
79
|
+
- Reuses existing cache if same commit is already cached.
|
|
80
|
+
- Writes metadata files (`.opkg-repo.json`, `.opkg-commit.json`) for tracking.
|
|
74
81
|
- If `ref` is provided:
|
|
75
|
-
- For branch/tag: clone the specified ref.
|
|
82
|
+
- For branch/tag: clone the specified ref and resolve to commit SHA.
|
|
76
83
|
- For commit SHA: clone and checkout that SHA (best-effort shallow fetch).
|
|
77
84
|
- Without subdirectory: The cloned repository root MUST contain `openpackage.yml`.
|
|
78
85
|
- The installed package version is read from the repo's `openpackage.yml`.
|
|
@@ -83,14 +90,35 @@ Rules:
|
|
|
83
90
|
### 3.2 Subdirectory installs
|
|
84
91
|
|
|
85
92
|
When `subdirectory` is specified:
|
|
86
|
-
- Repository is cloned to
|
|
93
|
+
- Repository is cloned to the structured cache (same as §3.1).
|
|
87
94
|
- The specified subdirectory path is resolved relative to the repository root.
|
|
88
95
|
- The subdirectory MUST contain either:
|
|
89
96
|
- `openpackage.yml` (standard OpenPackage package), OR
|
|
90
97
|
- `.claude-plugin/plugin.json` (Claude Code plugin), OR
|
|
91
98
|
- `.claude-plugin/marketplace.json` (Claude Code plugin marketplace)
|
|
92
99
|
- For OpenPackage packages: `openpackage.yml` is read from the subdirectory.
|
|
93
|
-
- For Claude Code plugins: See §4 for special handling.
|
|
100
|
+
- For Claude Code plugins: See §4 for special handling including scoped naming.
|
|
101
|
+
|
|
102
|
+
### 3.3 Cache persistence and management
|
|
103
|
+
|
|
104
|
+
The Git cache persists across sessions:
|
|
105
|
+
- **Location**: `~/.openpackage/cache/git/`
|
|
106
|
+
- **Benefits**: Faster reinstalls, survives reboots, debuggable with metadata
|
|
107
|
+
- **Structure**:
|
|
108
|
+
```
|
|
109
|
+
~/.openpackage/cache/git/
|
|
110
|
+
├── a1b2c3d4e5f6/ # URL hash
|
|
111
|
+
│ ├── .opkg-repo.json # Repo metadata (URL, last fetched)
|
|
112
|
+
│ ├── abc1234/ # Commit SHA
|
|
113
|
+
│ │ ├── .git/ # Shallow clone
|
|
114
|
+
│ │ ├── .opkg-commit.json # Commit metadata (ref, timestamp)
|
|
115
|
+
│ │ └── <repo contents>
|
|
116
|
+
│ └── def5678/ # Different commit
|
|
117
|
+
└── x9y8z7w6v5u4/ # Different repo
|
|
118
|
+
```
|
|
119
|
+
- **Metadata tracking**:
|
|
120
|
+
- `.opkg-repo.json`: Stores URL, normalized URL, last fetch timestamp
|
|
121
|
+
- `.opkg-commit.json`: Stores full commit SHA, ref name, clone/access timestamps
|
|
94
122
|
|
|
95
123
|
---
|
|
96
124
|
|
|
@@ -116,20 +144,29 @@ Detection happens automatically after cloning, before attempting to load as an O
|
|
|
116
144
|
|
|
117
145
|
When an individual plugin is detected:
|
|
118
146
|
1. Plugin manifest (`.claude-plugin/plugin.json`) is read and validated.
|
|
119
|
-
2. Plugin
|
|
120
|
-
-
|
|
147
|
+
2. **Plugin name is generated with scoping**:
|
|
148
|
+
- **GitHub plugins**: Use scoped format `@<username>/<plugin-name>`
|
|
149
|
+
- **GitHub plugins from subdirectory**: Use `@<username>/<repo>/<plugin-name>`
|
|
150
|
+
- **Non-GitHub sources**: Use original plugin name (no scoping)
|
|
151
|
+
- **Fallback behavior**: If `plugin.json` has no `name` field:
|
|
152
|
+
- Use subdirectory basename if installing from subdirectory
|
|
153
|
+
- Use repository name if installing full repo
|
|
154
|
+
- Use "unnamed-plugin" as last resort
|
|
155
|
+
3. Plugin metadata is transformed to OpenPackage format in-memory:
|
|
156
|
+
- Scoped `name` becomes package metadata
|
|
157
|
+
- `version` from `plugin.json` becomes package version
|
|
121
158
|
- `description`, `author`, `repository`, etc. are preserved
|
|
122
|
-
|
|
123
|
-
|
|
159
|
+
4. All plugin files are collected (commands/, agents/, skills/, hooks/, .mcp.json, .lsp.json, etc.)
|
|
160
|
+
5. **Package format is detected** and appropriate installation strategy selected:
|
|
124
161
|
- **Direct AS-IS**: Source platform = target platform (fastest)
|
|
125
162
|
- **Cross-platform conversion**: Source ≠ target (via Universal Converter)
|
|
126
163
|
- **Standard flows**: Universal format packages
|
|
127
|
-
|
|
164
|
+
6. Files are installed to platform-specific directories:
|
|
128
165
|
- `commands/` → `.claude/commands/`, `.cursor/commands/`, etc.
|
|
129
166
|
- `agents/` → `.claude/agents/`, `.cursor/agents/`, etc.
|
|
130
167
|
- Root files (`.mcp.json`, `.lsp.json`) → platform roots
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
7. The dependency is tracked in `openpackage.yml` with its **scoped name** and git source (not as a registry version).
|
|
169
|
+
8. No registry copy is created (git repository remains source of truth).
|
|
133
170
|
|
|
134
171
|
**See:** [Universal Platform Converter](../platforms/universal-converter.md) for cross-platform conversion details.
|
|
135
172
|
|
|
@@ -141,22 +178,29 @@ opkg install github:anthropics/claude-code#subdirectory=plugins/commit-commands
|
|
|
141
178
|
Result in `openpackage.yml`:
|
|
142
179
|
```yaml
|
|
143
180
|
packages:
|
|
144
|
-
- name: commit-commands
|
|
181
|
+
- name: "@anthropics/claude-code/commit-commands" # Scoped name
|
|
145
182
|
git: https://github.com/anthropics/claude-code.git
|
|
146
183
|
subdirectory: plugins/commit-commands
|
|
147
184
|
```
|
|
148
185
|
|
|
186
|
+
Installed to cache:
|
|
187
|
+
```
|
|
188
|
+
~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/plugins/commit-commands/
|
|
189
|
+
```
|
|
190
|
+
|
|
149
191
|
### 4.3 Marketplace install
|
|
150
192
|
|
|
151
193
|
When a plugin marketplace is detected:
|
|
152
194
|
1. Marketplace manifest (`.claude-plugin/marketplace.json`) is parsed.
|
|
195
|
+
- **Fallback behavior**: If `marketplace.json` has no `name` field, uses repository name
|
|
153
196
|
2. An interactive multiselect prompt is displayed listing all available plugins.
|
|
154
197
|
3. User selects which plugin(s) to install (space to select, enter to confirm).
|
|
155
198
|
4. Each selected plugin is installed individually:
|
|
199
|
+
- **Scoped name is generated**: `@<username>/<marketplace-name>/<plugin-name>`
|
|
156
200
|
- Plugin subdirectory is resolved within the cloned repository.
|
|
157
201
|
- Plugin is validated (must have `.claude-plugin/plugin.json`).
|
|
158
202
|
- Plugin is installed following the individual plugin flow (§4.2).
|
|
159
|
-
5. Each plugin gets its own entry in `openpackage.yml` with its specific subdirectory.
|
|
203
|
+
5. Each plugin gets its own entry in `openpackage.yml` with its **scoped name** and specific subdirectory.
|
|
160
204
|
|
|
161
205
|
**Example:**
|
|
162
206
|
```bash
|
|
@@ -177,15 +221,48 @@ Select plugins to install (space to select, enter to confirm):
|
|
|
177
221
|
Result in `openpackage.yml` (if user selected commit-commands and pr-review-toolkit):
|
|
178
222
|
```yaml
|
|
179
223
|
packages:
|
|
180
|
-
- name: commit-commands
|
|
224
|
+
- name: "@anthropics/claude-code/commit-commands" # Scoped name
|
|
181
225
|
git: https://github.com/anthropics/claude-code.git
|
|
182
226
|
subdirectory: plugins/commit-commands
|
|
183
|
-
- name: pr-review-toolkit
|
|
227
|
+
- name: "@anthropics/claude-code/pr-review-toolkit" # Scoped name
|
|
184
228
|
git: https://github.com/anthropics/claude-code.git
|
|
185
229
|
subdirectory: plugins/pr-review-toolkit
|
|
186
230
|
```
|
|
187
231
|
|
|
188
|
-
|
|
232
|
+
Installed to cache:
|
|
233
|
+
```
|
|
234
|
+
~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/plugins/commit-commands/
|
|
235
|
+
~/.openpackage/cache/git/a1b2c3d4e5f6/abc1234/plugins/pr-review-toolkit/
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 4.4 Plugin naming convention
|
|
239
|
+
|
|
240
|
+
**Scoped naming for GitHub plugins**: Plugins installed from GitHub repositories use scoped names to provide clear provenance and prevent naming conflicts.
|
|
241
|
+
|
|
242
|
+
**Naming formats**:
|
|
243
|
+
- **Marketplace plugin**: `@<username>/<marketplace-name>/<plugin-name>`
|
|
244
|
+
- **Standalone plugin**: `@<username>/<plugin-name>`
|
|
245
|
+
- **Non-GitHub source**: `<plugin-name>` (no scoping)
|
|
246
|
+
|
|
247
|
+
**Examples**:
|
|
248
|
+
| Source | Plugin Name | Scoped Name |
|
|
249
|
+
|--------|-------------|-------------|
|
|
250
|
+
| `github:anthropics/claude-code#subdirectory=plugins/commit-commands` | `commit-commands` | `@anthropics/claude-code/commit-commands` |
|
|
251
|
+
| `github:anthropics/my-plugin` | `my-plugin` | `@anthropics/my-plugin` |
|
|
252
|
+
| `git:https://gitlab.com/user/plugin.git` | `cool-plugin` | `cool-plugin` (no scoping) |
|
|
253
|
+
| `./local-plugin/` | `local-plugin` | `local-plugin` (no scoping) |
|
|
254
|
+
|
|
255
|
+
**Fallback behavior**:
|
|
256
|
+
1. **Plugin name missing**: Uses subdirectory basename → repo name → "unnamed-plugin"
|
|
257
|
+
2. **Marketplace name missing**: Uses repo name → "unnamed-marketplace"
|
|
258
|
+
|
|
259
|
+
**Benefits**:
|
|
260
|
+
- Clear GitHub provenance at a glance
|
|
261
|
+
- No name conflicts between authors
|
|
262
|
+
- Easy to identify plugin source
|
|
263
|
+
- Consistent with npm/yarn scoping patterns
|
|
264
|
+
|
|
265
|
+
### 4.5 Plugin transformation details
|
|
189
266
|
|
|
190
267
|
**In-memory transformation** (no registry copy):
|
|
191
268
|
- Plugin manifest fields map to OpenPackage metadata:
|
|
@@ -213,8 +290,8 @@ packages:
|
|
|
213
290
|
|
|
214
291
|
### 5.1 Current limitations
|
|
215
292
|
|
|
216
|
-
- No lockfile
|
|
217
|
-
-
|
|
293
|
+
- **No lockfile pinning**: Commit SHAs are resolved but not persisted to `openpackage.yml` (no `resolvedSha` field).
|
|
294
|
+
- **Branch tracking**: Installing from a branch will use the latest commit at install time, not track updates.
|
|
218
295
|
- Authentication behavior is delegated to `git` (credentials configured in the user's environment).
|
|
219
296
|
|
|
220
297
|
### 5.2 Subdirectory support notes
|
|
@@ -223,8 +300,19 @@ packages:
|
|
|
223
300
|
- Subdirectory must contain a valid package or plugin manifest.
|
|
224
301
|
- For OpenPackage packages in subdirectories, their dependencies are resolved relative to the subdirectory location.
|
|
225
302
|
|
|
226
|
-
### 5.3
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
303
|
+
### 5.3 Cache features
|
|
304
|
+
|
|
305
|
+
**Implemented**:
|
|
306
|
+
- ✅ Structured cache at `~/.openpackage/cache/git/`
|
|
307
|
+
- ✅ Deterministic paths based on URL hash + commit SHA
|
|
308
|
+
- ✅ Automatic cache reuse when same commit exists
|
|
309
|
+
- ✅ Metadata tracking (`.opkg-repo.json`, `.opkg-commit.json`)
|
|
310
|
+
- ✅ Shallow clones for space efficiency
|
|
311
|
+
- ✅ Persistent across reboots
|
|
312
|
+
|
|
313
|
+
**Future considerations**:
|
|
314
|
+
- Cache management commands (`opkg cache list`, `opkg cache clean`)
|
|
315
|
+
- Cache update detection (detect when branch has new commits)
|
|
316
|
+
- Git worktrees for further space optimization
|
|
317
|
+
- Automatic cache cleanup based on age/size
|
|
318
|
+
- Commit SHA lockfile support for reproducible installs
|
package/specs/new/README.md
CHANGED
|
@@ -20,7 +20,9 @@ Creates a new package with an `openpackage.yml` manifest in one of three predefi
|
|
|
20
20
|
- `[package-name]` (optional for root scope, required for local/global)
|
|
21
21
|
- Package name following OpenPackage naming conventions
|
|
22
22
|
- Supports scoped packages (`@org/package-name`)
|
|
23
|
-
-
|
|
23
|
+
- Supports hierarchical packages (`@org/package-name/subpackage`)
|
|
24
|
+
- Validated against naming rules (lowercase, alphanumeric, dots, underscores, hyphens, slashes)
|
|
25
|
+
- No consecutive or trailing slashes allowed
|
|
24
26
|
|
|
25
27
|
### Options
|
|
26
28
|
- `--scope <scope>` - Package scope: `root`, `local`, or `global` (prompts if not specified in interactive mode)
|
package/specs/set/README.md
CHANGED
|
@@ -126,16 +126,19 @@ opkg set --ver 0.1.0
|
|
|
126
126
|
**Format:** Package name string
|
|
127
127
|
|
|
128
128
|
**Validation:**
|
|
129
|
-
- Must contain only: `a-z`, `0-9`, `.`, `_`,
|
|
129
|
+
- Must contain only: `a-z`, `0-9`, `.`, `_`, `-`, `/`
|
|
130
130
|
- Automatically normalized to lowercase
|
|
131
131
|
- No spaces allowed
|
|
132
132
|
- Supports scoped names: `@scope/package-name`
|
|
133
|
+
- Supports hierarchical names: `@scope/package-name/subpackage`
|
|
134
|
+
- No consecutive or trailing slashes
|
|
133
135
|
|
|
134
136
|
**Examples:**
|
|
135
137
|
```bash
|
|
136
138
|
opkg set --name my-package
|
|
137
139
|
opkg set --name @myorg/my-package
|
|
138
140
|
opkg set --name package.name
|
|
141
|
+
opkg set --name @anthropics/claude-code/commit-commands
|
|
139
142
|
```
|
|
140
143
|
|
|
141
144
|
### Description Field (`--description`)
|
|
@@ -305,7 +308,7 @@ Version must be valid semver (e.g., 1.0.0, 2.1.3-beta.1)
|
|
|
305
308
|
**Error:** Name contains invalid characters
|
|
306
309
|
|
|
307
310
|
```
|
|
308
|
-
Error: Package name 'invalid name' contains invalid characters (use only: a-z, 0-9, ., _, -)
|
|
311
|
+
Error: Package name 'invalid name' segment 'invalid name' contains invalid characters (use only: a-z, 0-9, ., _, -)
|
|
309
312
|
```
|
|
310
313
|
|
|
311
314
|
### Invalid URL
|