@keepgoingdev/cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +158 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -286,16 +286,54 @@ var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
|
|
|
286
286
|
|
|
287
287
|
// ../../packages/shared/src/licenseClient.ts
|
|
288
288
|
var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
|
|
289
|
-
|
|
289
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
290
|
+
var EXPECTED_STORE_ID = 301555;
|
|
291
|
+
var EXPECTED_PRODUCT_ID = 864311;
|
|
292
|
+
function fetchWithTimeout(url, init) {
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
295
|
+
return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
|
|
296
|
+
}
|
|
297
|
+
function validateProductIdentity(meta) {
|
|
298
|
+
if (!meta) return "License response missing product metadata.";
|
|
299
|
+
if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {
|
|
300
|
+
return "This license key does not belong to KeepGoing.";
|
|
301
|
+
}
|
|
302
|
+
return void 0;
|
|
303
|
+
}
|
|
304
|
+
async function safeJson(res) {
|
|
305
|
+
try {
|
|
306
|
+
const text = await res.text();
|
|
307
|
+
return JSON.parse(text);
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function activateLicense(licenseKey, instanceName, options) {
|
|
290
313
|
try {
|
|
291
|
-
const res = await
|
|
314
|
+
const res = await fetchWithTimeout(`${BASE_URL}/activate`, {
|
|
292
315
|
method: "POST",
|
|
293
316
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
294
317
|
body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName })
|
|
295
318
|
});
|
|
296
|
-
const data = await res
|
|
297
|
-
if (!res.ok || !data
|
|
298
|
-
return { valid: false, error: data
|
|
319
|
+
const data = await safeJson(res);
|
|
320
|
+
if (!res.ok || !data?.activated) {
|
|
321
|
+
return { valid: false, error: data?.error || `Activation failed (${res.status})` };
|
|
322
|
+
}
|
|
323
|
+
if (!options?.allowTestMode && data.license_key?.test_mode) {
|
|
324
|
+
if (data.license_key?.key && data.instance?.id) {
|
|
325
|
+
await deactivateLicense(data.license_key.key, data.instance.id);
|
|
326
|
+
}
|
|
327
|
+
return { valid: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
|
|
328
|
+
}
|
|
329
|
+
if (!options?.allowTestMode) {
|
|
330
|
+
const productError = validateProductIdentity(data.meta);
|
|
331
|
+
if (productError) {
|
|
332
|
+
if (data.license_key?.key && data.instance?.id) {
|
|
333
|
+
await deactivateLicense(data.license_key.key, data.instance.id);
|
|
334
|
+
}
|
|
335
|
+
return { valid: false, error: productError };
|
|
336
|
+
}
|
|
299
337
|
}
|
|
300
338
|
return {
|
|
301
339
|
valid: true,
|
|
@@ -305,23 +343,25 @@ async function activateLicense(licenseKey, instanceName) {
|
|
|
305
343
|
productName: data.meta?.product_name
|
|
306
344
|
};
|
|
307
345
|
} catch (err) {
|
|
308
|
-
|
|
346
|
+
const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
|
|
347
|
+
return { valid: false, error: message };
|
|
309
348
|
}
|
|
310
349
|
}
|
|
311
350
|
async function deactivateLicense(licenseKey, instanceId) {
|
|
312
351
|
try {
|
|
313
|
-
const res = await
|
|
352
|
+
const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
|
|
314
353
|
method: "POST",
|
|
315
354
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
316
355
|
body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
|
|
317
356
|
});
|
|
318
|
-
const data = await res
|
|
319
|
-
if (!res.ok || !data
|
|
320
|
-
return { deactivated: false, error: data
|
|
357
|
+
const data = await safeJson(res);
|
|
358
|
+
if (!res.ok || !data?.deactivated) {
|
|
359
|
+
return { deactivated: false, error: data?.error || `Deactivation failed (${res.status})` };
|
|
321
360
|
}
|
|
322
361
|
return { deactivated: true };
|
|
323
362
|
} catch (err) {
|
|
324
|
-
|
|
363
|
+
const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
|
|
364
|
+
return { deactivated: false, error: message };
|
|
325
365
|
}
|
|
326
366
|
}
|
|
327
367
|
|
|
@@ -438,7 +478,96 @@ function renderNoData() {
|
|
|
438
478
|
);
|
|
439
479
|
}
|
|
440
480
|
|
|
481
|
+
// src/updateCheck.ts
|
|
482
|
+
import { spawn } from "child_process";
|
|
483
|
+
import { readFileSync, existsSync } from "fs";
|
|
484
|
+
import path6 from "path";
|
|
485
|
+
import os2 from "os";
|
|
486
|
+
var CLI_VERSION = "0.2.1";
|
|
487
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@keepgoingdev/cli/latest";
|
|
488
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
489
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
490
|
+
var CACHE_DIR = path6.join(os2.homedir(), ".keepgoing");
|
|
491
|
+
var CACHE_PATH = path6.join(CACHE_DIR, "update-check.json");
|
|
492
|
+
function isNewerVersion(current, latest) {
|
|
493
|
+
const cur = current.split(".").map(Number);
|
|
494
|
+
const lat = latest.split(".").map(Number);
|
|
495
|
+
for (let i = 0; i < 3; i++) {
|
|
496
|
+
if ((lat[i] ?? 0) > (cur[i] ?? 0)) return true;
|
|
497
|
+
if ((lat[i] ?? 0) < (cur[i] ?? 0)) return false;
|
|
498
|
+
}
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
function getCachedUpdateInfo() {
|
|
502
|
+
try {
|
|
503
|
+
if (!existsSync(CACHE_PATH)) return null;
|
|
504
|
+
const raw = readFileSync(CACHE_PATH, "utf-8");
|
|
505
|
+
const cache = JSON.parse(raw);
|
|
506
|
+
if (!cache.latest || !cache.checkedAt) return null;
|
|
507
|
+
const age = Date.now() - new Date(cache.checkedAt).getTime();
|
|
508
|
+
if (age > CHECK_INTERVAL_MS) return null;
|
|
509
|
+
return {
|
|
510
|
+
current: CLI_VERSION,
|
|
511
|
+
latest: cache.latest,
|
|
512
|
+
updateAvailable: isNewerVersion(CLI_VERSION, cache.latest)
|
|
513
|
+
};
|
|
514
|
+
} catch {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function spawnBackgroundCheck() {
|
|
519
|
+
try {
|
|
520
|
+
if (existsSync(CACHE_PATH)) {
|
|
521
|
+
const raw = readFileSync(CACHE_PATH, "utf-8");
|
|
522
|
+
const cache = JSON.parse(raw);
|
|
523
|
+
const age = Date.now() - new Date(cache.checkedAt).getTime();
|
|
524
|
+
if (age < CHECK_INTERVAL_MS) return;
|
|
525
|
+
}
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
const script = `
|
|
529
|
+
const https = require('https');
|
|
530
|
+
const fs = require('fs');
|
|
531
|
+
const path = require('path');
|
|
532
|
+
const os = require('os');
|
|
533
|
+
|
|
534
|
+
const url = ${JSON.stringify(NPM_REGISTRY_URL)};
|
|
535
|
+
const cacheDir = path.join(os.homedir(), '.keepgoing');
|
|
536
|
+
const cachePath = path.join(cacheDir, 'update-check.json');
|
|
537
|
+
const currentVersion = ${JSON.stringify(CLI_VERSION)};
|
|
538
|
+
|
|
539
|
+
const req = https.get(url, { timeout: ${FETCH_TIMEOUT_MS} }, (res) => {
|
|
540
|
+
let data = '';
|
|
541
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
542
|
+
res.on('end', () => {
|
|
543
|
+
try {
|
|
544
|
+
const latest = JSON.parse(data).version;
|
|
545
|
+
if (!latest) process.exit(0);
|
|
546
|
+
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
547
|
+
fs.writeFileSync(cachePath, JSON.stringify({
|
|
548
|
+
latest,
|
|
549
|
+
current: currentVersion,
|
|
550
|
+
checkedAt: new Date().toISOString(),
|
|
551
|
+
}));
|
|
552
|
+
} catch {}
|
|
553
|
+
process.exit(0);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
req.on('error', () => process.exit(0));
|
|
557
|
+
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
558
|
+
`;
|
|
559
|
+
const child = spawn(process.execPath, ["-e", script], {
|
|
560
|
+
detached: true,
|
|
561
|
+
stdio: "ignore",
|
|
562
|
+
env: { ...process.env }
|
|
563
|
+
});
|
|
564
|
+
child.unref();
|
|
565
|
+
}
|
|
566
|
+
|
|
441
567
|
// src/commands/status.ts
|
|
568
|
+
var RESET2 = "\x1B[0m";
|
|
569
|
+
var BOLD2 = "\x1B[1m";
|
|
570
|
+
var DIM2 = "\x1B[2m";
|
|
442
571
|
async function statusCommand(opts) {
|
|
443
572
|
const reader = new KeepGoingReader(opts.cwd);
|
|
444
573
|
if (!reader.exists()) {
|
|
@@ -466,11 +595,16 @@ async function statusCommand(opts) {
|
|
|
466
595
|
(Date.now() - new Date(lastSession.timestamp).getTime()) / (1e3 * 60 * 60 * 24)
|
|
467
596
|
);
|
|
468
597
|
renderCheckpoint(lastSession, daysSince);
|
|
598
|
+
const cached = getCachedUpdateInfo();
|
|
599
|
+
if (cached?.updateAvailable) {
|
|
600
|
+
console.log(`${DIM2}Update available: ${cached.current} \u2192 ${cached.latest}. Run: ${RESET2}${BOLD2}npm install -g @keepgoingdev/cli@latest${RESET2}`);
|
|
601
|
+
}
|
|
602
|
+
spawnBackgroundCheck();
|
|
469
603
|
}
|
|
470
604
|
|
|
471
605
|
// src/commands/save.ts
|
|
472
606
|
import readline from "readline";
|
|
473
|
-
import
|
|
607
|
+
import path7 from "path";
|
|
474
608
|
function prompt(rl, question) {
|
|
475
609
|
return new Promise((resolve) => {
|
|
476
610
|
rl.question(question, (answer) => {
|
|
@@ -514,7 +648,7 @@ async function saveCommand(opts) {
|
|
|
514
648
|
workspaceRoot: opts.cwd,
|
|
515
649
|
source: "manual"
|
|
516
650
|
});
|
|
517
|
-
const projectName =
|
|
651
|
+
const projectName = path7.basename(opts.cwd);
|
|
518
652
|
const writer = new KeepGoingWriter(opts.cwd);
|
|
519
653
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
520
654
|
console.log("Checkpoint saved.");
|
|
@@ -522,8 +656,8 @@ async function saveCommand(opts) {
|
|
|
522
656
|
|
|
523
657
|
// src/commands/hook.ts
|
|
524
658
|
import fs5 from "fs";
|
|
525
|
-
import
|
|
526
|
-
import
|
|
659
|
+
import path8 from "path";
|
|
660
|
+
import os3 from "os";
|
|
527
661
|
var HOOK_MARKER_START = "# keepgoing-hook-start";
|
|
528
662
|
var HOOK_MARKER_END = "# keepgoing-hook-end";
|
|
529
663
|
var ZSH_HOOK = `${HOOK_MARKER_START}
|
|
@@ -549,12 +683,12 @@ fi
|
|
|
549
683
|
${HOOK_MARKER_END}`;
|
|
550
684
|
function detectShellRcFile() {
|
|
551
685
|
const shellEnv = process.env["SHELL"] ?? "";
|
|
552
|
-
const home =
|
|
686
|
+
const home = os3.homedir();
|
|
553
687
|
if (shellEnv.endsWith("zsh")) {
|
|
554
|
-
return { shell: "zsh", rcFile:
|
|
688
|
+
return { shell: "zsh", rcFile: path8.join(home, ".zshrc") };
|
|
555
689
|
}
|
|
556
690
|
if (shellEnv.endsWith("bash")) {
|
|
557
|
-
return { shell: "bash", rcFile:
|
|
691
|
+
return { shell: "bash", rcFile: path8.join(home, ".bashrc") };
|
|
558
692
|
}
|
|
559
693
|
return void 0;
|
|
560
694
|
}
|
|
@@ -683,6 +817,7 @@ Options:
|
|
|
683
817
|
--cwd <path> Override the working directory (default: current directory)
|
|
684
818
|
--json Output raw JSON (status only)
|
|
685
819
|
--quiet Output a single summary line (status only)
|
|
820
|
+
-v, --version Show the CLI version
|
|
686
821
|
-h, --help Show this help text
|
|
687
822
|
|
|
688
823
|
Hook subcommands:
|
|
@@ -704,6 +839,8 @@ function parseArgs(argv) {
|
|
|
704
839
|
json = true;
|
|
705
840
|
} else if (arg === "--quiet") {
|
|
706
841
|
quiet = true;
|
|
842
|
+
} else if (arg === "-v" || arg === "--version") {
|
|
843
|
+
command = "version";
|
|
707
844
|
} else if (arg === "-h" || arg === "--help") {
|
|
708
845
|
command = "help";
|
|
709
846
|
} else if (!command) {
|
|
@@ -736,6 +873,9 @@ async function main() {
|
|
|
736
873
|
process.exit(1);
|
|
737
874
|
}
|
|
738
875
|
break;
|
|
876
|
+
case "version":
|
|
877
|
+
console.log(`keepgoing v${"0.2.1"}`);
|
|
878
|
+
break;
|
|
739
879
|
case "activate":
|
|
740
880
|
await activateCommand({ licenseKey: subcommand });
|
|
741
881
|
break;
|