@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.
Files changed (2) hide show
  1. package/dist/index.js +158 -18
  2. 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
- async function activateLicense(licenseKey, instanceName) {
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 fetch(`${BASE_URL}/activate`, {
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.json();
297
- if (!res.ok || !data.activated) {
298
- return { valid: false, error: data.error || `Activation failed (${res.status})` };
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
- return { valid: false, error: err instanceof Error ? err.message : "Network error" };
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 fetch(`${BASE_URL}/deactivate`, {
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.json();
319
- if (!res.ok || !data.deactivated) {
320
- return { deactivated: false, error: data.error || `Deactivation failed (${res.status})` };
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
- return { deactivated: false, error: err instanceof Error ? err.message : "Network error" };
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 path6 from "path";
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 = path6.basename(opts.cwd);
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 path7 from "path";
526
- import os2 from "os";
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 = os2.homedir();
686
+ const home = os3.homedir();
553
687
  if (shellEnv.endsWith("zsh")) {
554
- return { shell: "zsh", rcFile: path7.join(home, ".zshrc") };
688
+ return { shell: "zsh", rcFile: path8.join(home, ".zshrc") };
555
689
  }
556
690
  if (shellEnv.endsWith("bash")) {
557
- return { shell: "bash", rcFile: path7.join(home, ".bashrc") };
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Terminal CLI for KeepGoing. Resume side projects without the mental friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",