uncompact 0.44.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 +284 -0
- package/hooks/hooks.json +80 -0
- package/npm/install.js +183 -0
- package/npm/run.js +63 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Uncompact
|
|
2
|
+
|
|
3
|
+
[](https://github.com/supermodeltools/Uncompact)
|
|
4
|
+
|
|
5
|
+
> Stop Claude Code compaction from making your AI stupid.
|
|
6
|
+
|
|
7
|
+
Uncompact hooks into Claude Code's lifecycle to reinject a high-density "context bomb" after compaction. It's powered by the [Supermodel Public API](https://supermodeltools.com) and stores versioned project graphs locally in SQLite.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⭐ Star the Supermodel Ecosystem
|
|
12
|
+
|
|
13
|
+
If this is useful, please star our tools — it helps us grow:
|
|
14
|
+
|
|
15
|
+
[](https://github.com/supermodeltools/mcp) [](https://github.com/supermodeltools/mcpbr) [](https://github.com/supermodeltools/typescript-sdk) [](https://github.com/supermodeltools/arch-docs) [](https://github.com/supermodeltools/dead-code-hunter) [](https://github.com/supermodeltools/Uncompact) [](https://github.com/supermodeltools/narsil-mcp)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
Claude Code compaction occurs
|
|
23
|
+
↓
|
|
24
|
+
Stop hook fires → uncompact run
|
|
25
|
+
↓
|
|
26
|
+
Check local SQLite cache (TTL: 15 min)
|
|
27
|
+
↓ (cache miss or stale)
|
|
28
|
+
Zip repo → POST /v1/graphs/supermodel
|
|
29
|
+
↓
|
|
30
|
+
Poll for result (async job)
|
|
31
|
+
↓
|
|
32
|
+
Render token-budgeted Markdown context bomb
|
|
33
|
+
↓
|
|
34
|
+
Claude Code receives context via stdout
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Claude Code Plugin
|
|
38
|
+
|
|
39
|
+
The fastest way to integrate Uncompact is via the Claude Code plugin system — no manual hook installation required.
|
|
40
|
+
|
|
41
|
+
### Install the plugin
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
/plugin marketplace add supermodeltools/Uncompact
|
|
45
|
+
/plugin install uncompact@supermodeltools-Uncompact
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This installs both hooks automatically:
|
|
49
|
+
|
|
50
|
+
- **`SessionStart`** — runs `scripts/setup.sh` which auto-installs the `uncompact` binary via `go install` if not already present.
|
|
51
|
+
- **`Stop`** — runs `scripts/uncompact-hook.sh` which reinjects context after every compaction event.
|
|
52
|
+
|
|
53
|
+
> **Note:** After plugin installation, authenticate once with `uncompact auth login` to connect your Supermodel API key. That's it — no manual binary install or hook setup required.
|
|
54
|
+
|
|
55
|
+
### CI / GitHub Actions
|
|
56
|
+
|
|
57
|
+
In remote environments (`CLAUDE_CODE_REMOTE=true`), the hook automatically enables `--fallback` mode. Pass your API key via the `SUPERMODEL_API_KEY` environment variable:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
env:
|
|
61
|
+
SUPERMODEL_API_KEY: ${{ secrets.SUPERMODEL_API_KEY }}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
### 1. Install
|
|
69
|
+
|
|
70
|
+
**Via npm (recommended):**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install -g uncompact
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or run without installing:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx uncompact --help
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Via Go:**
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
go install github.com/supermodeltools/uncompact@latest
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Or download a binary** from [Releases](https://github.com/supermodeltools/Uncompact/releases).
|
|
89
|
+
|
|
90
|
+
### 2. Authenticate
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uncompact auth login
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This opens [dashboard.supermodeltools.com](https://dashboard.supermodeltools.com) where you can subscribe and generate an API key.
|
|
97
|
+
|
|
98
|
+
### 3. Install Claude Code Hooks
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uncompact install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This auto-detects your Claude Code `settings.json`, shows a diff, and merges the hooks non-destructively.
|
|
105
|
+
|
|
106
|
+
### 4. Verify
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uncompact verify-install
|
|
110
|
+
uncompact run --debug
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## CLI Reference
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
uncompact auth login # Authenticate via dashboard.supermodeltools.com
|
|
117
|
+
uncompact auth status # Show auth status and API key validity
|
|
118
|
+
uncompact auth logout # Remove stored API key
|
|
119
|
+
|
|
120
|
+
uncompact install # Merge hooks into Claude Code settings.json (with diff preview)
|
|
121
|
+
uncompact install --dry-run # Preview changes without writing
|
|
122
|
+
uncompact verify-install # Check if hooks are correctly installed
|
|
123
|
+
|
|
124
|
+
uncompact run # Emit context bomb to stdout (used by the hook)
|
|
125
|
+
uncompact run --debug # Show debug output on stderr
|
|
126
|
+
uncompact run --force-refresh # Bypass cache and fetch fresh from API
|
|
127
|
+
uncompact run --max-tokens 1500 # Cap context bomb at 1500 tokens
|
|
128
|
+
uncompact run --fallback # Emit minimal static context on failure
|
|
129
|
+
|
|
130
|
+
uncompact dry-run # Preview context bomb without emitting it
|
|
131
|
+
uncompact status # Show last injection, cache state, auth status
|
|
132
|
+
uncompact logs # Show recent injection activity
|
|
133
|
+
uncompact logs --tail 50 # Show last 50 entries
|
|
134
|
+
uncompact stats # Token usage, cache hit rate, API call count
|
|
135
|
+
|
|
136
|
+
uncompact cache clear # Clear all cached graph data
|
|
137
|
+
uncompact cache clear --project # Clear only the current project's cache
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
### Environment Variables
|
|
143
|
+
|
|
144
|
+
| Variable | Description |
|
|
145
|
+
|----------|-------------|
|
|
146
|
+
| `SUPERMODEL_API_KEY` | Supermodel API key (overrides config file) |
|
|
147
|
+
|
|
148
|
+
### Config File
|
|
149
|
+
|
|
150
|
+
Located at `~/.config/uncompact/config.json` (Linux/macOS) or `%APPDATA%\uncompact\config.json` (Windows).
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"api_key": "your-api-key-here",
|
|
155
|
+
"base_url": "https://api.supermodeltools.com",
|
|
156
|
+
"max_tokens": 2000
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### CLI Flags (Global)
|
|
161
|
+
|
|
162
|
+
| Flag | Default | Description |
|
|
163
|
+
|------|---------|-------------|
|
|
164
|
+
| `--api-key` | | Override API key |
|
|
165
|
+
| `--max-tokens` | `2000` | Max tokens in context bomb |
|
|
166
|
+
| `--force-refresh` | `false` | Bypass cache |
|
|
167
|
+
| `--fallback` | `false` | Emit minimal static context on failure |
|
|
168
|
+
| `--debug` | `false` | Debug output to stderr |
|
|
169
|
+
|
|
170
|
+
## Manual Hook Installation
|
|
171
|
+
|
|
172
|
+
If `uncompact install` doesn't work for your setup, add this to your Claude Code `settings.json`:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"hooks": {
|
|
177
|
+
"Stop": [
|
|
178
|
+
{
|
|
179
|
+
"hooks": [
|
|
180
|
+
{
|
|
181
|
+
"type": "command",
|
|
182
|
+
"command": "uncompact run"
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
See the [Claude Code hooks guide](https://code.claude.com/docs/en/hooks-guide#re-inject-context-after-compaction) for more details.
|
|
192
|
+
|
|
193
|
+
## Architecture
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
uncompact/
|
|
197
|
+
├── main.go
|
|
198
|
+
├── cmd/
|
|
199
|
+
│ ├── root.go # Root command, global flags
|
|
200
|
+
│ ├── run.go # Core hook command
|
|
201
|
+
│ ├── auth.go # auth login/status/logout
|
|
202
|
+
│ ├── install.go # install, verify-install
|
|
203
|
+
│ └── status.go # status, logs, stats, dry-run, cache
|
|
204
|
+
└── internal/
|
|
205
|
+
├── api/
|
|
206
|
+
│ └── client.go # Supermodel API client (async 200/202 polling)
|
|
207
|
+
├── cache/
|
|
208
|
+
│ └── store.go # SQLite cache (TTL, staleness, injection log)
|
|
209
|
+
├── config/
|
|
210
|
+
│ └── config.go # Config loading (flag > env > file)
|
|
211
|
+
├── hooks/
|
|
212
|
+
│ └── hooks.go # settings.json installer (non-destructive)
|
|
213
|
+
├── project/
|
|
214
|
+
│ └── project.go # Git-aware project detection
|
|
215
|
+
├── template/
|
|
216
|
+
│ └── render.go # Token-budgeted Markdown renderer
|
|
217
|
+
└── zip/
|
|
218
|
+
└── zip.go # Repo zipper (excludes .git, node_modules, etc.)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Caching Strategy
|
|
222
|
+
|
|
223
|
+
| Concern | Policy |
|
|
224
|
+
|---------|--------|
|
|
225
|
+
| Default TTL | 15 minutes |
|
|
226
|
+
| Stale cache | Served with `⚠️ STALE` warning; fresh fetch attempted |
|
|
227
|
+
| API unavailable | Serve most recent cache entry silently |
|
|
228
|
+
| No cache + API down | Silent exit 0 (never blocks Claude Code) |
|
|
229
|
+
| Storage growth | Auto-prune entries older than 30 days |
|
|
230
|
+
| Force refresh | `--force-refresh` flag |
|
|
231
|
+
|
|
232
|
+
### Context Bomb Design
|
|
233
|
+
|
|
234
|
+
The context bomb is capped at `--max-tokens` (default: 2,000) to avoid triggering the very compaction it's trying to prevent. Sections are rendered in priority order:
|
|
235
|
+
|
|
236
|
+
1. **Required** (always included): Project overview, language, stats
|
|
237
|
+
2. **Optional** (filled to token budget): Domain map, key files, dependencies
|
|
238
|
+
|
|
239
|
+
### Fallback Chain
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
1. Fresh cache hit → serve immediately
|
|
243
|
+
2. Cache miss / stale → fetch from API → cache result
|
|
244
|
+
3. API fails + stale cache exists → serve stale with warning
|
|
245
|
+
4. API fails + no cache → silent exit 0 (or minimal static if --fallback)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## KPIs
|
|
249
|
+
|
|
250
|
+
| Metric | Target |
|
|
251
|
+
|--------|--------|
|
|
252
|
+
| Clarifying questions per session (proxy for context loss) | ↓ after Uncompact |
|
|
253
|
+
| Post-compaction task completion rate | ↑ after Uncompact |
|
|
254
|
+
| Token efficiency (useful tokens / injected tokens) | > 80% |
|
|
255
|
+
| 7-day retention | % of users who keep hooks enabled |
|
|
256
|
+
|
|
257
|
+
## Building from Source
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
git clone https://github.com/supermodeltools/Uncompact
|
|
261
|
+
cd Uncompact
|
|
262
|
+
go mod tidy
|
|
263
|
+
go build -o uncompact .
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Requires Go 1.22+.
|
|
267
|
+
|
|
268
|
+
## Add to your project
|
|
269
|
+
|
|
270
|
+
Show that your project uses Uncompact by adding this badge to your README:
|
|
271
|
+
|
|
272
|
+
```markdown
|
|
273
|
+
[](https://github.com/supermodeltools/Uncompact)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Or with Shields.io:
|
|
277
|
+
|
|
278
|
+
```markdown
|
|
279
|
+
[](https://github.com/supermodeltools/Uncompact)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## License
|
|
283
|
+
|
|
284
|
+
MIT
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh",
|
|
10
|
+
"timeout": 60
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pregen-hook.sh",
|
|
15
|
+
"timeout": 10
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"matcher": "clear",
|
|
21
|
+
"hooks": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh",
|
|
25
|
+
"timeout": 60
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pregen-hook.sh",
|
|
30
|
+
"timeout": 10
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"matcher": "compact",
|
|
36
|
+
"hooks": [
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/uncompact-hook.sh",
|
|
40
|
+
"timeout": 120
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"PreCompact": [
|
|
46
|
+
{
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pregen-hook.sh",
|
|
51
|
+
"timeout": 10
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"PostToolUse": [
|
|
57
|
+
{
|
|
58
|
+
"matcher": "Write|Edit",
|
|
59
|
+
"hooks": [
|
|
60
|
+
{
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/pregen-hook.sh",
|
|
63
|
+
"timeout": 10
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"UserPromptSubmit": [
|
|
69
|
+
{
|
|
70
|
+
"hooks": [
|
|
71
|
+
{
|
|
72
|
+
"type": "command",
|
|
73
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/show-hook.sh",
|
|
74
|
+
"timeout": 5
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
package/npm/install.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { execSync, execFileSync } = require("child_process");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
|
|
9
|
+
const REPO_OWNER = "supermodeltools";
|
|
10
|
+
const REPO_NAME = "Uncompact";
|
|
11
|
+
const BINARY_NAME = "uncompact";
|
|
12
|
+
|
|
13
|
+
function getPackageVersion() {
|
|
14
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
16
|
+
return pkg.version;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getPlatform() {
|
|
20
|
+
const platform = os.platform();
|
|
21
|
+
if (platform === "darwin") return "darwin";
|
|
22
|
+
if (platform === "linux") return "linux";
|
|
23
|
+
if (platform === "win32") return "windows";
|
|
24
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getArch() {
|
|
28
|
+
const arch = os.arch();
|
|
29
|
+
if (arch === "x64") return "amd64";
|
|
30
|
+
if (arch === "arm64") return "arm64";
|
|
31
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getBinaryName(platform) {
|
|
35
|
+
return platform === "windows" ? `${BINARY_NAME}.exe` : BINARY_NAME;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getAssetName(platform, arch) {
|
|
39
|
+
const ext = platform === "windows" ? ".zip" : ".tar.gz";
|
|
40
|
+
return `${BINARY_NAME}_${platform}_${arch}${ext}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function httpsGet(url) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const request = https.get(url, { headers: { "User-Agent": "uncompact-npm" } }, (response) => {
|
|
46
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
47
|
+
httpsGet(response.headers.location).then(resolve).catch(reject);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (response.statusCode !== 200) {
|
|
51
|
+
reject(new Error(`HTTP ${response.statusCode}: ${url}`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const chunks = [];
|
|
55
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
56
|
+
response.on("end", () => resolve(Buffer.concat(chunks)));
|
|
57
|
+
response.on("error", reject);
|
|
58
|
+
});
|
|
59
|
+
request.setTimeout(20000, () => {
|
|
60
|
+
request.destroy();
|
|
61
|
+
reject(new Error(`Request timed out: ${url}`));
|
|
62
|
+
});
|
|
63
|
+
request.on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function httpsGetJson(url) {
|
|
68
|
+
return httpsGet(url).then((buffer) => JSON.parse(buffer.toString()));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function getRelease(version) {
|
|
72
|
+
if (version && version !== "0.0.0") {
|
|
73
|
+
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
74
|
+
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`;
|
|
75
|
+
return await httpsGetJson(url);
|
|
76
|
+
}
|
|
77
|
+
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
78
|
+
return httpsGetJson(url);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extractTarGz(buffer, destDir, binaryName) {
|
|
82
|
+
const tarPath = path.join(destDir, "archive.tar.gz");
|
|
83
|
+
fs.writeFileSync(tarPath, buffer);
|
|
84
|
+
execFileSync("tar", ["-xzf", tarPath, "-C", destDir], { stdio: "pipe" });
|
|
85
|
+
fs.unlinkSync(tarPath);
|
|
86
|
+
|
|
87
|
+
const extracted = path.join(destDir, binaryName);
|
|
88
|
+
if (!fs.existsSync(extracted)) {
|
|
89
|
+
throw new Error(`Binary not found after extraction: ${extracted}`);
|
|
90
|
+
}
|
|
91
|
+
return extracted;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function extractZip(buffer, destDir, binaryName) {
|
|
95
|
+
const zipPath = path.join(destDir, "archive.zip");
|
|
96
|
+
fs.writeFileSync(zipPath, buffer);
|
|
97
|
+
|
|
98
|
+
if (process.platform === "win32") {
|
|
99
|
+
execFileSync("powershell", ["-Command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`], { stdio: "pipe" });
|
|
100
|
+
} else {
|
|
101
|
+
execFileSync("unzip", ["-o", zipPath, "-d", destDir], { stdio: "pipe" });
|
|
102
|
+
}
|
|
103
|
+
fs.unlinkSync(zipPath);
|
|
104
|
+
|
|
105
|
+
const extracted = path.join(destDir, binaryName);
|
|
106
|
+
if (!fs.existsSync(extracted)) {
|
|
107
|
+
throw new Error(`Binary not found after extraction: ${extracted}`);
|
|
108
|
+
}
|
|
109
|
+
return extracted;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
const platform = getPlatform();
|
|
114
|
+
const arch = getArch();
|
|
115
|
+
const assetName = getAssetName(platform, arch);
|
|
116
|
+
const binaryName = getBinaryName(platform);
|
|
117
|
+
const binDir = path.join(__dirname, "bin");
|
|
118
|
+
|
|
119
|
+
console.log(`[uncompact] Installing ${BINARY_NAME} for ${platform}/${arch}...`);
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(binDir)) {
|
|
122
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const destPath = path.join(binDir, binaryName);
|
|
126
|
+
|
|
127
|
+
if (fs.existsSync(destPath)) {
|
|
128
|
+
console.log(`[uncompact] Binary already exists at ${destPath}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const version = getPackageVersion();
|
|
133
|
+
let release;
|
|
134
|
+
try {
|
|
135
|
+
release = await getRelease(version);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(`[uncompact] Failed to fetch release: ${err.message}`);
|
|
138
|
+
console.error(`[uncompact] You can install manually: go install github.com/${REPO_OWNER}/${REPO_NAME.toLowerCase()}@latest`);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
143
|
+
if (!asset) {
|
|
144
|
+
console.error(`[uncompact] No binary found for ${platform}/${arch} in release ${release.tag_name}`);
|
|
145
|
+
console.error(`[uncompact] Available assets: ${release.assets.map((a) => a.name).join(", ")}`);
|
|
146
|
+
console.error(`[uncompact] You can install manually: go install github.com/${REPO_OWNER}/${REPO_NAME.toLowerCase()}@latest`);
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(`[uncompact] Downloading ${asset.name}...`);
|
|
151
|
+
|
|
152
|
+
let buffer;
|
|
153
|
+
try {
|
|
154
|
+
buffer = await httpsGet(asset.browser_download_url);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(`[uncompact] Failed to download: ${err.message}`);
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`[uncompact] Extracting...`);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
if (platform === "windows") {
|
|
164
|
+
extractZip(buffer, binDir, binaryName);
|
|
165
|
+
} else {
|
|
166
|
+
extractTarGz(buffer, binDir, binaryName);
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error(`[uncompact] Failed to extract: ${err.message}`);
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (platform !== "windows") {
|
|
174
|
+
fs.chmodSync(destPath, 0o755);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(`[uncompact] Installed to ${destPath}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
main().catch((err) => {
|
|
181
|
+
console.error(`[uncompact] Installation failed: ${err.message}`);
|
|
182
|
+
process.exit(0);
|
|
183
|
+
});
|
package/npm/run.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
const BINARY_NAME = os.platform() === "win32" ? "uncompact.exe" : "uncompact";
|
|
9
|
+
|
|
10
|
+
function findBinary() {
|
|
11
|
+
const localBin = path.join(__dirname, "bin", BINARY_NAME);
|
|
12
|
+
if (fs.existsSync(localBin)) {
|
|
13
|
+
return localBin;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const globalPaths = [
|
|
17
|
+
path.join(os.homedir(), "go", "bin", BINARY_NAME),
|
|
18
|
+
path.join(os.homedir(), ".local", "bin", BINARY_NAME),
|
|
19
|
+
"/usr/local/bin/" + BINARY_NAME,
|
|
20
|
+
"/opt/homebrew/bin/" + BINARY_NAME,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const p of globalPaths) {
|
|
24
|
+
if (fs.existsSync(p)) {
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const binary = findBinary();
|
|
34
|
+
|
|
35
|
+
if (!binary) {
|
|
36
|
+
console.error("[uncompact] Binary not found. Try reinstalling:");
|
|
37
|
+
console.error(" npm install -g uncompact");
|
|
38
|
+
console.error("Or install via Go:");
|
|
39
|
+
console.error(" go install github.com/supermodeltools/uncompact@latest");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
const child = spawn(binary, args, {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
env: process.env,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
child.on("error", (err) => {
|
|
50
|
+
console.error(`[uncompact] Failed to run binary: ${err.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
child.on("close", (code, signal) => {
|
|
55
|
+
if (code === null && signal) {
|
|
56
|
+
process.kill(process.pid, signal);
|
|
57
|
+
} else {
|
|
58
|
+
process.exit(code ?? 0);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uncompact",
|
|
3
|
+
"version": "0.44.0",
|
|
4
|
+
"description": "Stop Claude Code compaction from making your AI stupid. Re-inject project context after compaction via the Supermodel API.",
|
|
5
|
+
"author": "Supermodel <abe@supermodel.software>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/supermodeltools/Uncompact",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/supermodeltools/Uncompact.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/supermodeltools/Uncompact/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"cli",
|
|
19
|
+
"ai",
|
|
20
|
+
"context",
|
|
21
|
+
"compaction",
|
|
22
|
+
"supermodel"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"uncompact": "npm/run.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"postinstall": "node npm/install.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"npm/",
|
|
32
|
+
"hooks/"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=16"
|
|
36
|
+
}
|
|
37
|
+
}
|