shardstitch 0.0.8 → 0.0.9
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/LICENSE.txt +1 -1
- package/cli.js +327 -327
- package/package.json +37 -37
package/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
ShardStitch — Proprietary License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
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/cli.js
CHANGED
|
@@ -1,327 +1,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 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 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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
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. 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.9",
|
|
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
|
+
}
|