shardstitch 0.0.9 → 0.0.11
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 +71 -65
- package/cli.js +347 -327
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,65 +1,71 @@
|
|
|
1
|
-
# ShardStitch
|
|
2
|
-
|
|
3
|
-
**"Rate limit reached." "Context window full." "VRAM out of memory."**
|
|
4
|
-
|
|
5
|
-
Your code is half-written. Your plan is in your head. ShardStitch captures your entire session — git diff, changed files, dependency graph, intent — and stitches it into the next AI tool. One hotkey. Keep building like nothing happened.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install shardstitch
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
# ShardStitch
|
|
2
|
+
|
|
3
|
+
**"Rate limit reached." "Context window full." "VRAM out of memory."**
|
|
4
|
+
|
|
5
|
+
Your code is half-written. Your plan is in your head. ShardStitch captures your entire session — git diff, changed files, dependency graph, intent — and stitches it into the next AI tool. One hotkey. Keep building like nothing happened.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g shardstitch # installs the launcher
|
|
11
|
+
shardstitch install <your-license-key> # after purchase — downloads & activates the app
|
|
12
|
+
shardstitch # launch the dashboard
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
ShardStitch is a **one-time purchase** (no subscription). `npm install` gives you the
|
|
16
|
+
installer; activate it with the license key from your purchase email. Get a key at
|
|
17
|
+
[shardstitch.com](https://shardstitch.com).
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
When Claude, Cursor, Codex, or Gemini locks you out mid-session, ShardStitch:
|
|
22
|
+
|
|
23
|
+
1. **Scans your project** — git diff, changed files, recent commits, your notes
|
|
24
|
+
2. **Builds a dependency graph** — impact radius, architecture communities — and warns the next AI what's dangerous to touch
|
|
25
|
+
3. **Generates a continuation prompt** formatted for the target tool
|
|
26
|
+
4. **Injects it** into the tool's pickup file (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules) for automatic discovery
|
|
27
|
+
|
|
28
|
+
Under 30 seconds, end to end.
|
|
29
|
+
|
|
30
|
+
## Not just for switching tools
|
|
31
|
+
|
|
32
|
+
The same trick fixes a bloated session in the *same* tool. Long AI chats accumulate a huge context tree — token burn climbs, responses slow down, and eventually you hit "Service is busy" even with quota left. ShardStitch is the clean-restart button: extract a compact context, open a fresh window of the same tool, paste, keep going.
|
|
33
|
+
|
|
34
|
+
## 23 supported AI tools
|
|
35
|
+
|
|
36
|
+
Claude Code, Claude Desktop, Cursor, Codex CLI, Gemini CLI, Windsurf, Aider, Kiro, Amazon Q Developer, DeepSeek, OpenCode, Trae, Factory Droid, OpenClaw, Amp, Cline, Roo Code, Kilo Code, Crush, Kimi, Qwen Code, Antigravity, and Grok.
|
|
37
|
+
|
|
38
|
+
## 7 surfaces
|
|
39
|
+
|
|
40
|
+
- **Web dashboard** — scan, generate, copy, download
|
|
41
|
+
- **VS Code / Cursor extension** — Alt+G hotkey
|
|
42
|
+
- **MCP server** — Claude Code, Cursor, and Windsurf call it directly
|
|
43
|
+
- **CLI** — `npx shardstitch` or `shardstitch capture`
|
|
44
|
+
- **Desktop app** — standalone Windows exe
|
|
45
|
+
- **Autosave hooks** — auto-inject on session end
|
|
46
|
+
- **Local LLM failover** — Ollama, vLLM, LM Studio (zero cloud)
|
|
47
|
+
|
|
48
|
+
## Key features
|
|
49
|
+
|
|
50
|
+
- **Per-agent formatting** — 22 per-agent format adapters across the 23 supported tools
|
|
51
|
+
- **AI task router** — semantic matching picks the best tool for each task
|
|
52
|
+
- **Persistent memory** — project decisions survive across sessions
|
|
53
|
+
- **Team coordination** — shared `.shardstitch/` directory
|
|
54
|
+
- **Tamper-evident audit trail** — hash-chained timeline logs
|
|
55
|
+
- **Dependency graph analysis** — impact radius, god nodes, architecture communities
|
|
56
|
+
|
|
57
|
+
## Local-first
|
|
58
|
+
|
|
59
|
+
Your code and conversations never leave your machine — no telemetry, no cloud
|
|
60
|
+
processing. The **only** network call is a single HTTPS request at activation to
|
|
61
|
+
validate your license key; everything else runs entirely on your hardware.
|
|
62
|
+
Activation needs a license key, not an account.
|
|
63
|
+
|
|
64
|
+
## Links
|
|
65
|
+
|
|
66
|
+
- Website: https://shardstitch.com
|
|
67
|
+
- GitHub: https://github.com/shardstitch/shardstitch
|
|
68
|
+
- X / Twitter: https://x.com/ShardStitch
|
|
69
|
+
- VS Code / Cursor Extension: https://marketplace.visualstudio.com/items?itemName=shardstitch.shardstitch
|
|
70
|
+
- Changelog: https://github.com/shardstitch/shardstitch/releases
|
|
71
|
+
- Support: support@shardstitch.com
|
package/cli.js
CHANGED
|
@@ -1,327 +1,347 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ShardStitch launcher — `shardstitch` or `shardstitch install <key>`
|
|
3
|
-
// Finds a local install and starts the dashboard.
|
|
4
|
-
// Run `shardstitch install <key>` after purchase to download the app.
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
const { existsSync, mkdirSync, createWriteStream, chmodSync, renameSync } = require("fs");
|
|
8
|
-
const { join, dirname } = require("path");
|
|
9
|
-
const { createHash } = require("crypto");
|
|
10
|
-
const os = require("os");
|
|
11
|
-
const https = require("https");
|
|
12
|
-
const fs = require("fs");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
res.on("
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
res.on("error",
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.log(`
|
|
269
|
-
console.log("
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
console.log("
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ShardStitch launcher — `shardstitch` or `shardstitch install <key>`
|
|
3
|
+
// Finds a local install and starts the dashboard.
|
|
4
|
+
// Run `shardstitch install <key>` after purchase to download the app.
|
|
5
|
+
|
|
6
|
+
const { spawn } = require("child_process");
|
|
7
|
+
const { existsSync, mkdirSync, createWriteStream, chmodSync, renameSync } = require("fs");
|
|
8
|
+
const { join, dirname } = require("path");
|
|
9
|
+
const { createHash } = require("crypto");
|
|
10
|
+
const os = require("os");
|
|
11
|
+
const https = require("https");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
|
|
14
|
+
// The installer never talks to Polar or any storage host directly. It POSTs the
|
|
15
|
+
// license key to a gated endpoint on our own domain (a Cloudflare Worker; see
|
|
16
|
+
// cloudflare/download-worker/). That gate validates the key server-side, fetches
|
|
17
|
+
// the paid binary from Polar on the customer's behalf, and streams it back with
|
|
18
|
+
// the file's real SHA-256 in the X-Checksum-Sha256 header. The binary therefore
|
|
19
|
+
// has no public URL and only a valid paid key can trigger a download.
|
|
20
|
+
const GATE_URL = process.env.SHARDSTITCH_GATE_URL || "https://shardstitch.com/api/download";
|
|
21
|
+
|
|
22
|
+
// Hard ceiling on the download so a misbehaving or impersonating gate cannot
|
|
23
|
+
// fill the disk before the integrity check runs.
|
|
24
|
+
const MAX_DOWNLOAD_BYTES = 500 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
const INSTALL_DIR = join(os.homedir(), ".shardstitch", "app");
|
|
27
|
+
const ACTIVATION_FILE = join(os.homedir(), ".shardstitch", "activation.json");
|
|
28
|
+
const MACHINE_SALT = "shardstitch-activation/1";
|
|
29
|
+
|
|
30
|
+
// --- Supply-chain hardening -------------------------------------------------
|
|
31
|
+
// Primary integrity anchor: the gate streams the binary together with its real
|
|
32
|
+
// SHA-256 (the checksum Polar computed for the uploaded file) in the
|
|
33
|
+
// X-Checksum-Sha256 header. We hash the bytes we wrote and require an exact
|
|
34
|
+
// match before the file is ever made executable or run. Fails CLOSED.
|
|
35
|
+
//
|
|
36
|
+
// Optional second anchor: a hash baked into this published package. Because npm
|
|
37
|
+
// guarantees this file's integrity independently, a populated value here means
|
|
38
|
+
// even a compromised gate cannot swap the binary. Leave it EMPTY to rely solely
|
|
39
|
+
// on the gate/Polar checksum (no republish needed when the binary is rebuilt);
|
|
40
|
+
// populate it (python tools/gen_checksums.py) to add the registry anchor at the
|
|
41
|
+
// cost of republishing on every rebuild. Keep in lockstep with
|
|
42
|
+
// packages/pypi/shardstitch/__init__.py _CHECKSUMS.
|
|
43
|
+
const EMBEDDED_CHECKSUMS = {
|
|
44
|
+
windows: "",
|
|
45
|
+
linux: "",
|
|
46
|
+
mac: "",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Downloads (and any redirect) are restricted to our own gate host.
|
|
50
|
+
const TRUSTED_DOWNLOAD_HOST_SUFFIXES = [
|
|
51
|
+
"shardstitch.com",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function isTrustedHost(hostname) {
|
|
55
|
+
const h = (hostname || "").toLowerCase();
|
|
56
|
+
return TRUSTED_DOWNLOAD_HOST_SUFFIXES.some(
|
|
57
|
+
(suffix) => h === suffix || h.endsWith("." + suffix)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const PLATFORM = process.platform === "win32" ? "windows"
|
|
62
|
+
: process.platform === "darwin" ? "mac"
|
|
63
|
+
: "linux";
|
|
64
|
+
|
|
65
|
+
const EXE_NAME = PLATFORM === "windows" ? "ShardStitch.exe" : "shardstitch";
|
|
66
|
+
|
|
67
|
+
const CANDIDATE_DIRS = [
|
|
68
|
+
process.env.SHARDSTITCH_HOME,
|
|
69
|
+
INSTALL_DIR,
|
|
70
|
+
join(os.homedir(), "ShardStitch"),
|
|
71
|
+
PLATFORM === "windows" ? join(process.env.LOCALAPPDATA || "", "ShardStitch") : null,
|
|
72
|
+
PLATFORM === "windows" ? "C:\\Program Files\\ShardStitch" : "/opt/shardstitch",
|
|
73
|
+
].filter(Boolean);
|
|
74
|
+
|
|
75
|
+
function findExe() {
|
|
76
|
+
for (const dir of CANDIDATE_DIRS) {
|
|
77
|
+
const p = join(dir, EXE_NAME);
|
|
78
|
+
if (existsSync(p)) return p;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function machineFingerprint() {
|
|
84
|
+
const raw = `${MACHINE_SALT}|${process.env.COMPUTERNAME || ""}|${process.env.USERNAME || os.userInfo().username}`;
|
|
85
|
+
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function activationSignature(key, store) {
|
|
89
|
+
const raw = `${MACHINE_SALT}|${key}|${store}|${machineFingerprint()}`;
|
|
90
|
+
return createHash("sha256").update(raw).digest("hex");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function saveActivation(key) {
|
|
94
|
+
mkdirSync(dirname(ACTIVATION_FILE), { recursive: true });
|
|
95
|
+
const record = {
|
|
96
|
+
key,
|
|
97
|
+
store: "polar",
|
|
98
|
+
activatedAt: Math.floor(Date.now() / 1000),
|
|
99
|
+
signature: activationSignature(key, "polar"),
|
|
100
|
+
};
|
|
101
|
+
fs.writeFileSync(ACTIVATION_FILE, JSON.stringify(record, null, 2));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isActivated() {
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(fs.readFileSync(ACTIVATION_FILE, "utf8"));
|
|
107
|
+
return data.signature === activationSignature(data.key || "", data.store || "");
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// POST the key to the gate; stream the binary to dest; resolve with the
|
|
114
|
+
// server-provided SHA-256 (X-Checksum-Sha256, lower-case) or "". Rejects with
|
|
115
|
+
// the gate's JSON error code (e.g. "invalid_key") or "HTTP <status>".
|
|
116
|
+
function downloadFromGate(key, platform, dest) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
let u;
|
|
119
|
+
try {
|
|
120
|
+
u = new URL(GATE_URL);
|
|
121
|
+
} catch {
|
|
122
|
+
return reject(new Error("Invalid gate URL"));
|
|
123
|
+
}
|
|
124
|
+
if (u.protocol !== "https:") {
|
|
125
|
+
return reject(new Error(`Refusing non-HTTPS gate (${u.protocol})`));
|
|
126
|
+
}
|
|
127
|
+
if (!isTrustedHost(u.hostname)) {
|
|
128
|
+
return reject(new Error(`Refusing untrusted gate host: ${u.hostname}`));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const payload = JSON.stringify({ key, platform });
|
|
132
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
133
|
+
const tmp = dest + ".tmp";
|
|
134
|
+
const file = createWriteStream(tmp);
|
|
135
|
+
let settled = false;
|
|
136
|
+
const fail = (err) => {
|
|
137
|
+
if (settled) return;
|
|
138
|
+
settled = true;
|
|
139
|
+
try { file.destroy(); } catch {}
|
|
140
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
141
|
+
reject(err);
|
|
142
|
+
};
|
|
143
|
+
// A mid-download disk failure emits 'error' on the write stream; route it
|
|
144
|
+
// through fail() so the partial .tmp is cleaned up instead of crashing.
|
|
145
|
+
file.on("error", fail);
|
|
146
|
+
|
|
147
|
+
const req = https.request({
|
|
148
|
+
hostname: u.hostname,
|
|
149
|
+
port: u.port || 443,
|
|
150
|
+
path: u.pathname,
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
155
|
+
"User-Agent": "ShardStitch-Installer/1.0",
|
|
156
|
+
"Accept": "application/octet-stream",
|
|
157
|
+
},
|
|
158
|
+
}, (res) => {
|
|
159
|
+
// The gate returns the binary on 200 and a JSON error otherwise. It does
|
|
160
|
+
// not redirect; treat any non-200 as an error.
|
|
161
|
+
if (res.statusCode !== 200) {
|
|
162
|
+
let b = "";
|
|
163
|
+
res.on("data", (c) => (b += c));
|
|
164
|
+
res.on("end", () => {
|
|
165
|
+
let code = "";
|
|
166
|
+
try { code = JSON.parse(b).error || ""; } catch {}
|
|
167
|
+
fail(new Error(code || `HTTP ${res.statusCode}`));
|
|
168
|
+
});
|
|
169
|
+
res.on("error", fail);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const checksum = (res.headers["x-checksum-sha256"] || "").toLowerCase();
|
|
173
|
+
const total = parseInt(res.headers["content-length"] || "0", 10);
|
|
174
|
+
let done = 0;
|
|
175
|
+
res.on("data", (chunk) => {
|
|
176
|
+
done += chunk.length;
|
|
177
|
+
if (done > MAX_DOWNLOAD_BYTES) {
|
|
178
|
+
res.destroy();
|
|
179
|
+
return fail(new Error("download exceeded size limit"));
|
|
180
|
+
}
|
|
181
|
+
// Honor backpressure so a fast gate + slow disk doesn't buffer in memory.
|
|
182
|
+
if (!file.write(chunk)) {
|
|
183
|
+
res.pause();
|
|
184
|
+
file.once("drain", () => res.resume());
|
|
185
|
+
}
|
|
186
|
+
if (total) {
|
|
187
|
+
process.stdout.write(`\r Downloading... ${Math.floor(done * 100 / total)}%`);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
res.on("end", () => {
|
|
191
|
+
file.end(() => {
|
|
192
|
+
if (settled) return;
|
|
193
|
+
if (total) process.stdout.write("\n");
|
|
194
|
+
try {
|
|
195
|
+
renameSync(tmp, dest);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return fail(e);
|
|
198
|
+
}
|
|
199
|
+
settled = true;
|
|
200
|
+
resolve(checksum);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
res.on("error", fail);
|
|
204
|
+
});
|
|
205
|
+
req.setTimeout(120000, () => req.destroy(new Error("gate timeout")));
|
|
206
|
+
req.on("error", fail);
|
|
207
|
+
req.write(payload);
|
|
208
|
+
req.end();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function sha256File(path) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const hash = createHash("sha256");
|
|
215
|
+
const stream = fs.createReadStream(path);
|
|
216
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
217
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
218
|
+
stream.on("error", reject);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function cmdInstall(key) {
|
|
223
|
+
key = key.trim();
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(" ShardStitch — verifying license key & downloading...");
|
|
226
|
+
console.log();
|
|
227
|
+
|
|
228
|
+
const exePath = join(INSTALL_DIR, EXE_NAME);
|
|
229
|
+
|
|
230
|
+
let checksumHeader = "";
|
|
231
|
+
try {
|
|
232
|
+
checksumHeader = await downloadFromGate(key, PLATFORM, exePath);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
const msg = String((e && e.message) || e);
|
|
235
|
+
if (msg.includes("invalid_key") || msg.includes("key_not_granted") ||
|
|
236
|
+
msg.includes("key_expired") || msg.includes("no_customer") || msg.includes("403")) {
|
|
237
|
+
console.log(" ✗ License key not valid or not active.");
|
|
238
|
+
console.log(" Check for typos, or email support@shardstitch.com.");
|
|
239
|
+
} else if (msg.includes("no_file_for_platform") || msg.includes("404")) {
|
|
240
|
+
console.log(` ✗ No ${PLATFORM} build is available for this license yet.`);
|
|
241
|
+
console.log(" Email support@shardstitch.com.");
|
|
242
|
+
} else if (msg.includes("missing_key") || msg.includes("bad_platform") || msg.includes("bad_request")) {
|
|
243
|
+
console.log(" ✗ The installer sent a bad request. Update and retry:");
|
|
244
|
+
console.log(" npm install -g shardstitch@latest");
|
|
245
|
+
} else if (msg.includes("_failed") || msg.includes("server_misconfigured") ||
|
|
246
|
+
msg.includes("gate timeout") || msg.includes("size limit") ||
|
|
247
|
+
msg.includes("500") || msg.includes("502")) {
|
|
248
|
+
console.log(" ✗ The download service is temporarily unavailable.");
|
|
249
|
+
console.log(" Try again in a few minutes, or email support@shardstitch.com.");
|
|
250
|
+
} else {
|
|
251
|
+
console.log(` ✗ Download failed: ${msg}`);
|
|
252
|
+
console.log(" Try again in a moment, or email support@shardstitch.com.");
|
|
253
|
+
}
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Integrity — verify the bytes we wrote against the gate's checksum (Polar's
|
|
258
|
+
// real SHA-256) and, if present, the embedded registry anchor. Fails closed.
|
|
259
|
+
const allowUnverified = process.argv.includes("--allow-unverified");
|
|
260
|
+
try {
|
|
261
|
+
const actual = (await sha256File(exePath)).toLowerCase();
|
|
262
|
+
const embedded = (EMBEDDED_CHECKSUMS[PLATFORM] || "").toLowerCase();
|
|
263
|
+
const mismatch = (label, expected) => {
|
|
264
|
+
try { fs.unlinkSync(exePath); } catch {}
|
|
265
|
+
console.log(" ✗ Integrity check FAILED — the downloaded file does not match");
|
|
266
|
+
console.log(` the ${label} checksum and was deleted. Do NOT run it.`);
|
|
267
|
+
console.log(` expected ${expected}`);
|
|
268
|
+
console.log(` actual ${actual}`);
|
|
269
|
+
console.log(" Report this to support@shardstitch.com.");
|
|
270
|
+
process.exit(1);
|
|
271
|
+
};
|
|
272
|
+
let verified = false;
|
|
273
|
+
if (checksumHeader) {
|
|
274
|
+
if (actual !== checksumHeader) mismatch("server (Polar)", checksumHeader);
|
|
275
|
+
verified = true;
|
|
276
|
+
}
|
|
277
|
+
if (embedded) {
|
|
278
|
+
if (actual !== embedded) mismatch("published package", embedded);
|
|
279
|
+
verified = true;
|
|
280
|
+
}
|
|
281
|
+
if (!verified) {
|
|
282
|
+
throw new Error("the server did not provide a checksum to verify against");
|
|
283
|
+
}
|
|
284
|
+
console.log(" ✓ Integrity verified (SHA-256).");
|
|
285
|
+
} catch (e) {
|
|
286
|
+
if (!allowUnverified) {
|
|
287
|
+
try { fs.unlinkSync(exePath); } catch {}
|
|
288
|
+
console.log(` ✗ Could not verify download integrity: ${e.message}`);
|
|
289
|
+
console.log(" Aborting for safety. Re-run with --allow-unverified to override");
|
|
290
|
+
console.log(" (not recommended), or contact support@shardstitch.com.");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
console.log(` ! Skipping integrity check (--allow-unverified): ${e.message}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (PLATFORM !== "windows") chmodSync(exePath, 0o755);
|
|
297
|
+
|
|
298
|
+
saveActivation(key);
|
|
299
|
+
|
|
300
|
+
console.log(` ✓ Installed to ${exePath}`);
|
|
301
|
+
console.log();
|
|
302
|
+
console.log(" Run `shardstitch` to launch the dashboard.");
|
|
303
|
+
console.log();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function cmdLaunch() {
|
|
307
|
+
const exe = findExe();
|
|
308
|
+
if (exe) {
|
|
309
|
+
console.log(" Starting ShardStitch...");
|
|
310
|
+
spawn(exe, [], { detached: true, stdio: "ignore" }).unref();
|
|
311
|
+
console.log(" Dashboard: http://127.0.0.1:8765");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log();
|
|
316
|
+
console.log(" ShardStitch — move your AI coding session between tools.");
|
|
317
|
+
console.log();
|
|
318
|
+
if (isActivated()) {
|
|
319
|
+
console.log(" App file not found. Re-run: shardstitch install <your-key>");
|
|
320
|
+
} else {
|
|
321
|
+
console.log(" Not installed. After purchase run:");
|
|
322
|
+
console.log(" shardstitch install <your-license-key>");
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(" Get ShardStitch at: https://shardstitch.com");
|
|
325
|
+
console.log(" Launch week: $19 — lifetime license, zero cloud.");
|
|
326
|
+
}
|
|
327
|
+
console.log();
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const args = process.argv.slice(2);
|
|
332
|
+
if (args[0] === "install") {
|
|
333
|
+
if (!args[1]) {
|
|
334
|
+
console.log();
|
|
335
|
+
console.log(" Usage: shardstitch install <your-license-key>");
|
|
336
|
+
console.log();
|
|
337
|
+
console.log(" Get your key at: https://shardstitch.com");
|
|
338
|
+
console.log();
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
cmdInstall(args[1]).catch((e) => {
|
|
342
|
+
console.log(" ✗ Unexpected error:", e.message);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
cmdLaunch();
|
|
347
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shardstitch",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "ShardStitch — capture your AI coding session (git diff, dependency graph, intent) and stitch it into the next tool.
|
|
3
|
+
"version": "0.0.11",
|
|
4
|
+
"description": "ShardStitch — capture your AI coding session (git diff, dependency graph, intent) and stitch it into the next tool. 23 AI tools supported. Local-first, no telemetry. https://shardstitch.com",
|
|
5
5
|
"homepage": "https://shardstitch.com",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/shardstitch/shardstitch/issues"
|