open-nami 1.2.7 → 1.2.8

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/cli.js +71 -206
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ var require_package = __commonJS({
9
9
  "package.json"(exports, module) {
10
10
  module.exports = {
11
11
  name: "open-nami",
12
- version: "1.2.7",
12
+ version: "1.2.8",
13
13
  description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
14
14
  type: "module",
15
15
  bin: {
@@ -77,8 +77,8 @@ import { createRequire } from "module";
77
77
  import { render } from "ink";
78
78
 
79
79
  // src/app.tsx
80
- import { useState as useState9, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect8, useMemo as useMemo3 } from "react";
81
- import { Box as Box15, Text as Text17, useApp, useInput as useInput6, useStdout as useStdout3 } from "ink";
80
+ import { useState as useState9, useCallback as useCallback4, useRef as useRef5, useEffect as useEffect8, useMemo as useMemo3 } from "react";
81
+ import { Box as Box15, Text as Text17, useApp, useInput as useInput5, useStdout as useStdout3 } from "ink";
82
82
  import Spinner4 from "ink-spinner";
83
83
 
84
84
  // src/components/fullscreen.tsx
@@ -691,8 +691,8 @@ function Panel({ title, width, children }) {
691
691
  }
692
692
 
693
693
  // src/components/views/picking-view.tsx
694
- import { useState as useState4, useEffect as useEffect4, useCallback } from "react";
695
- import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
694
+ import { useState as useState4, useEffect as useEffect4 } from "react";
695
+ import { Box as Box6, Text as Text7 } from "ink";
696
696
  import SelectInput from "ink-select-input";
697
697
 
698
698
  // src/lib/engines/checkpoint.ts
@@ -4368,7 +4368,7 @@ async function dispatchDownload(options) {
4368
4368
  }
4369
4369
 
4370
4370
  // src/components/views/picking-view.tsx
4371
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4371
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4372
4372
  function ChoiceIndicator({ isSelected }) {
4373
4373
  const theme = useTheme();
4374
4374
  return /* @__PURE__ */ jsx8(Box6, { marginRight: 1, children: /* @__PURE__ */ jsx8(Text7, { color: theme.primary, children: isSelected ? "\u276F" : " " }) });
@@ -4392,14 +4392,13 @@ function getPickingOptions(platform) {
4392
4392
  });
4393
4393
  return items;
4394
4394
  }
4395
- var DATE_PRESETS = ["all", "24h", "7d", "30d", "2024", "2023", "2022", "2021", "custom"];
4396
- var LIMIT_PRESETS = [void 0, 10, 24, 50, 100, "custom"];
4397
4395
  function PickingView({
4398
4396
  contentWidth,
4399
4397
  profile,
4400
4398
  outputDir,
4401
4399
  initialLimit,
4402
4400
  initialSince,
4401
+ initialUntil,
4403
4402
  initialWriteMetadata = false,
4404
4403
  initialResume = true,
4405
4404
  onSelect,
@@ -4409,26 +4408,13 @@ function PickingView({
4409
4408
  }) {
4410
4409
  const platformName = profile.platform.charAt(0).toUpperCase() + profile.platform.slice(1);
4411
4410
  const items = getPickingOptions(profile.platform);
4412
- const [limit, setLimit] = useState4(initialLimit);
4413
- const [dateFilter, setDateFilter] = useState4(() => {
4414
- if (typeof initialSince === "string" && initialSince.trim()) {
4415
- return initialSince.trim();
4416
- }
4417
- return "all";
4418
- });
4419
- const [writeMetadata, setWriteMetadata] = useState4(initialWriteMetadata);
4420
- const [resume, setResume] = useState4(initialResume);
4421
4411
  const [hasCheckpoint, setHasCheckpoint] = useState4(false);
4422
- const [editingModal, setEditingModal] = useState4(null);
4423
- const [customLimitText, setCustomLimitText] = useState4("");
4424
- const [customDateText, setCustomDateText] = useState4("");
4425
4412
  const targetDir = resolveDefaultDownloadDir(profile.platform, profile.username, outputDir);
4426
4413
  useEffect4(() => {
4427
4414
  let isMounted = true;
4428
4415
  loadCheckpoint(targetDir).then((cp) => {
4429
4416
  if (isMounted && cp && !cp.completed && Object.keys(cp.cursors || {}).length > 0) {
4430
4417
  setHasCheckpoint(true);
4431
- setResume(true);
4432
4418
  }
4433
4419
  }).catch(() => {
4434
4420
  });
@@ -4436,68 +4422,14 @@ function PickingView({
4436
4422
  isMounted = false;
4437
4423
  };
4438
4424
  }, [targetDir]);
4439
- const cycleLimit = useCallback(() => {
4440
- const curIdx = LIMIT_PRESETS.findIndex((p) => p === limit);
4441
- const nextIdx = (curIdx + 1) % LIMIT_PRESETS.length;
4442
- const nextVal = LIMIT_PRESETS[nextIdx];
4443
- if (nextVal === "custom") {
4444
- setCustomLimitText(limit ? String(limit) : "24");
4445
- setEditingModal("limit");
4446
- } else {
4447
- setLimit(nextVal);
4448
- }
4449
- }, [limit]);
4450
- const cycleDate = useCallback(() => {
4451
- const curIdx = DATE_PRESETS.indexOf(dateFilter);
4452
- const nextIdx = (curIdx + 1) % DATE_PRESETS.length;
4453
- const nextVal = DATE_PRESETS[nextIdx];
4454
- if (nextVal === "custom") {
4455
- setCustomDateText(dateFilter !== "all" ? dateFilter : "2021");
4456
- setEditingModal("date");
4457
- } else {
4458
- setDateFilter(nextVal);
4459
- }
4460
- }, [dateFilter]);
4461
- const toggleMetadata = useCallback(() => {
4462
- setWriteMetadata((prev) => !prev);
4463
- }, []);
4464
- const toggleResume = useCallback(() => {
4465
- setResume((prev) => !prev);
4466
- }, []);
4467
- useInput2((input, key) => {
4468
- if (editingModal) {
4469
- if (key.escape) {
4470
- setEditingModal(null);
4471
- }
4472
- return;
4473
- }
4474
- if (input === "l" || input === "L") {
4475
- cycleLimit();
4476
- return;
4477
- }
4478
- if (input === "d" || input === "D") {
4479
- cycleDate();
4480
- return;
4481
- }
4482
- if (input === "m" || input === "M") {
4483
- toggleMetadata();
4484
- return;
4485
- }
4486
- if (input === "r" || input === "R") {
4487
- toggleResume();
4488
- return;
4489
- }
4490
- }, { isActive: Boolean(process.stdin.isTTY) });
4491
4425
  const handleItemSelect = (selectedChoice) => {
4492
- if (editingModal) return;
4493
- const dateRange = resolveDateFilter(dateFilter);
4494
4426
  onSelect({
4495
4427
  choice: selectedChoice,
4496
- limit,
4497
- since: dateRange.since,
4498
- until: dateRange.until,
4499
- writeMetadata,
4500
- resume
4428
+ limit: initialLimit,
4429
+ since: typeof initialSince === "string" ? initialSince : void 0,
4430
+ until: typeof initialUntil === "string" ? initialUntil : void 0,
4431
+ writeMetadata: initialWriteMetadata,
4432
+ resume: initialResume
4501
4433
  });
4502
4434
  };
4503
4435
  return /* @__PURE__ */ jsxs6(Box6, { width: contentWidth, children: [
@@ -4513,90 +4445,23 @@ function PickingView({
4513
4445
  profile.cleanUrl ? ` \xB7 ${profile.cleanUrl}` : ""
4514
4446
  ] }),
4515
4447
  /* @__PURE__ */ jsx8(Gap2, {}),
4516
- /* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: "Select media type. Hotkeys [l], [d], [m], [r] adjust options." }),
4448
+ /* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: "Select what to download from this profile." }),
4517
4449
  hasCheckpoint ? /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: "\u26A1 Checkpoint found (cursor saved)" }) }) : null
4518
4450
  ] }),
4519
- /* @__PURE__ */ jsxs6(Panel, { title: "Download", width: 38, children: [
4520
- !editingModal && /* @__PURE__ */ jsx8(
4521
- SelectInput,
4522
- {
4523
- indicatorComponent: ChoiceIndicator,
4524
- itemComponent: ChoiceItem,
4525
- items: items.map((item, index) => ({
4526
- key: String(index),
4527
- label: item.label,
4528
- value: item.value
4529
- })),
4530
- onSelect: (item) => handleItemSelect(item.value),
4531
- onHighlight: (item) => onHighlight?.(item.value)
4532
- }
4533
- ),
4534
- /* @__PURE__ */ jsx8(
4535
- Box6,
4536
- {
4537
- flexDirection: "column",
4538
- marginTop: 1,
4539
- borderStyle: "round",
4540
- borderColor: editingModal ? theme.primary : theme.gray,
4541
- borderDimColor: !editingModal,
4542
- paddingX: 1,
4543
- children: editingModal === "limit" ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4544
- /* @__PURE__ */ jsx8(Text7, { bold: true, color: theme.primary, children: "Set post limit (e.g. 24, all):" }),
4545
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(
4546
- TextInput,
4547
- {
4548
- value: customLimitText,
4549
- onChange: setCustomLimitText,
4550
- onSubmit: (val) => {
4551
- const parsed = parseLimit(val);
4552
- setLimit(parsed);
4553
- setEditingModal(null);
4554
- },
4555
- width: 24
4556
- }
4557
- ) }),
4558
- /* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
4559
- ] }) : editingModal === "date" ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4560
- /* @__PURE__ */ jsx8(Text7, { bold: true, color: theme.primary, children: "Set date or year (e.g. 2021, 7d):" }),
4561
- /* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx8(
4562
- TextInput,
4563
- {
4564
- value: customDateText,
4565
- onChange: setCustomDateText,
4566
- onSubmit: (val) => {
4567
- const trimmed = val.trim();
4568
- setDateFilter(trimmed ? trimmed : "all");
4569
- setEditingModal(null);
4570
- },
4571
- width: 24
4572
- }
4573
- ) }),
4574
- /* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
4575
- ] }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
4576
- /* @__PURE__ */ jsxs6(Box6, { justifyContent: "space-between", children: [
4577
- /* @__PURE__ */ jsxs6(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: [
4578
- "[l] Posts: ",
4579
- /* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: limit ? `${limit}` : "ALL" })
4580
- ] }),
4581
- /* @__PURE__ */ jsxs6(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: [
4582
- "[d] Date: ",
4583
- /* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: dateFilter.toUpperCase() })
4584
- ] })
4585
- ] }),
4586
- /* @__PURE__ */ jsxs6(Box6, { justifyContent: "space-between", children: [
4587
- /* @__PURE__ */ jsxs6(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: [
4588
- "[m] Meta: ",
4589
- /* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: writeMetadata ? "ON" : "OFF" })
4590
- ] }),
4591
- /* @__PURE__ */ jsxs6(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: [
4592
- "[r] Resume: ",
4593
- /* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: resume ? "ON" : "OFF" })
4594
- ] })
4595
- ] })
4596
- ] })
4597
- }
4598
- )
4599
- ] })
4451
+ /* @__PURE__ */ jsx8(Panel, { title: "Download", width: 38, children: /* @__PURE__ */ jsx8(
4452
+ SelectInput,
4453
+ {
4454
+ indicatorComponent: ChoiceIndicator,
4455
+ itemComponent: ChoiceItem,
4456
+ items: items.map((item, index) => ({
4457
+ key: String(index),
4458
+ label: item.label,
4459
+ value: item.value
4460
+ })),
4461
+ onSelect: (item) => handleItemSelect(item.value),
4462
+ onHighlight: (item) => onHighlight?.(item.value)
4463
+ }
4464
+ ) })
4600
4465
  ] });
4601
4466
  }
4602
4467
 
@@ -4808,7 +4673,7 @@ function DownloadingView({
4808
4673
  // src/components/views/completion-view.tsx
4809
4674
  import os7 from "os";
4810
4675
  import { Box as Box10, Text as Text12 } from "ink";
4811
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
4676
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
4812
4677
  var DONE_LABEL = "\u21B5 download another";
4813
4678
  function CompletionView({
4814
4679
  profile,
@@ -4820,10 +4685,10 @@ function CompletionView({
4820
4685
  const plural = result.downloadedCount === 1 ? "" : "s";
4821
4686
  const skipText = result.skippedCount > 0 ? ` (${result.skippedCount} archived)` : "";
4822
4687
  return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", alignItems: "center", children: [
4823
- /* @__PURE__ */ jsx13(Text12, { children: isZero ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
4688
+ /* @__PURE__ */ jsx13(Text12, { children: isZero ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
4824
4689
  /* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: "\u2713 profile scanned " }),
4825
4690
  /* @__PURE__ */ jsx13(Text12, { color: theme.primary, children: "\u2014 0 media items found" })
4826
- ] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
4691
+ ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
4827
4692
  /* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: "\u2713 profile downloaded! " }),
4828
4693
  /* @__PURE__ */ jsx13(Text12, { color: theme.primary, children: "saved to:" })
4829
4694
  ] }) }),
@@ -4870,8 +4735,8 @@ function ErrorView({ message, hint, columns, theme }) {
4870
4735
  }
4871
4736
 
4872
4737
  // src/components/views/settings-view.tsx
4873
- import { useState as useState6, useMemo as useMemo2, useCallback as useCallback2 } from "react";
4874
- import { Box as Box12, Text as Text14, useInput as useInput3 } from "ink";
4738
+ import { useState as useState6, useMemo as useMemo2, useCallback } from "react";
4739
+ import { Box as Box12, Text as Text14, useInput as useInput2 } from "ink";
4875
4740
  import fs11 from "fs";
4876
4741
  import path11 from "path";
4877
4742
  import os8 from "os";
@@ -4895,7 +4760,7 @@ function SettingsView({
4895
4760
  const initialConfig = useMemo2(() => loadConfig(), []);
4896
4761
  const [internalFocus, setInternalFocus] = useState6("cookies");
4897
4762
  const focusedField = controlledFocus ?? internalFocus;
4898
- const setField = useCallback2(
4763
+ const setField = useCallback(
4899
4764
  (field) => {
4900
4765
  setInternalFocus(field);
4901
4766
  onFocusChange?.(field);
@@ -4907,7 +4772,7 @@ function SettingsView({
4907
4772
  const [writeMetadata, setWriteMetadata] = useState6(Boolean(initialConfig.writeMetadata));
4908
4773
  const [defaultLimit, setDefaultLimit] = useState6(initialConfig.defaultLimit);
4909
4774
  const [saved, setSaved] = useState6(false);
4910
- const handleSave = useCallback2(() => {
4775
+ const handleSave = useCallback(() => {
4911
4776
  const existing = loadConfig();
4912
4777
  const nextConfig = {
4913
4778
  ...existing,
@@ -4955,20 +4820,20 @@ function SettingsView({
4955
4820
  }
4956
4821
  return `target: ${path11.resolve(expandDir(dir))}`;
4957
4822
  }, [downloadDir]);
4958
- const cycleFocus = useCallback2((forward = true) => {
4823
+ const cycleFocus = useCallback((forward = true) => {
4959
4824
  const fields = ["cookies", "download", "metadata", "limit"];
4960
4825
  const idx = fields.indexOf(focusedField);
4961
4826
  const nextIdx = forward ? (idx + 1) % fields.length : (idx - 1 + fields.length) % fields.length;
4962
4827
  setField(fields[nextIdx]);
4963
4828
  }, [focusedField, setField]);
4964
- const cycleLimit = useCallback2(() => {
4829
+ const cycleLimit = useCallback(() => {
4965
4830
  setDefaultLimit((prev) => {
4966
4831
  const idx = LIMIT_OPTIONS.indexOf(prev);
4967
4832
  const nextIdx = (idx + 1) % LIMIT_OPTIONS.length;
4968
4833
  return LIMIT_OPTIONS[nextIdx];
4969
4834
  });
4970
4835
  }, []);
4971
- useInput3((input, key) => {
4836
+ useInput2((input, key) => {
4972
4837
  if (key.escape) {
4973
4838
  onCancel();
4974
4839
  return;
@@ -5096,8 +4961,8 @@ function SettingsView({
5096
4961
  }
5097
4962
 
5098
4963
  // src/components/views/watcher-view.tsx
5099
- import { useState as useState7, useEffect as useEffect6, useRef as useRef3, useCallback as useCallback3 } from "react";
5100
- import { Box as Box13, Text as Text15, useInput as useInput4 } from "ink";
4964
+ import { useState as useState7, useEffect as useEffect6, useRef as useRef3, useCallback as useCallback2 } from "react";
4965
+ import { Box as Box13, Text as Text15, useInput as useInput3 } from "ink";
5101
4966
  import Spinner2 from "ink-spinner";
5102
4967
 
5103
4968
  // src/lib/clipboard.ts
@@ -5531,17 +5396,17 @@ function WatcherView({
5531
5396
  watcher.stop();
5532
5397
  };
5533
5398
  }, [outputDir]);
5534
- const cycleChoice = useCallback3(() => {
5399
+ const cycleChoice = useCallback2(() => {
5535
5400
  setMediaChoice((prev) => {
5536
5401
  const idx = CHOICES.indexOf(prev);
5537
5402
  const nextChoice = CHOICES[(idx + 1) % CHOICES.length] || "all";
5538
5403
  return nextChoice;
5539
5404
  });
5540
5405
  }, []);
5541
- const toggleMetadata = useCallback3(() => {
5406
+ const toggleMetadata = useCallback2(() => {
5542
5407
  setWriteMetadata((prev) => !prev);
5543
5408
  }, []);
5544
- const openFolder = useCallback3(() => {
5409
+ const openFolder = useCallback2(() => {
5545
5410
  const target = outputDir || resolveDefaultDownloadDir("instagram", "watcher");
5546
5411
  revealInFileManager(target);
5547
5412
  }, [outputDir]);
@@ -5576,7 +5441,7 @@ function WatcherView({
5576
5441
  ];
5577
5442
  onRegisterClickTargets(targets);
5578
5443
  }, [onRegisterClickTargets, cycleChoice, toggleMetadata, openFolder, onBack, mediaChoice, writeMetadata]);
5579
- useInput4((input, key) => {
5444
+ useInput3((input, key) => {
5580
5445
  if (key.escape) {
5581
5446
  onBack();
5582
5447
  return;
@@ -5711,8 +5576,8 @@ function WatcherView({
5711
5576
  }
5712
5577
 
5713
5578
  // src/components/views/batch-view.tsx
5714
- import { useState as useState8, useRef as useRef4, useCallback as useCallback4, useEffect as useEffect7 } from "react";
5715
- import { Box as Box14, Text as Text16, useInput as useInput5 } from "ink";
5579
+ import { useState as useState8, useRef as useRef4, useCallback as useCallback3, useEffect as useEffect7 } from "react";
5580
+ import { Box as Box14, Text as Text16, useInput as useInput4 } from "ink";
5716
5581
  import Spinner3 from "ink-spinner";
5717
5582
  import fs13 from "fs";
5718
5583
 
@@ -5836,11 +5701,11 @@ async function runBatchQueue(options) {
5836
5701
  }
5837
5702
 
5838
5703
  // src/components/views/batch-view.tsx
5839
- import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
5704
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
5840
5705
  var PLATFORMS = ["instagram", "tiktok", "facebook", "x"];
5841
5706
  var CHOICES2 = ["all", "photos", "videos", "stories", "highlights"];
5842
- var DATE_PRESETS2 = ["all", "24h", "7d", "30d", "2024", "2023", "2022", "2021", "custom"];
5843
- var LIMIT_PRESETS2 = [void 0, 10, 24, 50, 100, "custom"];
5707
+ var DATE_PRESETS = ["all", "24h", "7d", "30d", "2024", "2023", "2022", "2021", "custom"];
5708
+ var LIMIT_PRESETS = [void 0, 10, 24, 50, 100, "custom"];
5844
5709
  function BatchView({
5845
5710
  onBack,
5846
5711
  outputDir,
@@ -5872,22 +5737,22 @@ function BatchView({
5872
5737
  const [isCooldown, setIsCooldown] = useState8(false);
5873
5738
  const [result, setResult] = useState8(null);
5874
5739
  const abortControllerRef = useRef4(null);
5875
- const cyclePlatform = useCallback4(() => {
5740
+ const cyclePlatform = useCallback3(() => {
5876
5741
  setDefaultPlatform((prev) => {
5877
5742
  const idx = PLATFORMS.indexOf(prev);
5878
5743
  return PLATFORMS[(idx + 1) % PLATFORMS.length];
5879
5744
  });
5880
5745
  }, []);
5881
- const cycleChoice = useCallback4(() => {
5746
+ const cycleChoice = useCallback3(() => {
5882
5747
  setChoice((prev) => {
5883
5748
  const idx = CHOICES2.indexOf(prev);
5884
5749
  return CHOICES2[(idx + 1) % CHOICES2.length];
5885
5750
  });
5886
5751
  }, []);
5887
- const cycleLimit = useCallback4(() => {
5888
- const curIdx = LIMIT_PRESETS2.findIndex((p) => p === limit);
5889
- const nextIdx = (curIdx + 1) % LIMIT_PRESETS2.length;
5890
- const nextVal = LIMIT_PRESETS2[nextIdx];
5752
+ const cycleLimit = useCallback3(() => {
5753
+ const curIdx = LIMIT_PRESETS.findIndex((p) => p === limit);
5754
+ const nextIdx = (curIdx + 1) % LIMIT_PRESETS.length;
5755
+ const nextVal = LIMIT_PRESETS[nextIdx];
5891
5756
  if (nextVal === "custom") {
5892
5757
  setCustomLimitText(limit ? String(limit) : "24");
5893
5758
  setFocusedControl("custom-limit");
@@ -5895,10 +5760,10 @@ function BatchView({
5895
5760
  setLimit(nextVal);
5896
5761
  }
5897
5762
  }, [limit]);
5898
- const cycleDate = useCallback4(() => {
5899
- const curIdx = DATE_PRESETS2.indexOf(dateFilter);
5900
- const nextIdx = (curIdx + 1) % DATE_PRESETS2.length;
5901
- const nextVal = DATE_PRESETS2[nextIdx];
5763
+ const cycleDate = useCallback3(() => {
5764
+ const curIdx = DATE_PRESETS.indexOf(dateFilter);
5765
+ const nextIdx = (curIdx + 1) % DATE_PRESETS.length;
5766
+ const nextVal = DATE_PRESETS[nextIdx];
5902
5767
  if (nextVal === "custom") {
5903
5768
  setCustomDateText(dateFilter !== "all" ? dateFilter : "2024");
5904
5769
  setFocusedControl("custom-date");
@@ -5906,13 +5771,13 @@ function BatchView({
5906
5771
  setDateFilter(nextVal);
5907
5772
  }
5908
5773
  }, [dateFilter]);
5909
- const toggleMetadata = useCallback4(() => {
5774
+ const toggleMetadata = useCallback3(() => {
5910
5775
  setWriteMetadata((prev) => !prev);
5911
5776
  }, []);
5912
- const toggleResume = useCallback4(() => {
5777
+ const toggleResume = useCallback3(() => {
5913
5778
  setResume((prev) => !prev);
5914
5779
  }, []);
5915
- const startQueue = useCallback4(async () => {
5780
+ const startQueue = useCallback3(async () => {
5916
5781
  setErrorMessage(null);
5917
5782
  let lines = [];
5918
5783
  if (inputField === "file") {
@@ -5999,7 +5864,7 @@ function BatchView({
5999
5864
  setPhase("input");
6000
5865
  }
6001
5866
  }, [inputField, filePath, rawText, choice, defaultPlatform, outputDir, dateFilter, limit, resume, writeMetadata]);
6002
- useInput5((input, key) => {
5867
+ useInput4((input, key) => {
6003
5868
  if (key.ctrl && (input === "c" || input === "C")) {
6004
5869
  return;
6005
5870
  }
@@ -6543,7 +6408,7 @@ function BatchView({
6543
6408
  }
6544
6409
  ) }),
6545
6410
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
6546
- ] }) : /* @__PURE__ */ jsxs14(Fragment4, { children: [
6411
+ ] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
6547
6412
  /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", width: "100%", children: [
6548
6413
  /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6549
6414
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[p] Platform: " }),
@@ -6755,7 +6620,7 @@ function frameRowSpan(row) {
6755
6620
  }
6756
6621
 
6757
6622
  // src/app.tsx
6758
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
6623
+ import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
6759
6624
  var TAGLINE = "grab any profile. paste. download. done.";
6760
6625
  var DOWNLOAD_BUTTON = "download";
6761
6626
  var Gap = ({ lines = 1 }) => /* @__PURE__ */ jsx18(Box15, { flexDirection: "column", flexShrink: 0, children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx18(Text17, { children: " " }, i)) });
@@ -6834,7 +6699,7 @@ function App({
6834
6699
  version = "1.0.0"
6835
6700
  }) {
6836
6701
  const [themeMode, setThemeMode] = useState9(initialThemeMode);
6837
- const cycleTheme = useCallback5(() => {
6702
+ const cycleTheme = useCallback4(() => {
6838
6703
  setThemeMode(nextThemeMode);
6839
6704
  }, []);
6840
6705
  return /* @__PURE__ */ jsx18(ThemeProvider, { mode: themeMode, children: /* @__PURE__ */ jsx18(
@@ -6912,7 +6777,7 @@ function InnerApp({
6912
6777
  }
6913
6778
  };
6914
6779
  }, []);
6915
- const resetToInput = useCallback5(() => {
6780
+ const resetToInput = useCallback4(() => {
6916
6781
  if (probingTimeoutRef.current) {
6917
6782
  clearTimeout(probingTimeoutRef.current);
6918
6783
  probingTimeoutRef.current = null;
@@ -6922,7 +6787,7 @@ function InnerApp({
6922
6787
  setUrlInput("");
6923
6788
  setPhase({ name: "input" });
6924
6789
  }, []);
6925
- const handleModeSelect = useCallback5(async (choiceOrOptions, profile) => {
6790
+ const handleModeSelect = useCallback4(async (choiceOrOptions, profile) => {
6926
6791
  abortControllerRef.current?.abort();
6927
6792
  const controller = new AbortController();
6928
6793
  abortControllerRef.current = controller;
@@ -6972,7 +6837,7 @@ function InnerApp({
6972
6837
  });
6973
6838
  }
6974
6839
  }, [outputDir, since, until, effectiveLimit, resume, effectiveWriteMetadata]);
6975
- const handlePlatformSelect = useCallback5((platform, username, rawInput) => {
6840
+ const handlePlatformSelect = useCallback4((platform, username, rawInput) => {
6976
6841
  const canonical = buildCanonicalProfile(platform, username, rawInput);
6977
6842
  setPhase({ name: "probing", target: canonical.cleanUrl, status: "probing profile\u2026" });
6978
6843
  if (probingTimeoutRef.current) clearTimeout(probingTimeoutRef.current);
@@ -6985,7 +6850,7 @@ function InnerApp({
6985
6850
  }
6986
6851
  }, 450);
6987
6852
  }, [initialChoice, handleModeSelect]);
6988
- const handleInputSubmit = useCallback5((input) => {
6853
+ const handleInputSubmit = useCallback4((input) => {
6989
6854
  const trimmed = input.trim();
6990
6855
  if (!trimmed) return;
6991
6856
  const parsed = parseProfileInput(trimmed);
@@ -7011,7 +6876,7 @@ function InnerApp({
7011
6876
  warning: "paste a full profile url (e.g. instagram.com/profile) or @profile"
7012
6877
  });
7013
6878
  }, [initialChoice, handleModeSelect]);
7014
- useInput6((input, key) => {
6879
+ useInput5((input, key) => {
7015
6880
  if (key.ctrl && input === "t") {
7016
6881
  cycleTheme();
7017
6882
  return;
@@ -7319,7 +7184,7 @@ function InnerApp({
7319
7184
  onRegisterClickTargets: setBatchClickTargets
7320
7185
  }
7321
7186
  ),
7322
- hints.length > 0 ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
7187
+ hints.length > 0 ? /* @__PURE__ */ jsxs15(Fragment4, { children: [
7323
7188
  /* @__PURE__ */ jsx18(Gap, { lines: 2 }),
7324
7189
  /* @__PURE__ */ jsx18(
7325
7190
  Shortcuts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-nami",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {