@react-grab/cli 0.1.40 → 0.1.41

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/cli.cjs CHANGED
@@ -22,12 +22,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
  //#endregion
24
24
  let commander = require("commander");
25
+ let node_path = require("node:path");
26
+ node_path = __toESM(node_path, 1);
25
27
  let picocolors = require("picocolors");
26
28
  picocolors = __toESM(picocolors, 1);
27
29
  let node_fs = require("node:fs");
28
30
  node_fs = __toESM(node_fs, 1);
29
- let node_path = require("node:path");
30
- node_path = __toESM(node_path, 1);
31
31
  let package_manager_detector_detect = require("package-manager-detector/detect");
32
32
  let ignore = require("ignore");
33
33
  ignore = __toESM(ignore, 1);
@@ -450,8 +450,11 @@ const spinner = (text) => (0, ora.default)({ text });
450
450
  const SKILL_NAME = "react-grab";
451
451
  const SKILL_SOURCE = (0, node_url.fileURLToPath)(new URL("../skills/react-grab", require("url").pathToFileURL(__filename).href));
452
452
  const agentLabel = (agent) => (0, agent_install_skill.getSkillAgentConfig)(agent).displayName;
453
- const installedSkillDir = (agent) => (0, node_path.join)((0, agent_install_skill.isUniversalSkillAgent)(agent) ? (0, agent_install_skill.getCanonicalSkillsDir)(true) : (0, agent_install_skill.getSkillAgentDir)(agent, { global: true }), SKILL_NAME);
454
- const promptSkillInstall = async ({ yes = false } = {}) => {
453
+ const installedSkillDir = (agent, global, cwd) => (0, node_path.join)((0, agent_install_skill.isUniversalSkillAgent)(agent) ? (0, agent_install_skill.getCanonicalSkillsDir)(global, cwd) : (0, agent_install_skill.getSkillAgentDir)(agent, {
454
+ global,
455
+ cwd
456
+ }), SKILL_NAME);
457
+ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.cwd() } = {}) => {
455
458
  const detectedAgents = await detectAvailableAgents();
456
459
  if (detectedAgents.length === 0) {
457
460
  logger.warn("No supported agents detected.");
@@ -462,7 +465,7 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
462
465
  const { agents } = await prompts$1({
463
466
  type: "multiselect",
464
467
  name: "agents",
465
- message: "Install the React Grab skill for:",
468
+ message: `Install the React Grab skill (${global ? "global" : "this project"}) for:`,
466
469
  choices: detectedAgents.map((agent) => ({
467
470
  title: agentLabel(agent),
468
471
  value: agent,
@@ -478,7 +481,8 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
478
481
  const { installed, failed } = await (0, agent_install_skill.add)({
479
482
  source: SKILL_SOURCE,
480
483
  agents: selectedAgents,
481
- global: true,
484
+ global,
485
+ cwd,
482
486
  mode: "copy"
483
487
  });
484
488
  if (installed.length === 0) {
@@ -489,19 +493,27 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
489
493
  for (const record of failed) logger.log(` ${highlighter.error("✗")} ${agentLabel(record.agent)} ${record.error}`);
490
494
  return true;
491
495
  };
492
- const removeSkill = async () => {
493
- const agentsWithSkill = (await detectAvailableAgents()).filter((agent) => (0, node_fs.existsSync)(installedSkillDir(agent)));
494
- for (const skillDir of new Set(agentsWithSkill.map(installedSkillDir))) (0, node_fs.rmSync)(skillDir, {
496
+ const removeSkill = async (cwd = process.cwd()) => {
497
+ const agents = await detectAvailableAgents();
498
+ const removedAgents = [];
499
+ const dirsToRemove = /* @__PURE__ */ new Set();
500
+ for (const agent of agents) {
501
+ const present = [installedSkillDir(agent, false, cwd), installedSkillDir(agent, true, cwd)].filter((dir) => (0, node_fs.existsSync)(dir));
502
+ if (present.length === 0) continue;
503
+ removedAgents.push(agent);
504
+ for (const dir of present) dirsToRemove.add(dir);
505
+ }
506
+ for (const skillDir of dirsToRemove) (0, node_fs.rmSync)(skillDir, {
495
507
  recursive: true,
496
508
  force: true
497
509
  });
498
- for (const agent of agentsWithSkill) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
499
- return agentsWithSkill.length;
510
+ for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
511
+ return removedAgents.length;
500
512
  };
501
513
  //#endregion
502
514
  //#region src/commands/add.ts
503
- const VERSION$5 = "0.1.40";
504
- const add = new commander.Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).action(async (opts) => {
515
+ const VERSION$5 = "0.1.41";
516
+ const add = new commander.Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
505
517
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$5)}`);
506
518
  console.log();
507
519
  try {
@@ -516,7 +528,11 @@ const add = new commander.Command().name("add").alias("install").description("in
516
528
  }
517
529
  preflightSpinner.succeed();
518
530
  logger.break();
519
- if (!await promptSkillInstall({ yes: isNonInteractive }) && isNonInteractive) {
531
+ if (!await promptSkillInstall({
532
+ yes: isNonInteractive,
533
+ global: opts.global,
534
+ cwd: (0, node_path.resolve)(opts.cwd)
535
+ }) && isNonInteractive) {
520
536
  logger.break();
521
537
  process.exit(1);
522
538
  }
@@ -1095,6 +1111,7 @@ const previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDo
1095
1111
  //#region src/utils/constants.ts
1096
1112
  const MAX_KEY_HOLD_DURATION_MS = 2e3;
1097
1113
  const DEFAULT_WATCH_DIR = ".react-grab";
1114
+ const MAX_GRAB_AGE_MS = 300 * 1e3;
1098
1115
  //#endregion
1099
1116
  //#region src/utils/format-activation-key.ts
1100
1117
  const formatActivationKeyDisplay = (activationKey) => {
@@ -1112,7 +1129,7 @@ const formatActivationKeyDisplay = (activationKey) => {
1112
1129
  };
1113
1130
  //#endregion
1114
1131
  //#region src/commands/configure.ts
1115
- const VERSION$4 = "0.1.40";
1132
+ const VERSION$4 = "0.1.41";
1116
1133
  const isMac = process.platform === "darwin";
1117
1134
  const META_LABEL = isMac ? "Cmd" : "Win";
1118
1135
  const ALT_LABEL = isMac ? "Option" : "Alt";
@@ -1731,7 +1748,7 @@ const installPackagesWithFeedback = async (packages, packageManager, projectRoot
1731
1748
  };
1732
1749
  //#endregion
1733
1750
  //#region src/commands/init.ts
1734
- const VERSION$3 = "0.1.40";
1751
+ const VERSION$3 = "0.1.41";
1735
1752
  const REPORT_URL = "https://react-grab.com/api/report-cli";
1736
1753
  const DOCS_URL = "https://github.com/aidenybai/react-grab";
1737
1754
  const reportToCli = (type, config, error) => {
@@ -1798,7 +1815,7 @@ const failWithManualSetup = (failingSpinner, message, { listSupportedFrameworks
1798
1815
  logger.break();
1799
1816
  process.exit(1);
1800
1817
  };
1801
- const init = new commander.Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option("-k, --key <key>", "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).action(async (opts) => {
1818
+ const init = new commander.Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option("-k, --key <key>", "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
1802
1819
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$3)}`);
1803
1820
  console.log();
1804
1821
  try {
@@ -1956,7 +1973,11 @@ const init = new commander.Command().name("init").alias("setup").description("in
1956
1973
  }
1957
1974
  }
1958
1975
  logger.break();
1959
- await promptSkillInstall({ yes: isNonInteractive });
1976
+ await promptSkillInstall({
1977
+ yes: isNonInteractive,
1978
+ global: opts.global,
1979
+ cwd
1980
+ });
1960
1981
  logger.break();
1961
1982
  process.exit(0);
1962
1983
  }
@@ -2022,7 +2043,11 @@ const init = new commander.Command().name("init").alias("setup").description("in
2022
2043
  let didInstallSkill = false;
2023
2044
  if (!isNonInteractive) {
2024
2045
  logger.break();
2025
- didInstallSkill = await promptSkillInstall({ yes: isNonInteractive });
2046
+ didInstallSkill = await promptSkillInstall({
2047
+ yes: isNonInteractive,
2048
+ global: opts.global,
2049
+ cwd
2050
+ });
2026
2051
  }
2027
2052
  const result = previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, false, opts.force);
2028
2053
  if (!result.success) {
@@ -2417,18 +2442,48 @@ const readGrabCursor = (dir) => {
2417
2442
  return 0;
2418
2443
  }
2419
2444
  };
2445
+ const grabReceivedAt = (line) => {
2446
+ try {
2447
+ const value = JSON.parse(line).receivedAt;
2448
+ return typeof value === "number" ? value : null;
2449
+ } catch {
2450
+ return null;
2451
+ }
2452
+ };
2420
2453
  const consumeGrabs = (dir, options) => {
2421
2454
  const lines = readCompleteGrabLines(dir);
2422
2455
  if (options.all) return lines;
2456
+ const maxAgeMs = options.maxAgeMs ?? 0;
2423
2457
  const cursor = readGrabCursor(dir);
2424
- const start = Math.min(cursor, lines.length);
2425
- const end = options.limit > 0 ? Math.min(start + options.limit, lines.length) : lines.length;
2426
- if (end !== cursor) node_fs.default.writeFileSync(cursorFilePath(dir), String(end));
2427
- return lines.slice(start, end);
2458
+ const total = lines.length;
2459
+ const now = Date.now();
2460
+ const fresh = [];
2461
+ let index = Math.min(cursor, total);
2462
+ while (index < total && (options.limit <= 0 || fresh.length < options.limit)) {
2463
+ const line = lines[index];
2464
+ index += 1;
2465
+ if (maxAgeMs > 0) {
2466
+ const receivedAt = grabReceivedAt(line);
2467
+ if (receivedAt !== null && now - receivedAt > maxAgeMs) continue;
2468
+ }
2469
+ fresh.push(line);
2470
+ }
2471
+ if (index !== cursor) node_fs.default.writeFileSync(cursorFilePath(dir), String(index));
2472
+ return fresh;
2428
2473
  };
2429
2474
  //#endregion
2430
2475
  //#region src/commands/read.ts
2431
- const read = new commander.Command().name("read").description("print React Grab selections captured since the last read, then advance the cursor").option("-d, --dir <dir>", "work dir holding history.jsonl + cursor.txt", DEFAULT_WATCH_DIR).option("-w, --wait <ms>", "block up to <ms> for at least one new grab (default: no wait)").option("-n, --limit <count>", "max grabs to print per call (0 = no limit); the cursor only advances past what is printed", String(50)).option("--all", "print the entire history without advancing the cursor").action(async (options) => {
2476
+ const parseWaitMs = (raw) => {
2477
+ if (!raw) return 0;
2478
+ if (/^(inf|infinite|forever)$/i.test(raw.trim())) return Number.POSITIVE_INFINITY;
2479
+ const ms = Number(raw);
2480
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
2481
+ };
2482
+ const parseNonNegativeInt = (raw, fallback) => {
2483
+ const value = Number(raw);
2484
+ return Number.isInteger(value) && value >= 0 ? value : fallback;
2485
+ };
2486
+ const read = new commander.Command().name("read").description("print React Grab selections captured since the last read, then advance the cursor").option("-d, --dir <dir>", "work dir holding history.jsonl + cursor.txt", DEFAULT_WATCH_DIR).option("-w, --wait <ms>", "block up to <ms> (or 'infinite') for a new grab (default: no wait)").option("-n, --limit <count>", "max grabs to print per call (0 = no limit); the cursor only advances past what is printed", String(50)).option("--max-age <ms>", "skip grabs captured longer ago than <ms> (0 = never evict)", String(MAX_GRAB_AGE_MS)).option("--all", "print the entire history without advancing the cursor").action(async (options) => {
2432
2487
  const dir = node_path.default.resolve(options.dir);
2433
2488
  try {
2434
2489
  prepareWorkDir(dir);
@@ -2437,19 +2492,20 @@ const read = new commander.Command().name("read").description("print React Grab
2437
2492
  process.exit(1);
2438
2493
  }
2439
2494
  unrefStdin();
2440
- const limitRaw = Number(options.limit);
2441
- const limit = Number.isInteger(limitRaw) && limitRaw >= 0 ? limitRaw : 50;
2495
+ const limit = parseNonNegativeInt(options.limit, 50);
2496
+ const maxAgeMs = parseNonNegativeInt(options.maxAge, MAX_GRAB_AGE_MS);
2442
2497
  const all = Boolean(options.all);
2443
2498
  const drain = () => {
2444
2499
  const fresh = consumeGrabs(dir, {
2445
2500
  limit,
2446
- all
2501
+ all,
2502
+ maxAgeMs
2447
2503
  });
2448
2504
  if (fresh.length > 0) process.stdout.write(`${fresh.join("\n")}\n`);
2449
2505
  return fresh.length;
2450
2506
  };
2451
- const waitRaw = options.wait ? Number(options.wait) : 0;
2452
- const deadline = Date.now() + (Number.isFinite(waitRaw) && waitRaw > 0 ? waitRaw : 0);
2507
+ const waitMs = parseWaitMs(options.wait);
2508
+ const deadline = waitMs === Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY : Date.now() + waitMs;
2453
2509
  if (drain() > 0) process.exit(0);
2454
2510
  while (Date.now() < deadline) {
2455
2511
  await sleep(200);
@@ -2459,7 +2515,7 @@ const read = new commander.Command().name("read").description("print React Grab
2459
2515
  });
2460
2516
  //#endregion
2461
2517
  //#region src/commands/remove.ts
2462
- const VERSION$2 = "0.1.40";
2518
+ const VERSION$2 = "0.1.41";
2463
2519
  const remove = new commander.Command().name("remove").description("uninstall the React Grab skill from your agent").action(async () => {
2464
2520
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$2)}`);
2465
2521
  console.log();
@@ -2476,7 +2532,7 @@ const remove = new commander.Command().name("remove").description("uninstall the
2476
2532
  });
2477
2533
  //#endregion
2478
2534
  //#region src/commands/upgrade.ts
2479
- const VERSION$1 = "0.1.40";
2535
+ const VERSION$1 = "0.1.41";
2480
2536
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/react-grab/latest";
2481
2537
  const fetchLatestVersion = async () => {
2482
2538
  try {
@@ -2701,7 +2757,7 @@ const watch = new commander.Command().name("watch").description("start a backgro
2701
2757
  });
2702
2758
  //#endregion
2703
2759
  //#region src/cli.ts
2704
- const VERSION = "0.1.40";
2760
+ const VERSION = "0.1.41";
2705
2761
  const VERSION_API_URL = "https://www.react-grab.com/api/version";
2706
2762
  process.on("SIGINT", () => process.exit(0));
2707
2763
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ import path, { basename, delimiter, dirname, join, relative, resolve } from "node:path";
3
4
  import pc from "picocolors";
4
5
  import fs, { accessSync, constants, existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
5
- import path, { basename, delimiter, dirname, join, relative, resolve } from "node:path";
6
6
  import { detect } from "package-manager-detector/detect";
7
7
  import ignore from "ignore";
8
8
  import { fileURLToPath } from "node:url";
@@ -422,8 +422,11 @@ const spinner = (text) => ora({ text });
422
422
  const SKILL_NAME = "react-grab";
423
423
  const SKILL_SOURCE = fileURLToPath(new URL("../skills/react-grab", import.meta.url));
424
424
  const agentLabel = (agent) => getSkillAgentConfig(agent).displayName;
425
- const installedSkillDir = (agent) => join(isUniversalSkillAgent(agent) ? getCanonicalSkillsDir(true) : getSkillAgentDir(agent, { global: true }), SKILL_NAME);
426
- const promptSkillInstall = async ({ yes = false } = {}) => {
425
+ const installedSkillDir = (agent, global, cwd) => join(isUniversalSkillAgent(agent) ? getCanonicalSkillsDir(global, cwd) : getSkillAgentDir(agent, {
426
+ global,
427
+ cwd
428
+ }), SKILL_NAME);
429
+ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.cwd() } = {}) => {
427
430
  const detectedAgents = await detectAvailableAgents();
428
431
  if (detectedAgents.length === 0) {
429
432
  logger.warn("No supported agents detected.");
@@ -434,7 +437,7 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
434
437
  const { agents } = await prompts({
435
438
  type: "multiselect",
436
439
  name: "agents",
437
- message: "Install the React Grab skill for:",
440
+ message: `Install the React Grab skill (${global ? "global" : "this project"}) for:`,
438
441
  choices: detectedAgents.map((agent) => ({
439
442
  title: agentLabel(agent),
440
443
  value: agent,
@@ -450,7 +453,8 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
450
453
  const { installed, failed } = await add({
451
454
  source: SKILL_SOURCE,
452
455
  agents: selectedAgents,
453
- global: true,
456
+ global,
457
+ cwd,
454
458
  mode: "copy"
455
459
  });
456
460
  if (installed.length === 0) {
@@ -461,19 +465,27 @@ const promptSkillInstall = async ({ yes = false } = {}) => {
461
465
  for (const record of failed) logger.log(` ${highlighter.error("✗")} ${agentLabel(record.agent)} ${record.error}`);
462
466
  return true;
463
467
  };
464
- const removeSkill = async () => {
465
- const agentsWithSkill = (await detectAvailableAgents()).filter((agent) => existsSync(installedSkillDir(agent)));
466
- for (const skillDir of new Set(agentsWithSkill.map(installedSkillDir))) rmSync(skillDir, {
468
+ const removeSkill = async (cwd = process.cwd()) => {
469
+ const agents = await detectAvailableAgents();
470
+ const removedAgents = [];
471
+ const dirsToRemove = /* @__PURE__ */ new Set();
472
+ for (const agent of agents) {
473
+ const present = [installedSkillDir(agent, false, cwd), installedSkillDir(agent, true, cwd)].filter((dir) => existsSync(dir));
474
+ if (present.length === 0) continue;
475
+ removedAgents.push(agent);
476
+ for (const dir of present) dirsToRemove.add(dir);
477
+ }
478
+ for (const skillDir of dirsToRemove) rmSync(skillDir, {
467
479
  recursive: true,
468
480
  force: true
469
481
  });
470
- for (const agent of agentsWithSkill) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
471
- return agentsWithSkill.length;
482
+ for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
483
+ return removedAgents.length;
472
484
  };
473
485
  //#endregion
474
486
  //#region src/commands/add.ts
475
- const VERSION$5 = "0.1.40";
476
- const add$1 = new Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).action(async (opts) => {
487
+ const VERSION$5 = "0.1.41";
488
+ const add$1 = new Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
477
489
  console.log(`${pc.magenta("✿")} ${pc.bold("React Grab")} ${pc.gray(VERSION$5)}`);
478
490
  console.log();
479
491
  try {
@@ -488,7 +500,11 @@ const add$1 = new Command().name("add").alias("install").description("install th
488
500
  }
489
501
  preflightSpinner.succeed();
490
502
  logger.break();
491
- if (!await promptSkillInstall({ yes: isNonInteractive }) && isNonInteractive) {
503
+ if (!await promptSkillInstall({
504
+ yes: isNonInteractive,
505
+ global: opts.global,
506
+ cwd: resolve(opts.cwd)
507
+ }) && isNonInteractive) {
492
508
  logger.break();
493
509
  process.exit(1);
494
510
  }
@@ -1067,6 +1083,7 @@ const previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDo
1067
1083
  //#region src/utils/constants.ts
1068
1084
  const MAX_KEY_HOLD_DURATION_MS = 2e3;
1069
1085
  const DEFAULT_WATCH_DIR = ".react-grab";
1086
+ const MAX_GRAB_AGE_MS = 300 * 1e3;
1070
1087
  //#endregion
1071
1088
  //#region src/utils/format-activation-key.ts
1072
1089
  const formatActivationKeyDisplay = (activationKey) => {
@@ -1084,7 +1101,7 @@ const formatActivationKeyDisplay = (activationKey) => {
1084
1101
  };
1085
1102
  //#endregion
1086
1103
  //#region src/commands/configure.ts
1087
- const VERSION$4 = "0.1.40";
1104
+ const VERSION$4 = "0.1.41";
1088
1105
  const isMac = process.platform === "darwin";
1089
1106
  const META_LABEL = isMac ? "Cmd" : "Win";
1090
1107
  const ALT_LABEL = isMac ? "Option" : "Alt";
@@ -1703,7 +1720,7 @@ const installPackagesWithFeedback = async (packages, packageManager, projectRoot
1703
1720
  };
1704
1721
  //#endregion
1705
1722
  //#region src/commands/init.ts
1706
- const VERSION$3 = "0.1.40";
1723
+ const VERSION$3 = "0.1.41";
1707
1724
  const REPORT_URL = "https://react-grab.com/api/report-cli";
1708
1725
  const DOCS_URL = "https://github.com/aidenybai/react-grab";
1709
1726
  const reportToCli = (type, config, error) => {
@@ -1770,7 +1787,7 @@ const failWithManualSetup = (failingSpinner, message, { listSupportedFrameworks
1770
1787
  logger.break();
1771
1788
  process.exit(1);
1772
1789
  };
1773
- const init = new Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option("-k, --key <key>", "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).action(async (opts) => {
1790
+ const init = new Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option("-k, --key <key>", "activation key (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
1774
1791
  console.log(`${pc.magenta("✿")} ${pc.bold("React Grab")} ${pc.gray(VERSION$3)}`);
1775
1792
  console.log();
1776
1793
  try {
@@ -1928,7 +1945,11 @@ const init = new Command().name("init").alias("setup").description("initialize R
1928
1945
  }
1929
1946
  }
1930
1947
  logger.break();
1931
- await promptSkillInstall({ yes: isNonInteractive });
1948
+ await promptSkillInstall({
1949
+ yes: isNonInteractive,
1950
+ global: opts.global,
1951
+ cwd
1952
+ });
1932
1953
  logger.break();
1933
1954
  process.exit(0);
1934
1955
  }
@@ -1994,7 +2015,11 @@ const init = new Command().name("init").alias("setup").description("initialize R
1994
2015
  let didInstallSkill = false;
1995
2016
  if (!isNonInteractive) {
1996
2017
  logger.break();
1997
- didInstallSkill = await promptSkillInstall({ yes: isNonInteractive });
2018
+ didInstallSkill = await promptSkillInstall({
2019
+ yes: isNonInteractive,
2020
+ global: opts.global,
2021
+ cwd
2022
+ });
1998
2023
  }
1999
2024
  const result = previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, false, opts.force);
2000
2025
  if (!result.success) {
@@ -2389,18 +2414,48 @@ const readGrabCursor = (dir) => {
2389
2414
  return 0;
2390
2415
  }
2391
2416
  };
2417
+ const grabReceivedAt = (line) => {
2418
+ try {
2419
+ const value = JSON.parse(line).receivedAt;
2420
+ return typeof value === "number" ? value : null;
2421
+ } catch {
2422
+ return null;
2423
+ }
2424
+ };
2392
2425
  const consumeGrabs = (dir, options) => {
2393
2426
  const lines = readCompleteGrabLines(dir);
2394
2427
  if (options.all) return lines;
2428
+ const maxAgeMs = options.maxAgeMs ?? 0;
2395
2429
  const cursor = readGrabCursor(dir);
2396
- const start = Math.min(cursor, lines.length);
2397
- const end = options.limit > 0 ? Math.min(start + options.limit, lines.length) : lines.length;
2398
- if (end !== cursor) fs.writeFileSync(cursorFilePath(dir), String(end));
2399
- return lines.slice(start, end);
2430
+ const total = lines.length;
2431
+ const now = Date.now();
2432
+ const fresh = [];
2433
+ let index = Math.min(cursor, total);
2434
+ while (index < total && (options.limit <= 0 || fresh.length < options.limit)) {
2435
+ const line = lines[index];
2436
+ index += 1;
2437
+ if (maxAgeMs > 0) {
2438
+ const receivedAt = grabReceivedAt(line);
2439
+ if (receivedAt !== null && now - receivedAt > maxAgeMs) continue;
2440
+ }
2441
+ fresh.push(line);
2442
+ }
2443
+ if (index !== cursor) fs.writeFileSync(cursorFilePath(dir), String(index));
2444
+ return fresh;
2400
2445
  };
2401
2446
  //#endregion
2402
2447
  //#region src/commands/read.ts
2403
- const read = new Command().name("read").description("print React Grab selections captured since the last read, then advance the cursor").option("-d, --dir <dir>", "work dir holding history.jsonl + cursor.txt", DEFAULT_WATCH_DIR).option("-w, --wait <ms>", "block up to <ms> for at least one new grab (default: no wait)").option("-n, --limit <count>", "max grabs to print per call (0 = no limit); the cursor only advances past what is printed", String(50)).option("--all", "print the entire history without advancing the cursor").action(async (options) => {
2448
+ const parseWaitMs = (raw) => {
2449
+ if (!raw) return 0;
2450
+ if (/^(inf|infinite|forever)$/i.test(raw.trim())) return Number.POSITIVE_INFINITY;
2451
+ const ms = Number(raw);
2452
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
2453
+ };
2454
+ const parseNonNegativeInt = (raw, fallback) => {
2455
+ const value = Number(raw);
2456
+ return Number.isInteger(value) && value >= 0 ? value : fallback;
2457
+ };
2458
+ const read = new Command().name("read").description("print React Grab selections captured since the last read, then advance the cursor").option("-d, --dir <dir>", "work dir holding history.jsonl + cursor.txt", DEFAULT_WATCH_DIR).option("-w, --wait <ms>", "block up to <ms> (or 'infinite') for a new grab (default: no wait)").option("-n, --limit <count>", "max grabs to print per call (0 = no limit); the cursor only advances past what is printed", String(50)).option("--max-age <ms>", "skip grabs captured longer ago than <ms> (0 = never evict)", String(MAX_GRAB_AGE_MS)).option("--all", "print the entire history without advancing the cursor").action(async (options) => {
2404
2459
  const dir = path.resolve(options.dir);
2405
2460
  try {
2406
2461
  prepareWorkDir(dir);
@@ -2409,19 +2464,20 @@ const read = new Command().name("read").description("print React Grab selections
2409
2464
  process.exit(1);
2410
2465
  }
2411
2466
  unrefStdin();
2412
- const limitRaw = Number(options.limit);
2413
- const limit = Number.isInteger(limitRaw) && limitRaw >= 0 ? limitRaw : 50;
2467
+ const limit = parseNonNegativeInt(options.limit, 50);
2468
+ const maxAgeMs = parseNonNegativeInt(options.maxAge, MAX_GRAB_AGE_MS);
2414
2469
  const all = Boolean(options.all);
2415
2470
  const drain = () => {
2416
2471
  const fresh = consumeGrabs(dir, {
2417
2472
  limit,
2418
- all
2473
+ all,
2474
+ maxAgeMs
2419
2475
  });
2420
2476
  if (fresh.length > 0) process.stdout.write(`${fresh.join("\n")}\n`);
2421
2477
  return fresh.length;
2422
2478
  };
2423
- const waitRaw = options.wait ? Number(options.wait) : 0;
2424
- const deadline = Date.now() + (Number.isFinite(waitRaw) && waitRaw > 0 ? waitRaw : 0);
2479
+ const waitMs = parseWaitMs(options.wait);
2480
+ const deadline = waitMs === Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY : Date.now() + waitMs;
2425
2481
  if (drain() > 0) process.exit(0);
2426
2482
  while (Date.now() < deadline) {
2427
2483
  await sleep(200);
@@ -2431,7 +2487,7 @@ const read = new Command().name("read").description("print React Grab selections
2431
2487
  });
2432
2488
  //#endregion
2433
2489
  //#region src/commands/remove.ts
2434
- const VERSION$2 = "0.1.40";
2490
+ const VERSION$2 = "0.1.41";
2435
2491
  const remove = new Command().name("remove").description("uninstall the React Grab skill from your agent").action(async () => {
2436
2492
  console.log(`${pc.magenta("✿")} ${pc.bold("React Grab")} ${pc.gray(VERSION$2)}`);
2437
2493
  console.log();
@@ -2448,7 +2504,7 @@ const remove = new Command().name("remove").description("uninstall the React Gra
2448
2504
  });
2449
2505
  //#endregion
2450
2506
  //#region src/commands/upgrade.ts
2451
- const VERSION$1 = "0.1.40";
2507
+ const VERSION$1 = "0.1.41";
2452
2508
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/react-grab/latest";
2453
2509
  const fetchLatestVersion = async () => {
2454
2510
  try {
@@ -2673,7 +2729,7 @@ const watch = new Command().name("watch").description("start a background daemon
2673
2729
  });
2674
2730
  //#endregion
2675
2731
  //#region src/cli.ts
2676
- const VERSION = "0.1.40";
2732
+ const VERSION = "0.1.41";
2677
2733
  const VERSION_API_URL = "https://www.react-grab.com/api/version";
2678
2734
  process.on("SIGINT", () => process.exit(0));
2679
2735
  process.on("SIGTERM", () => process.exit(0));