shardstitch 0.0.8 → 0.0.10

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.
Files changed (4) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +71 -65
  3. package/cli.js +334 -327
  4. package/package.json +37 -37
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  ShardStitch — Proprietary License
2
2
 
3
- Copyright (c) 2026 Roshan Dixit. All rights reserved.
3
+ Copyright (c) 2026 ShardStitch. All rights reserved.
4
4
 
5
5
  This placeholder package may be installed and executed. The ShardStitch
6
6
  product is licensed separately at https://shardstitch.com. Redistribution
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
- npx shardstitch
12
- ```
13
-
14
- ## What it does
15
-
16
- When Claude, Cursor, Codex, or Gemini locks you out mid-session, ShardStitch:
17
-
18
- 1. **Scans your project** — git diff, changed files, recent commits, your notes
19
- 2. **Builds a dependency graph** — impact radius, architecture communities — and warns the next AI what's dangerous to touch
20
- 3. **Generates a continuation prompt** formatted for the target tool
21
- 4. **Injects it** into the tool's pickup file (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules) for automatic discovery
22
-
23
- Under 30 seconds, end to end.
24
-
25
- ## Not just for switching tools
26
-
27
- 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.
28
-
29
- ## 22 supported AI tools
30
-
31
- 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, and Antigravity.
32
-
33
- **Coming soon:** Cody, Continue, Replit Agent, JetBrains AI, Tabnine.
34
-
35
- ## 7 surfaces
36
-
37
- - **Web dashboard** — scan, generate, copy, download
38
- - **VS Code / Cursor extension** — Alt+G hotkey
39
- - **MCP server** — Claude Code and Cursor call it directly
40
- - **CLI** — `npx shardstitch` or `shardstitch capture`
41
- - **Desktop app** — standalone Windows exe
42
- - **Autosave hooks** — auto-inject on session end
43
- - **Local LLM failover** — Ollama, vLLM, LM Studio (zero cloud)
44
-
45
- ## Key features
46
-
47
- - **Per-agent formatting** — 22 format adapters, one for each tool
48
- - **AI task router** — semantic matching picks the best tool for each task
49
- - **Persistent memory** — project decisions survive across sessions
50
- - **Team coordination** — shared `.shardstitch/` directory
51
- - **Tamper-evident audit trail** — hash-chained timeline logs
52
- - **Dependency graph analysis** — impact radius, god nodes, architecture communities
53
-
54
- ## 100% local
55
-
56
- Your code never leaves your machine. No cloud, no telemetry, no account required. All processing happens on your hardware.
57
-
58
- ## Links
59
-
60
- - Website: https://shardstitch.com
61
- - GitHub: https://github.com/shardstitch/shardstitch
62
- - X / Twitter: https://x.com/ShardStitch
63
- - VS Code / Cursor Extension: https://marketplace.visualstudio.com/items?itemName=shardstitch.shardstitch
64
- - Changelog: https://github.com/shardstitch/shardstitch/releases
65
- - Support: support@shardstitch.com
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,334 @@
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 { spawnSync, 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
- const POLAR_ORG_ID = "8e901881-4e3d-4356-98fb-2511337101b1";
15
- const INSTALL_DIR = join(os.homedir(), ".shardstitch", "app");
16
- const ACTIVATION_FILE = join(os.homedir(), ".shardstitch", "activation.json");
17
- const MACHINE_SALT = "shardstitch-activation/1";
18
-
19
- // --- Supply-chain hardening -------------------------------------------------
20
- // The binary SHA-256 is baked into this file at release time. No external
21
- // checksums.json needed Polar delivers the file, we verify the hash locally.
22
- // Update these after every build before publishing.
23
- const CHECKSUMS = {
24
- windows: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
25
- linux: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
26
- mac: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
27
- };
28
- const TRUSTED_DOWNLOAD_HOST_SUFFIXES = [
29
- "shardstitch.com",
30
- "polar.sh",
31
- "polarsource.com",
32
- ];
33
-
34
- function isTrustedHost(hostname) {
35
- const h = (hostname || "").toLowerCase();
36
- return TRUSTED_DOWNLOAD_HOST_SUFFIXES.some(
37
- (suffix) => h === suffix || h.endsWith("." + suffix)
38
- );
39
- }
40
-
41
- const PLATFORM = process.platform === "win32" ? "windows"
42
- : process.platform === "darwin" ? "mac"
43
- : "linux";
44
-
45
- const EXE_NAME = PLATFORM === "windows" ? "ShardStitch.exe" : "shardstitch";
46
-
47
- const CANDIDATE_DIRS = [
48
- process.env.SHARDSTITCH_HOME,
49
- INSTALL_DIR,
50
- join(os.homedir(), "ShardStitch"),
51
- PLATFORM === "windows" ? join(process.env.LOCALAPPDATA || "", "ShardStitch") : null,
52
- PLATFORM === "windows" ? "C:\\Program Files\\ShardStitch" : "/opt/shardstitch",
53
- ].filter(Boolean);
54
-
55
- function findExe() {
56
- for (const dir of CANDIDATE_DIRS) {
57
- const p = join(dir, EXE_NAME);
58
- if (existsSync(p)) return p;
59
- }
60
- return null;
61
- }
62
-
63
- function pythonCmd() {
64
- for (const cmd of ["python", "python3", "py"]) {
65
- const r = spawnSync(cmd, ["--version"], { stdio: "ignore", shell: false });
66
- if (r.status === 0) return cmd;
67
- }
68
- return null;
69
- }
70
-
71
- function machineFingerprint() {
72
- const raw = `${MACHINE_SALT}|${process.env.COMPUTERNAME || ""}|${process.env.USERNAME || os.userInfo().username}`;
73
- return createHash("sha256").update(raw).digest("hex").slice(0, 16);
74
- }
75
-
76
- function activationSignature(key, store) {
77
- const raw = `${MACHINE_SALT}|${key}|${store}|${machineFingerprint()}`;
78
- return createHash("sha256").update(raw).digest("hex");
79
- }
80
-
81
- function saveActivation(key) {
82
- mkdirSync(dirname(ACTIVATION_FILE), { recursive: true });
83
- const record = {
84
- key,
85
- store: "polar",
86
- activatedAt: Math.floor(Date.now() / 1000),
87
- signature: activationSignature(key, "polar"),
88
- };
89
- fs.writeFileSync(ACTIVATION_FILE, JSON.stringify(record, null, 2));
90
- }
91
-
92
- function isActivated() {
93
- try {
94
- const data = JSON.parse(fs.readFileSync(ACTIVATION_FILE, "utf8"));
95
- return data.signature === activationSignature(data.key || "", data.store || "");
96
- } catch {
97
- return false;
98
- }
99
- }
100
-
101
- function httpsPost(url, body) {
102
- return new Promise((resolve, reject) => {
103
- const u = new URL(url);
104
- const data = JSON.stringify(body);
105
- const req = https.request({
106
- hostname: u.hostname,
107
- path: u.pathname,
108
- method: "POST",
109
- headers: {
110
- "Content-Type": "application/json",
111
- "Content-Length": Buffer.byteLength(data),
112
- "User-Agent": "ShardStitch-Installer/1.0",
113
- },
114
- }, (res) => {
115
- let body = "";
116
- res.on("data", (c) => body += c);
117
- res.on("end", () => {
118
- if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${body}`));
119
- else resolve(JSON.parse(body));
120
- });
121
- });
122
- req.on("error", reject);
123
- req.write(data);
124
- req.end();
125
- });
126
- }
127
-
128
- function downloadFile(url, dest) {
129
- return new Promise((resolve, reject) => {
130
- mkdirSync(dirname(dest), { recursive: true });
131
- const tmp = dest + ".tmp";
132
- const file = createWriteStream(tmp);
133
-
134
- function follow(rawUrl) {
135
- let u;
136
- try {
137
- u = new URL(rawUrl);
138
- } catch {
139
- return reject(new Error(`Refusing to download: invalid URL`));
140
- }
141
- // Fail closed on protocol downgrade or untrusted host (blocks open-redirect
142
- // / MITM from pointing the download at an attacker-controlled binary).
143
- if (u.protocol !== "https:") {
144
- return reject(new Error(`Refusing non-HTTPS download (${u.protocol})`));
145
- }
146
- if (!isTrustedHost(u.hostname)) {
147
- return reject(new Error(`Refusing download from untrusted host: ${u.hostname}`));
148
- }
149
- https.get(rawUrl, { headers: { "User-Agent": "ShardStitch-Installer/1.0" } }, (res) => {
150
- if (res.statusCode === 301 || res.statusCode === 302) {
151
- return follow(res.headers.location);
152
- }
153
- const total = parseInt(res.headers["content-length"] || "0");
154
- let done = 0;
155
- res.on("data", (chunk) => {
156
- file.write(chunk);
157
- done += chunk.length;
158
- if (total) {
159
- const pct = Math.floor(done * 100 / total);
160
- process.stdout.write(`\r Downloading... ${pct}%`);
161
- }
162
- });
163
- res.on("end", () => {
164
- file.end();
165
- process.stdout.write("\n");
166
- renameSync(tmp, dest);
167
- resolve();
168
- });
169
- res.on("error", reject);
170
- }).on("error", reject);
171
- }
172
-
173
- follow(url);
174
- });
175
- }
176
-
177
- function sha256File(path) {
178
- return new Promise((resolve, reject) => {
179
- const hash = createHash("sha256");
180
- const stream = fs.createReadStream(path);
181
- stream.on("data", (chunk) => hash.update(chunk));
182
- stream.on("end", () => resolve(hash.digest("hex")));
183
- stream.on("error", reject);
184
- });
185
- }
186
-
187
-
188
- async function cmdInstall(key) {
189
- key = key.trim();
190
- console.log();
191
- console.log(" ShardStitch — verifying license key...");
192
-
193
- let data;
194
- try {
195
- data = await httpsPost("https://api.polar.sh/v1/customer-portal/license-keys/validate", {
196
- key,
197
- organization_id: POLAR_ORG_ID,
198
- });
199
- } catch (e) {
200
- console.log(` ${e.message.includes("404") ? "Key not found. Check for typos or email support@shardstitch.com." : "Network error: " + e.message}`);
201
- process.exit(1);
202
- }
203
-
204
- if (!["granted", "active"].includes(data.status) && !data.id) {
205
- console.log(" ✗ Invalid license key.");
206
- process.exit(1);
207
- }
208
-
209
- console.log(" ✓ License valid.");
210
- console.log();
211
-
212
- // Find download URL from benefit grants
213
- let downloadUrl = null;
214
- for (const grant of data.benefit_grants || []) {
215
- if (grant.benefit?.type === "downloadables") {
216
- const files = grant.properties?.files || [];
217
- for (const f of files) {
218
- const name = (f.name || "").toLowerCase();
219
- const url = f.url || f.download_url;
220
- if (!url) continue;
221
- if (PLATFORM === "windows" && name.includes(".exe")) { downloadUrl = url; break; }
222
- if (PLATFORM === "linux" && name.includes("linux")) { downloadUrl = url; break; }
223
- if (PLATFORM === "mac" && (name.includes("mac") || name.includes("darwin"))) { downloadUrl = url; break; }
224
- }
225
- if (!downloadUrl && files[0]) downloadUrl = files[0].url || files[0].download_url;
226
- }
227
- if (downloadUrl) break;
228
- }
229
-
230
- if (!downloadUrl) {
231
- console.log(" ✗ No downloadable files found on this license.");
232
- console.log(" Email support@shardstitch.com for help.");
233
- process.exit(1);
234
- }
235
-
236
- const exePath = join(INSTALL_DIR, EXE_NAME);
237
- console.log(` Downloading ShardStitch for ${PLATFORM}...`);
238
-
239
- try {
240
- await downloadFile(downloadUrl, exePath);
241
- } catch (e) {
242
- console.log(` ✗ Download failed: ${e.message}`);
243
- process.exit(1);
244
- }
245
-
246
- // Integrity check — verify the downloaded binary's SHA-256 against the manifest
247
- // on our pinned domain BEFORE making it executable or running it. Fails closed.
248
- const allowUnverified = process.argv.includes("--allow-unverified");
249
- try {
250
- const expected = (CHECKSUMS[PLATFORM] || "").toLowerCase();
251
- const actual = (await sha256File(exePath)).toLowerCase();
252
- if (!expected) {
253
- throw new Error(`no published checksum for platform "${PLATFORM}"`);
254
- }
255
- if (actual !== expected) {
256
- try { fs.unlinkSync(exePath); } catch {}
257
- console.log(" ✗ Integrity check FAILED the downloaded file does not match");
258
- console.log(" the published checksum and was deleted. Do NOT run it.");
259
- console.log(` expected ${expected}`);
260
- console.log(` actual ${actual}`);
261
- console.log(" Report this to support@shardstitch.com.");
262
- process.exit(1);
263
- }
264
- console.log(" Integrity verified (SHA-256).");
265
- } catch (e) {
266
- if (!allowUnverified) {
267
- try { fs.unlinkSync(exePath); } catch {}
268
- console.log(` ✗ Could not verify download integrity: ${e.message}`);
269
- console.log(" Aborting for safety. Re-run with --allow-unverified to override");
270
- console.log(" (not recommended), or contact support@shardstitch.com.");
271
- process.exit(1);
272
- }
273
- console.log(` ! Skipping integrity check (--allow-unverified): ${e.message}`);
274
- }
275
-
276
- if (PLATFORM !== "windows") chmodSync(exePath, 0o755);
277
-
278
- saveActivation(key);
279
-
280
- console.log(` Installed to ${exePath}`);
281
- console.log();
282
- console.log(" Run `shardstitch` to launch the dashboard.");
283
- console.log();
284
- }
285
-
286
- function cmdLaunch() {
287
- const exe = findExe();
288
- if (exe) {
289
- console.log(" Starting ShardStitch...");
290
- spawn(exe, [], { detached: true, stdio: "ignore" }).unref();
291
- console.log(" Dashboard: http://127.0.0.1:8765");
292
- process.exit(0);
293
- }
294
-
295
- console.log();
296
- console.log(" ShardStitch — move your AI coding session between tools.");
297
- console.log();
298
- if (isActivated()) {
299
- console.log(" App file not found. Re-run: shardstitch install <your-key>");
300
- } else {
301
- console.log(" Not installed. After purchase run:");
302
- console.log(" shardstitch install <your-license-key>");
303
- console.log();
304
- console.log(" Get ShardStitch at: https://shardstitch.com");
305
- console.log(" Launch week: $19 — lifetime license, zero cloud.");
306
- }
307
- console.log();
308
- process.exit(1);
309
- }
310
-
311
- const args = process.argv.slice(2);
312
- if (args[0] === "install") {
313
- if (!args[1]) {
314
- console.log();
315
- console.log(" Usage: shardstitch install <your-license-key>");
316
- console.log();
317
- console.log(" Get your key at: https://shardstitch.com");
318
- console.log();
319
- process.exit(1);
320
- }
321
- cmdInstall(args[1]).catch((e) => {
322
- console.log(" Unexpected error:", e.message);
323
- process.exit(1);
324
- });
325
- } else {
326
- cmdLaunch();
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 { spawnSync, 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
+ const POLAR_ORG_ID = "8e901881-4e3d-4356-98fb-2511337101b1";
15
+ const INSTALL_DIR = join(os.homedir(), ".shardstitch", "app");
16
+ const ACTIVATION_FILE = join(os.homedir(), ".shardstitch", "activation.json");
17
+ const MACHINE_SALT = "shardstitch-activation/1";
18
+
19
+ // --- Supply-chain hardening -------------------------------------------------
20
+ // The binary is downloaded over HTTPS and its SHA-256 is verified against the
21
+ // hashes EMBEDDED in this published package BEFORE it is ever made executable
22
+ // or run. Embedding (rather than fetching a manifest from the same host that
23
+ // serves the binary) means a compromised download origin cannot swap the
24
+ // binary without also defeating npm's own package integrity — a strictly
25
+ // stronger trust anchor. Downloads (and any redirects) are additionally
26
+ // restricted to an allowlist of trusted hosts. See SECURITY.md.
27
+ //
28
+ // Keep in lockstep with packages/pypi/shardstitch/__init__.py _CHECKSUMS.
29
+ // Regenerate with: python tools/gen_checksums.py. An empty string means "no
30
+ // binary published for this platform yet" — cmdInstall fails CLOSED on it.
31
+ const EMBEDDED_CHECKSUMS = {
32
+ windows: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
33
+ linux: "",
34
+ mac: "",
35
+ };
36
+ const TRUSTED_DOWNLOAD_HOST_SUFFIXES = [
37
+ "shardstitch.com",
38
+ "polar.sh",
39
+ "polarsource.com",
40
+ ];
41
+
42
+ function isTrustedHost(hostname) {
43
+ const h = (hostname || "").toLowerCase();
44
+ return TRUSTED_DOWNLOAD_HOST_SUFFIXES.some(
45
+ (suffix) => h === suffix || h.endsWith("." + suffix)
46
+ );
47
+ }
48
+
49
+ const PLATFORM = process.platform === "win32" ? "windows"
50
+ : process.platform === "darwin" ? "mac"
51
+ : "linux";
52
+
53
+ const EXE_NAME = PLATFORM === "windows" ? "ShardStitch.exe" : "shardstitch";
54
+
55
+ const CANDIDATE_DIRS = [
56
+ process.env.SHARDSTITCH_HOME,
57
+ INSTALL_DIR,
58
+ join(os.homedir(), "ShardStitch"),
59
+ PLATFORM === "windows" ? join(process.env.LOCALAPPDATA || "", "ShardStitch") : null,
60
+ PLATFORM === "windows" ? "C:\\Program Files\\ShardStitch" : "/opt/shardstitch",
61
+ ].filter(Boolean);
62
+
63
+ function findExe() {
64
+ for (const dir of CANDIDATE_DIRS) {
65
+ const p = join(dir, EXE_NAME);
66
+ if (existsSync(p)) return p;
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function pythonCmd() {
72
+ for (const cmd of ["python", "python3", "py"]) {
73
+ const r = spawnSync(cmd, ["--version"], { stdio: "ignore", shell: false });
74
+ if (r.status === 0) return cmd;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ function machineFingerprint() {
80
+ const raw = `${MACHINE_SALT}|${process.env.COMPUTERNAME || ""}|${process.env.USERNAME || os.userInfo().username}`;
81
+ return createHash("sha256").update(raw).digest("hex").slice(0, 16);
82
+ }
83
+
84
+ function activationSignature(key, store) {
85
+ const raw = `${MACHINE_SALT}|${key}|${store}|${machineFingerprint()}`;
86
+ return createHash("sha256").update(raw).digest("hex");
87
+ }
88
+
89
+ function saveActivation(key) {
90
+ mkdirSync(dirname(ACTIVATION_FILE), { recursive: true });
91
+ const record = {
92
+ key,
93
+ store: "polar",
94
+ activatedAt: Math.floor(Date.now() / 1000),
95
+ signature: activationSignature(key, "polar"),
96
+ };
97
+ fs.writeFileSync(ACTIVATION_FILE, JSON.stringify(record, null, 2));
98
+ }
99
+
100
+ function isActivated() {
101
+ try {
102
+ const data = JSON.parse(fs.readFileSync(ACTIVATION_FILE, "utf8"));
103
+ return data.signature === activationSignature(data.key || "", data.store || "");
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ function httpsPost(url, body) {
110
+ return new Promise((resolve, reject) => {
111
+ const u = new URL(url);
112
+ const data = JSON.stringify(body);
113
+ const req = https.request({
114
+ hostname: u.hostname,
115
+ path: u.pathname,
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "Content-Length": Buffer.byteLength(data),
120
+ "User-Agent": "ShardStitch-Installer/1.0",
121
+ },
122
+ }, (res) => {
123
+ let body = "";
124
+ res.on("data", (c) => body += c);
125
+ res.on("end", () => {
126
+ if (res.statusCode >= 400) reject(new Error(`HTTP ${res.statusCode}: ${body}`));
127
+ else resolve(JSON.parse(body));
128
+ });
129
+ });
130
+ req.on("error", reject);
131
+ req.write(data);
132
+ req.end();
133
+ });
134
+ }
135
+
136
+ function downloadFile(url, dest) {
137
+ return new Promise((resolve, reject) => {
138
+ mkdirSync(dirname(dest), { recursive: true });
139
+ const tmp = dest + ".tmp";
140
+ const file = createWriteStream(tmp);
141
+
142
+ function follow(rawUrl) {
143
+ let u;
144
+ try {
145
+ u = new URL(rawUrl);
146
+ } catch {
147
+ return reject(new Error(`Refusing to download: invalid URL`));
148
+ }
149
+ // Fail closed on protocol downgrade or untrusted host (blocks open-redirect
150
+ // / MITM from pointing the download at an attacker-controlled binary).
151
+ if (u.protocol !== "https:") {
152
+ return reject(new Error(`Refusing non-HTTPS download (${u.protocol})`));
153
+ }
154
+ if (!isTrustedHost(u.hostname)) {
155
+ return reject(new Error(`Refusing download from untrusted host: ${u.hostname}`));
156
+ }
157
+ https.get(rawUrl, { headers: { "User-Agent": "ShardStitch-Installer/1.0" } }, (res) => {
158
+ if (res.statusCode === 301 || res.statusCode === 302) {
159
+ return follow(res.headers.location);
160
+ }
161
+ const total = parseInt(res.headers["content-length"] || "0");
162
+ let done = 0;
163
+ res.on("data", (chunk) => {
164
+ file.write(chunk);
165
+ done += chunk.length;
166
+ if (total) {
167
+ const pct = Math.floor(done * 100 / total);
168
+ process.stdout.write(`\r Downloading... ${pct}%`);
169
+ }
170
+ });
171
+ res.on("end", () => {
172
+ file.end();
173
+ process.stdout.write("\n");
174
+ renameSync(tmp, dest);
175
+ resolve();
176
+ });
177
+ res.on("error", reject);
178
+ }).on("error", reject);
179
+ }
180
+
181
+ follow(url);
182
+ });
183
+ }
184
+
185
+ function sha256File(path) {
186
+ return new Promise((resolve, reject) => {
187
+ const hash = createHash("sha256");
188
+ const stream = fs.createReadStream(path);
189
+ stream.on("data", (chunk) => hash.update(chunk));
190
+ stream.on("end", () => resolve(hash.digest("hex")));
191
+ stream.on("error", reject);
192
+ });
193
+ }
194
+
195
+ async function cmdInstall(key) {
196
+ key = key.trim();
197
+ console.log();
198
+ console.log(" ShardStitch — verifying license key...");
199
+
200
+ let data;
201
+ try {
202
+ data = await httpsPost("https://api.polar.sh/v1/customer-portal/license-keys/validate", {
203
+ key,
204
+ organization_id: POLAR_ORG_ID,
205
+ });
206
+ } catch (e) {
207
+ console.log(` ✗ ${e.message.includes("404") ? "Key not found. Check for typos or email support@shardstitch.com." : "Network error: " + e.message}`);
208
+ process.exit(1);
209
+ }
210
+
211
+ if (!["granted", "active"].includes(data.status) && !data.id) {
212
+ console.log(" Invalid license key.");
213
+ process.exit(1);
214
+ }
215
+
216
+ console.log(" ✓ License valid.");
217
+ console.log();
218
+
219
+ // Find download URL from benefit grants
220
+ let downloadUrl = null;
221
+ for (const grant of data.benefit_grants || []) {
222
+ if (grant.benefit?.type === "downloadables") {
223
+ const files = grant.properties?.files || [];
224
+ for (const f of files) {
225
+ const name = (f.name || "").toLowerCase();
226
+ const url = f.url || f.download_url;
227
+ if (!url) continue;
228
+ if (PLATFORM === "windows" && name.includes(".exe")) { downloadUrl = url; break; }
229
+ if (PLATFORM === "linux" && name.includes("linux")) { downloadUrl = url; break; }
230
+ if (PLATFORM === "mac" && (name.includes("mac") || name.includes("darwin"))) { downloadUrl = url; break; }
231
+ }
232
+ if (!downloadUrl && files[0]) downloadUrl = files[0].url || files[0].download_url;
233
+ }
234
+ if (downloadUrl) break;
235
+ }
236
+
237
+ if (!downloadUrl) {
238
+ console.log(" ✗ No downloadable files found on this license.");
239
+ console.log(" Email support@shardstitch.com for help.");
240
+ process.exit(1);
241
+ }
242
+
243
+ const exePath = join(INSTALL_DIR, EXE_NAME);
244
+ console.log(` Downloading ShardStitch for ${PLATFORM}...`);
245
+
246
+ try {
247
+ await downloadFile(downloadUrl, exePath);
248
+ } catch (e) {
249
+ console.log(` Download failed: ${e.message}`);
250
+ process.exit(1);
251
+ }
252
+
253
+ // Integrity check verify the downloaded binary's SHA-256 against the manifest
254
+ // on our pinned domain BEFORE making it executable or running it. Fails closed.
255
+ const allowUnverified = process.argv.includes("--allow-unverified");
256
+ try {
257
+ const expected = (EMBEDDED_CHECKSUMS[PLATFORM] || "").toLowerCase();
258
+ const actual = (await sha256File(exePath)).toLowerCase();
259
+ if (!expected) {
260
+ throw new Error(`no published checksum for platform "${PLATFORM}"`);
261
+ }
262
+ if (actual !== expected) {
263
+ try { fs.unlinkSync(exePath); } catch {}
264
+ console.log(" Integrity check FAILED — the downloaded file does not match");
265
+ console.log(" the published checksum and was deleted. Do NOT run it.");
266
+ console.log(` expected ${expected}`);
267
+ console.log(` actual ${actual}`);
268
+ console.log(" Report this to support@shardstitch.com.");
269
+ process.exit(1);
270
+ }
271
+ console.log(" ✓ Integrity verified (SHA-256).");
272
+ } catch (e) {
273
+ if (!allowUnverified) {
274
+ try { fs.unlinkSync(exePath); } catch {}
275
+ console.log(` ✗ Could not verify download integrity: ${e.message}`);
276
+ console.log(" Aborting for safety. Re-run with --allow-unverified to override");
277
+ console.log(" (not recommended), or contact support@shardstitch.com.");
278
+ process.exit(1);
279
+ }
280
+ console.log(` ! Skipping integrity check (--allow-unverified): ${e.message}`);
281
+ }
282
+
283
+ if (PLATFORM !== "windows") chmodSync(exePath, 0o755);
284
+
285
+ saveActivation(key);
286
+
287
+ console.log(` ✓ Installed to ${exePath}`);
288
+ console.log();
289
+ console.log(" Run `shardstitch` to launch the dashboard.");
290
+ console.log();
291
+ }
292
+
293
+ function cmdLaunch() {
294
+ const exe = findExe();
295
+ if (exe) {
296
+ console.log(" Starting ShardStitch...");
297
+ spawn(exe, [], { detached: true, stdio: "ignore" }).unref();
298
+ console.log(" Dashboard: http://127.0.0.1:8765");
299
+ process.exit(0);
300
+ }
301
+
302
+ console.log();
303
+ console.log(" ShardStitch — move your AI coding session between tools.");
304
+ console.log();
305
+ if (isActivated()) {
306
+ console.log(" App file not found. Re-run: shardstitch install <your-key>");
307
+ } else {
308
+ console.log(" Not installed. After purchase run:");
309
+ console.log(" shardstitch install <your-license-key>");
310
+ console.log();
311
+ console.log(" Get ShardStitch at: https://shardstitch.com");
312
+ console.log(" Launch week: $19 — lifetime license, zero cloud.");
313
+ }
314
+ console.log();
315
+ process.exit(1);
316
+ }
317
+
318
+ const args = process.argv.slice(2);
319
+ if (args[0] === "install") {
320
+ if (!args[1]) {
321
+ console.log();
322
+ console.log(" Usage: shardstitch install <your-license-key>");
323
+ console.log();
324
+ console.log(" Get your key at: https://shardstitch.com");
325
+ console.log();
326
+ process.exit(1);
327
+ }
328
+ cmdInstall(args[1]).catch((e) => {
329
+ console.log(" ✗ Unexpected error:", e.message);
330
+ process.exit(1);
331
+ });
332
+ } else {
333
+ cmdLaunch();
334
+ }
package/package.json CHANGED
@@ -1,37 +1,37 @@
1
- {
2
- "name": "shardstitch",
3
- "version": "0.0.8",
4
- "description": "ShardStitch — capture your AI coding session (git diff, dependency graph, intent) and stitch it into the next tool. 22 AI tools supported. 100% local, zero telemetry. https://shardstitch.com",
5
- "homepage": "https://shardstitch.com",
6
- "bugs": {
7
- "url": "https://github.com/shardstitch/shardstitch/issues"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/shardstitch/shardstitch.git"
12
- },
13
- "funding": {
14
- "url": "https://shardstitch.com"
15
- },
16
- "keywords": [
17
- "ai",
18
- "claude",
19
- "cursor",
20
- "codex",
21
- "gemini",
22
- "context",
23
- "handoff",
24
- "rate-limit",
25
- "developer-tools"
26
- ],
27
- "author": "ShardStitch <support@shardstitch.com>",
28
- "license": "SEE LICENSE IN LICENSE.txt",
29
- "bin": {
30
- "shardstitch": "cli.js"
31
- },
32
- "files": [
33
- "cli.js",
34
- "LICENSE.txt",
35
- "README.md"
36
- ]
37
- }
1
+ {
2
+ "name": "shardstitch",
3
+ "version": "0.0.10",
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
+ "homepage": "https://shardstitch.com",
6
+ "bugs": {
7
+ "url": "https://github.com/shardstitch/shardstitch/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/shardstitch/shardstitch.git"
12
+ },
13
+ "funding": {
14
+ "url": "https://shardstitch.com"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "claude",
19
+ "cursor",
20
+ "codex",
21
+ "gemini",
22
+ "context",
23
+ "handoff",
24
+ "rate-limit",
25
+ "developer-tools"
26
+ ],
27
+ "author": "ShardStitch <support@shardstitch.com>",
28
+ "license": "SEE LICENSE IN LICENSE.txt",
29
+ "bin": {
30
+ "shardstitch": "cli.js"
31
+ },
32
+ "files": [
33
+ "cli.js",
34
+ "LICENSE.txt",
35
+ "README.md"
36
+ ]
37
+ }