@treeviz/gedcom-parser 1.0.23 → 2.0.0

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/index.js CHANGED
@@ -6,6 +6,7 @@ import { Command } from 'commander';
6
6
  import { debounce, set, get, uniqBy, unset, difference, intersectionBy, differenceBy } from 'lodash-es';
7
7
  import { format, isValid, parse } from 'date-fns';
8
8
  import chalk from 'chalk';
9
+ import * as readline from 'readline';
9
10
 
10
11
  var __defProp = Object.defineProperty;
11
12
  var __export = (target, all) => {
@@ -786,11 +787,30 @@ var Common = class _Common {
786
787
  const sour = get(head, "SOUR.value");
787
788
  return !!sour?.toLowerCase()?.startsWith("myheritage");
788
789
  }
790
+ /**
791
+ * Get the source type as a string (for prefixing tree IDs and names)
792
+ * Returns the detected source type or undefined if unknown
793
+ */
794
+ getSourceType() {
795
+ if (this.isAncestry()) return "Ancestry";
796
+ if (this.isMyHeritage()) return "MyHeritage";
797
+ if (this.isFamilySearch()) return "FamilySearch";
798
+ if (this.isGNO2GED()) return "GNO2GED";
799
+ if (this.isGenoPro()) return "GenoPro";
800
+ if (this.isAhnenblatt()) return "Ahnenblatt";
801
+ if (this.isGeni()) return "Geni";
802
+ return void 0;
803
+ }
789
804
  isFamilySearch() {
790
805
  const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
791
806
  const sourName = get(head, "SOUR.NAME.value");
792
807
  return sourName === "FamilySearch API";
793
808
  }
809
+ isGNO2GED() {
810
+ const head = get(this, "HEAD") || get(this.getGedcom(), "HEAD");
811
+ const sour = get(head, "SOUR.value");
812
+ return sour === "GNO2GED";
813
+ }
794
814
  getAncestryTreeId() {
795
815
  const path = "HEAD.SOUR._TREE.RIN.value";
796
816
  return get(this, path) || get(this.getGedcom(), path);
@@ -801,11 +821,34 @@ var Common = class _Common {
801
821
  }
802
822
  getTreeId() {
803
823
  if (this?.isAncestry()) {
804
- return this.getAncestryTreeId();
824
+ const id = this.getAncestryTreeId();
825
+ if (id !== void 0) return id;
805
826
  }
806
827
  if (this?.isMyHeritage()) {
807
- return this.getMyHeritageTreeId();
828
+ const id = this.getMyHeritageTreeId();
829
+ if (id !== void 0) return id;
830
+ }
831
+ if (this?.isFamilySearch()) {
832
+ const id = this.getFamilySearchTreeId();
833
+ if (id !== void 0) return id;
834
+ }
835
+ if (this?.isGNO2GED()) {
836
+ const id = this.getGNO2GEDTreeId();
837
+ if (id !== void 0) return id;
838
+ }
839
+ if (this?.isAhnenblatt()) {
840
+ const id = this.getAhnenblattTreeId();
841
+ if (id !== void 0) return id;
842
+ }
843
+ if (this?.isGeni()) {
844
+ const id = this.getGeniTreeId();
845
+ if (id !== void 0) return id;
808
846
  }
847
+ if (this?.isGenoPro()) {
848
+ const id = this.getGenoProTreeId();
849
+ if (id !== void 0) return id;
850
+ }
851
+ return this.getUniversalTreeId();
809
852
  }
810
853
  getAncestryTreeName() {
811
854
  const path = "HEAD.SOUR._TREE.value";
@@ -818,13 +861,146 @@ var Common = class _Common {
818
861
  /Exported by MyHeritage.com from (?<tree>.+) in.+$/
819
862
  )?.groups?.tree;
820
863
  }
864
+ getFamilySearchTreeId() {
865
+ const rin = get(this, "HEAD.SOUR._TREE.RIN.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.RIN.value");
866
+ if (rin) {
867
+ return rin;
868
+ }
869
+ return "familysearch";
870
+ }
871
+ getFamilySearchTreeName() {
872
+ const treeName = get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value");
873
+ if (treeName) {
874
+ return treeName;
875
+ }
876
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
877
+ return fileName || "FamilySearch Import";
878
+ }
879
+ getAhnenblattTreeId() {
880
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
881
+ if (fileName) {
882
+ const idMatch = fileName.match(/_(\d+)/);
883
+ if (idMatch) {
884
+ return idMatch[1];
885
+ }
886
+ }
887
+ return void 0;
888
+ }
889
+ getAhnenblattTreeName() {
890
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
891
+ return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
892
+ }
893
+ getGeniTreeId() {
894
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
895
+ if (fileName) {
896
+ const idMatch = fileName.match(/_(\d+)/);
897
+ if (idMatch) {
898
+ return idMatch[1];
899
+ }
900
+ }
901
+ return void 0;
902
+ }
903
+ getGeniTreeName() {
904
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
905
+ return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
906
+ }
907
+ getGenoProTreeId() {
908
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
909
+ if (fileName) {
910
+ const idMatch = fileName.match(/_(\d+)/);
911
+ if (idMatch) {
912
+ return idMatch[1];
913
+ }
914
+ }
915
+ return void 0;
916
+ }
917
+ getGenoProTreeName() {
918
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
919
+ return fileName?.replace(/\.ged$/i, "").replace(/_/g, " ");
920
+ }
921
+ getGNO2GEDTreeId() {
922
+ const rin = get(this, "HEAD._TREE.RIN.value") || get(this.getGedcom(), "HEAD._TREE.RIN.value");
923
+ if (rin) {
924
+ return rin;
925
+ }
926
+ return `gno_${this._gedcom?.refcount || "unknown"}`;
927
+ }
928
+ getGNO2GEDTreeName() {
929
+ const treeName = get(this, "HEAD._TREE.value") || get(this.getGedcom(), "HEAD._TREE.value");
930
+ if (treeName) {
931
+ return treeName;
932
+ }
933
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
934
+ return fileName || "GNO2GED Export";
935
+ }
936
+ /**
937
+ * Universal tree ID getter for unknown/unrecognized GEDCOM sources
938
+ * Tries to extract an ID from various common locations
939
+ */
940
+ getUniversalTreeId() {
941
+ const sourceType = this.getSourceType();
942
+ const prefix = sourceType ? sourceType.toLowerCase() : "tree";
943
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
944
+ if (fileName) {
945
+ const idMatch = fileName.match(/_(\d+)/);
946
+ if (idMatch) {
947
+ return `${prefix}_${idMatch[1]}`;
948
+ }
949
+ }
950
+ return `${prefix}_${this._gedcom?.refcount || "unknown"}`;
951
+ }
952
+ /**
953
+ * Universal tree name getter for unknown/unrecognized GEDCOM sources
954
+ * Tries to extract a name from various common locations
955
+ */
956
+ getUniversalTreeName() {
957
+ const sourceType = this.getSourceType();
958
+ const prefix = sourceType ? `${sourceType}-` : "";
959
+ const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
960
+ if (fileName) {
961
+ const cleanName = fileName.replace(/\.ged$/i, "").replace(/_/g, " ");
962
+ return `${prefix}${cleanName}`;
963
+ }
964
+ const sourName = get(this, "HEAD.SOUR.NAME.value") || get(this.getGedcom(), "HEAD.SOUR.NAME.value");
965
+ if (sourName) {
966
+ return `${prefix}${sourName}`;
967
+ }
968
+ const sourValue = get(this, "HEAD.SOUR.value") || get(this.getGedcom(), "HEAD.SOUR.value");
969
+ if (sourValue) {
970
+ return `${prefix}${sourValue}`;
971
+ }
972
+ return `${prefix}Unknown Tree`;
973
+ }
821
974
  getTreeName() {
822
975
  if (this?.isAncestry()) {
823
- return this.getAncestryTreeName();
976
+ const name = this.getAncestryTreeName();
977
+ if (name) return name;
824
978
  }
825
979
  if (this?.isMyHeritage()) {
826
- return this.getMyHeritageTreeName();
980
+ const name = this.getMyHeritageTreeName();
981
+ if (name) return name;
982
+ }
983
+ if (this?.isFamilySearch()) {
984
+ const name = this.getFamilySearchTreeName();
985
+ if (name) return name;
986
+ }
987
+ if (this?.isGNO2GED()) {
988
+ const name = this.getGNO2GEDTreeName();
989
+ if (name) return name;
990
+ }
991
+ if (this?.isAhnenblatt()) {
992
+ const name = this.getAhnenblattTreeName();
993
+ if (name) return name;
994
+ }
995
+ if (this?.isGeni()) {
996
+ const name = this.getGeniTreeName();
997
+ if (name) return name;
827
998
  }
999
+ if (this?.isGenoPro()) {
1000
+ const name = this.getGenoProTreeName();
1001
+ if (name) return name;
1002
+ }
1003
+ return this.getUniversalTreeName();
828
1004
  }
829
1005
  };
830
1006
  var createProxy = (target) => {
@@ -962,66 +1138,125 @@ var PARTNER = {
962
1138
  var SPOUSE = {
963
1139
  PART: "spouse" /* SPOUSE */
964
1140
  };
1141
+ var getGedcomId = (gedcom) => {
1142
+ if (!gedcom) {
1143
+ return "unknown";
1144
+ }
1145
+ const treeId = gedcom.getTreeId?.() || "";
1146
+ const treeName = gedcom.getTreeName?.() || "";
1147
+ const sanitizedName = treeName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1148
+ if (treeId && sanitizedName) {
1149
+ return `${treeId}_${sanitizedName}`;
1150
+ } else if (treeId) {
1151
+ return treeId;
1152
+ } else if (sanitizedName) {
1153
+ return sanitizedName;
1154
+ }
1155
+ return `gedcom_${gedcom.refcount}`;
1156
+ };
965
1157
  var caches = {
966
1158
  pathCache: {},
967
1159
  relativesOnDegreeCache: {},
968
- relativesOnLevelCache: {}
1160
+ relativesOnLevelCache: {},
1161
+ profilePictureCache: {}
969
1162
  };
970
- var getInstance = getCacheManagerFactory();
971
- var cacheDbs = {
972
- pathCache: getInstance("ftv", "Main", "path", true),
973
- relativesOnDegreeCache: getInstance(
974
- "ftv",
975
- "Main",
976
- "path",
977
- true
978
- ),
979
- relativesOnLevelCache: getInstance(
980
- "ftv",
981
- "Main",
982
- "path",
983
- true
984
- )
1163
+ var cacheDbs;
1164
+ var getCacheDbs = () => {
1165
+ if (!cacheDbs) {
1166
+ const getInstance = getCacheManagerFactory();
1167
+ cacheDbs = {
1168
+ pathCache: getInstance(
1169
+ "ftv",
1170
+ "Main",
1171
+ "path",
1172
+ true
1173
+ ),
1174
+ relativesOnDegreeCache: getInstance("ftv", "Main", "path", true),
1175
+ relativesOnLevelCache: getInstance(
1176
+ "ftv",
1177
+ "Main",
1178
+ "path",
1179
+ true
1180
+ ),
1181
+ profilePictureCache: getInstance(
1182
+ "ftv",
1183
+ "Main",
1184
+ "images",
1185
+ false
1186
+ )
1187
+ };
1188
+ }
1189
+ return cacheDbs;
985
1190
  };
986
- ({
1191
+ var storeCache = {
1192
+ // NOTE: pathCache, relativesOnLevelCache, and relativesOnDegreeCache are intentionally
1193
+ // kept in memory only. These debounced functions exist to satisfy the type system
1194
+ // but are never called.
987
1195
  pathCache: debounce((value) => {
988
1196
  if (value) {
989
- cacheDbs.pathCache.setItem(value);
1197
+ getCacheDbs().pathCache.setItem(value);
990
1198
  }
991
1199
  }, 50),
992
1200
  relativesOnLevelCache: debounce((value) => {
993
1201
  if (value) {
994
- cacheDbs.relativesOnLevelCache.setItem(value);
1202
+ getCacheDbs().relativesOnLevelCache.setItem(value);
995
1203
  }
996
1204
  }, 50),
997
1205
  relativesOnDegreeCache: debounce((value) => {
998
1206
  if (value) {
999
- cacheDbs.relativesOnDegreeCache.setItem(value);
1207
+ getCacheDbs().relativesOnDegreeCache.setItem(value);
1000
1208
  }
1001
- }, 50)
1002
- });
1003
- var relativesCache = (cacheKey) => (key, subKey, value) => {
1004
- if (!caches[cacheKey]) {
1209
+ }, 50),
1210
+ // profilePictureCache IS persisted to IndexedDB
1211
+ profilePictureCache: debounce((value) => {
1212
+ if (value) {
1213
+ getCacheDbs().profilePictureCache.setItem(value);
1214
+ }
1215
+ }, 100)
1216
+ };
1217
+ var relativesCache = (cacheKey) => (gedcom, key, subKey, value) => {
1218
+ const gedcomId = getGedcomId(gedcom);
1219
+ const fullKey = `${gedcomId}:${key}`;
1220
+ const cache = caches[cacheKey];
1221
+ if (!cache) {
1005
1222
  caches[cacheKey] = {};
1006
1223
  }
1007
- if (value && caches[cacheKey]) {
1008
- if (!caches[cacheKey][key]) {
1009
- caches[cacheKey][key] = {};
1224
+ if (value) {
1225
+ const typedCache2 = caches[cacheKey];
1226
+ if (!typedCache2[fullKey]) {
1227
+ typedCache2[fullKey] = {};
1010
1228
  }
1011
- caches[cacheKey][key][subKey] = value;
1012
- return caches[cacheKey][key][subKey];
1229
+ typedCache2[fullKey][subKey] = value;
1230
+ return typedCache2[fullKey][subKey];
1013
1231
  }
1014
- return caches[cacheKey]?.[key]?.[subKey];
1232
+ const typedCache = caches[cacheKey];
1233
+ return typedCache?.[fullKey]?.[subKey];
1015
1234
  };
1016
- var pathCache = (key, value) => {
1235
+ var pathCache = (gedcom, key, value) => {
1236
+ const gedcomId = getGedcomId(gedcom);
1237
+ const fullKey = `${gedcomId}:${key}`;
1017
1238
  if (!caches.pathCache) {
1018
1239
  caches.pathCache = {};
1019
1240
  }
1020
1241
  if (value && caches.pathCache) {
1021
- caches.pathCache[key] = value;
1022
- return caches.pathCache[key];
1242
+ caches.pathCache[fullKey] = value;
1243
+ return caches.pathCache[fullKey];
1023
1244
  }
1024
- return caches.pathCache?.[key];
1245
+ return caches.pathCache?.[fullKey];
1246
+ };
1247
+ var profilePictureCache = (gedcom, key, value) => {
1248
+ const gedcomId = getGedcomId(gedcom);
1249
+ const fullKey = `${gedcomId}:${key}`;
1250
+ if (!caches.profilePictureCache) {
1251
+ caches.profilePictureCache = {};
1252
+ }
1253
+ if (value && caches.profilePictureCache) {
1254
+ caches.profilePictureCache[fullKey] = value;
1255
+ storeCache.profilePictureCache(caches.profilePictureCache);
1256
+ return caches.profilePictureCache[fullKey];
1257
+ }
1258
+ const cached = caches.profilePictureCache?.[fullKey];
1259
+ return cached;
1025
1260
  };
1026
1261
 
1027
1262
  // src/utils/get-all-prop.ts
@@ -1927,7 +2162,7 @@ var Indi = class extends Common {
1927
2162
  }
1928
2163
  async ancestryMedia(namespace) {
1929
2164
  const list = {};
1930
- const objeList = this.get("OBJE")?.toList();
2165
+ const objeList = this.get("OBJE")?.toList().copy();
1931
2166
  const www = this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
1932
2167
  const tree = this.getAncestryTreeId();
1933
2168
  if (objeList) {
@@ -2001,11 +2236,12 @@ var Indi = class extends Common {
2001
2236
  if (!tree) {
2002
2237
  return;
2003
2238
  }
2004
- const objeList = this.get("OBJE")?.toList();
2005
- const birthObj = this.get("BIRT.OBJE")?.toList();
2006
- const deathObj = this.get("DEAT.OBJE")?.toList();
2239
+ const objeList = this.get("OBJE")?.toList().copy();
2240
+ const birthObj = this.get("BIRT.OBJE")?.toList().copy();
2241
+ const deathObj = this.get("DEAT.OBJE")?.toList().copy();
2242
+ objeList?.merge(birthObj).merge(deathObj);
2007
2243
  (this.get("FAMS")?.toValueList().values() ?? []).concat(this.get("FAMC")?.toValueList().values() ?? []).forEach((fam) => {
2008
- objeList?.merge(birthObj).merge(deathObj).merge(fam?.get("MARR.OBJE"));
2244
+ objeList.merge(fam?.get("MARR.OBJE"));
2009
2245
  });
2010
2246
  objeList?.forEach((o, index) => {
2011
2247
  if (!o) {
@@ -2123,6 +2359,88 @@ var Indi = class extends Common {
2123
2359
  };
2124
2360
  });
2125
2361
  }
2362
+ geniMedia() {
2363
+ const list = {};
2364
+ const objeList = this.get("OBJE")?.toList().copy();
2365
+ const sourList = this.get("SOUR")?.toList().copy();
2366
+ sourList?.forEach((sour) => {
2367
+ const sourObje = sour?.get("OBJE")?.toList();
2368
+ objeList.merge(sourObje);
2369
+ });
2370
+ if (!objeList || objeList.length === 0) {
2371
+ return void 0;
2372
+ }
2373
+ const rfn = this.get("RFN")?.toValue();
2374
+ const geniId = rfn?.replace(/^geni:/, "") || "unknown";
2375
+ objeList.forEach((obje, index) => {
2376
+ if (!obje) {
2377
+ return;
2378
+ }
2379
+ const key = `@O${index}@`;
2380
+ const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
2381
+ const url = obje?.get("FILE")?.toValue();
2382
+ const title = obje?.get("TITL")?.toValue() ?? "";
2383
+ const type = obje?.get("FORM")?.toValue() ?? "raw";
2384
+ if (url) {
2385
+ const urlMatch = url.match(/\/([^/]+)\?hash=/);
2386
+ const imgId = urlMatch?.[1] || `img-${index}-${Date.now().toString(36)}`;
2387
+ const id = `geni-${geniId}-${imgId}`;
2388
+ list[id] = {
2389
+ isPrimary,
2390
+ key,
2391
+ id,
2392
+ tree: geniId,
2393
+ imgId,
2394
+ person: this.id,
2395
+ title,
2396
+ url,
2397
+ contentType: type,
2398
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2399
+ };
2400
+ }
2401
+ });
2402
+ return list;
2403
+ }
2404
+ universalMedia() {
2405
+ const list = {};
2406
+ if (!this.id) {
2407
+ return list;
2408
+ }
2409
+ const objeList = this.get("OBJE")?.toList().copy();
2410
+ if (!objeList || objeList.length === 0) {
2411
+ return list;
2412
+ }
2413
+ const rfn = this.get("RFN")?.toValue();
2414
+ const treeId = rfn || "universal";
2415
+ objeList.forEach((obje, index) => {
2416
+ if (!obje) {
2417
+ return;
2418
+ }
2419
+ const key = `@O${index}@`;
2420
+ obje.standardizeMedia();
2421
+ const isPrimary = obje?.get("_PRIM")?.toValue() === "Y";
2422
+ const url = obje?.get("FILE")?.toValue();
2423
+ const title = obje?.get("TITL")?.toValue() ?? "";
2424
+ const type = obje?.get("FORM")?.toValue() ?? "raw";
2425
+ if (url) {
2426
+ const imgId = `media-${index}-${url.split("/").pop()?.split("?")[0]?.substring(0, 20) || Date.now().toString(36)}`;
2427
+ const id = `${treeId}-${this.id}-${imgId}`;
2428
+ list[id] = {
2429
+ isPrimary,
2430
+ key,
2431
+ id,
2432
+ tree: treeId,
2433
+ imgId,
2434
+ person: this.id,
2435
+ title,
2436
+ url,
2437
+ contentType: type,
2438
+ downloadName: `${this.id.replaceAll("@", "")}_${this.toNaturalName()?.replaceAll(" ", "-") || ""}_${(title || key.replaceAll("@", "").toString()).replaceAll(" ", "-")}`
2439
+ };
2440
+ }
2441
+ });
2442
+ return list;
2443
+ }
2126
2444
  async multimedia(namespace) {
2127
2445
  if (this?.isAncestry()) {
2128
2446
  return await this.ancestryMedia(namespace);
@@ -2130,11 +2448,30 @@ var Indi = class extends Common {
2130
2448
  if (this?.isMyHeritage()) {
2131
2449
  return this.myheritageMedia();
2132
2450
  }
2133
- return void 0;
2451
+ if (this?.isGeni()) {
2452
+ return this.geniMedia();
2453
+ }
2454
+ return this.universalMedia();
2134
2455
  }
2135
2456
  async getProfilePicture(namespace, onlyPrimary = true) {
2457
+ if (!this.id) {
2458
+ return void 0;
2459
+ }
2460
+ const cacheKey = this.id;
2461
+ const cached = profilePictureCache(
2462
+ this._gedcom,
2463
+ cacheKey
2464
+ );
2465
+ if (cached !== void 0) {
2466
+ return cached;
2467
+ }
2136
2468
  const mediaList = await this.multimedia(namespace);
2137
2469
  if (!mediaList) {
2470
+ profilePictureCache(
2471
+ this._gedcom,
2472
+ cacheKey,
2473
+ void 0
2474
+ );
2138
2475
  return void 0;
2139
2476
  }
2140
2477
  const mediaArray = Object.values(mediaList);
@@ -2142,27 +2479,41 @@ var Indi = class extends Common {
2142
2479
  (media) => media.isPrimary && isImageFormat(media.contentType || getFileExtension(media.url))
2143
2480
  );
2144
2481
  if (primaryMedia) {
2145
- return {
2482
+ const result = {
2146
2483
  file: primaryMedia.url,
2147
2484
  form: primaryMedia.contentType,
2148
2485
  title: primaryMedia.title,
2149
2486
  isPrimary: true
2150
2487
  };
2151
- }
2152
- if (!onlyPrimary) {
2488
+ profilePictureCache(this._gedcom, cacheKey, result);
2489
+ return result;
2490
+ }
2491
+ if (onlyPrimary) {
2492
+ profilePictureCache(
2493
+ this._gedcom,
2494
+ cacheKey,
2495
+ void 0
2496
+ );
2153
2497
  return void 0;
2154
2498
  }
2155
2499
  const secondaryMedia = mediaArray.find(
2156
2500
  (media) => isImageFormat(media.contentType || getFileExtension(media.url))
2157
2501
  );
2158
2502
  if (secondaryMedia) {
2159
- return {
2503
+ const result = {
2160
2504
  file: secondaryMedia.url,
2161
2505
  form: secondaryMedia.contentType,
2162
2506
  title: secondaryMedia.title,
2163
2507
  isPrimary: false
2164
2508
  };
2509
+ profilePictureCache(this._gedcom, cacheKey, result);
2510
+ return result;
2165
2511
  }
2512
+ profilePictureCache(
2513
+ this._gedcom,
2514
+ cacheKey,
2515
+ void 0
2516
+ );
2166
2517
  return void 0;
2167
2518
  }
2168
2519
  link(poolId) {
@@ -2530,7 +2881,7 @@ var Indi = class extends Common {
2530
2881
  return;
2531
2882
  }
2532
2883
  const cacheKey = `${this.id}|${usedIndi.id}`;
2533
- const cache = pathCache(cacheKey);
2884
+ const cache = pathCache(this._gedcom, cacheKey);
2534
2885
  if (cache) {
2535
2886
  return cache;
2536
2887
  }
@@ -2575,7 +2926,7 @@ var Indi = class extends Common {
2575
2926
  if (breakOnNext) {
2576
2927
  return void 0;
2577
2928
  }
2578
- pathCache(cacheKey, path2);
2929
+ pathCache(this._gedcom, cacheKey, path2);
2579
2930
  return path2;
2580
2931
  }
2581
2932
  visited.append(indi);
@@ -2745,7 +3096,7 @@ var Indi = class extends Common {
2745
3096
  }
2746
3097
  getRelativesOnDegree(degree = 0) {
2747
3098
  this.id = this.id || `@I${Math.random()}@`;
2748
- const cache = relativesOnDegreeCache(this.id, degree);
3099
+ const cache = relativesOnDegreeCache(this._gedcom, this.id, degree);
2749
3100
  if (cache) {
2750
3101
  return cache;
2751
3102
  }
@@ -2753,6 +3104,7 @@ var Indi = class extends Common {
2753
3104
  const excludes = persons;
2754
3105
  if (!Math.abs(degree)) {
2755
3106
  return relativesOnDegreeCache(
3107
+ this._gedcom,
2756
3108
  this.id,
2757
3109
  degree,
2758
3110
  persons.except(this)
@@ -2763,11 +3115,11 @@ var Indi = class extends Common {
2763
3115
  excludes.merge(persons);
2764
3116
  persons = this.getRelativesOnLevel(validDegree).getRelativesOnDegree(-validDegree).copy().exclude(excludes);
2765
3117
  }
2766
- return relativesOnDegreeCache(this.id, degree, persons);
3118
+ return relativesOnDegreeCache(this._gedcom, this.id, degree, persons);
2767
3119
  }
2768
3120
  getRelativesOnLevel(level = 0, filter) {
2769
3121
  this.id = this.id || `@I${Math.random()}@`;
2770
- const cache = relativesOnLevelCache(this.id, level);
3122
+ const cache = relativesOnLevelCache(this._gedcom, this.id, level);
2771
3123
  if (cache) {
2772
3124
  return cache;
2773
3125
  }
@@ -2778,7 +3130,7 @@ var Indi = class extends Common {
2778
3130
  };
2779
3131
  let families = this.get(config.key)?.toValueList();
2780
3132
  if (!families) {
2781
- return relativesOnLevelCache(this.id, level, persons);
3133
+ return relativesOnLevelCache(this._gedcom, this.id, level, persons);
2782
3134
  }
2783
3135
  if (filter) {
2784
3136
  families = families.filter(filter);
@@ -2789,7 +3141,12 @@ var Indi = class extends Common {
2789
3141
  persons = this.toFamilies(families).getParents();
2790
3142
  }
2791
3143
  if (level >= -1 && level <= 1) {
2792
- return relativesOnLevelCache(this.id, level, persons.except(this));
3144
+ return relativesOnLevelCache(
3145
+ this._gedcom,
3146
+ this.id,
3147
+ level,
3148
+ persons.except(this)
3149
+ );
2793
3150
  }
2794
3151
  for (let i = 1; i < Math.abs(level); i++) {
2795
3152
  if (config.isAscendant) {
@@ -2798,7 +3155,12 @@ var Indi = class extends Common {
2798
3155
  persons = persons.getParents();
2799
3156
  }
2800
3157
  }
2801
- return relativesOnLevelCache(this.id, level, persons.except(this));
3158
+ return relativesOnLevelCache(
3159
+ this._gedcom,
3160
+ this.id,
3161
+ level,
3162
+ persons.except(this)
3163
+ );
2802
3164
  }
2803
3165
  getAscendants(level = 0, filter) {
2804
3166
  if (!level) {
@@ -2837,7 +3199,12 @@ var Indi = class extends Common {
2837
3199
  }
2838
3200
  currentGen++;
2839
3201
  generations[currentGen] = descentants;
2840
- relativesOnLevelCache(this.id, -currentGen, descentants);
3202
+ relativesOnLevelCache(
3203
+ this._gedcom,
3204
+ this.id,
3205
+ -currentGen,
3206
+ descentants
3207
+ );
2841
3208
  descentants && relatives.merge(descentants);
2842
3209
  }
2843
3210
  return { relatives, generations };
@@ -2870,7 +3237,7 @@ var Indi = class extends Common {
2870
3237
  }
2871
3238
  currentGen++;
2872
3239
  generations[currentGen] = parents;
2873
- relativesOnLevelCache(this.id, currentGen, parents);
3240
+ relativesOnLevelCache(this._gedcom, this.id, currentGen, parents);
2874
3241
  parents && relatives.merge(parents);
2875
3242
  }
2876
3243
  return { relatives, generations };
@@ -5881,7 +6248,7 @@ var Families = class _Families extends List {
5881
6248
 
5882
6249
  // package.json
5883
6250
  var package_default = {
5884
- version: "1.0.23"};
6251
+ version: "2.0.0"};
5885
6252
 
5886
6253
  // src/utils/get-product-details.ts
5887
6254
  var isDevelopment = () => {
@@ -6872,7 +7239,7 @@ var GedcomTree = {
6872
7239
  return this.parseHierarchy(content, options);
6873
7240
  },
6874
7241
  parseHierarchy: function(content, options) {
6875
- const { settings } = options ?? {};
7242
+ const { settings, filename = "" } = options ?? {};
6876
7243
  const { linkedPersons = "skip", linkingKey } = settings ?? {};
6877
7244
  const gedcom = createGedCom();
6878
7245
  gedcom.removeValue();
@@ -6907,6 +7274,25 @@ var GedcomTree = {
6907
7274
  if (lineMatch) {
6908
7275
  const lineIndent = Number(lineMatch?.groups?.indent ?? 0);
6909
7276
  const lineValue = lineMatch?.groups?.value ?? "";
7277
+ const lineType = lineMatch?.groups?.type ?? "";
7278
+ const linesAcc = acc;
7279
+ if (lineIndent === 0 && lineType === "HEAD") {
7280
+ acc.push(line);
7281
+ linesAcc._inHead = true;
7282
+ linesAcc._hasFile = false;
7283
+ return acc;
7284
+ }
7285
+ if (lineIndent === 0 && linesAcc._inHead) {
7286
+ if (filename && !linesAcc._hasFile) {
7287
+ acc.push(`1 FILE ${filename}`);
7288
+ }
7289
+ linesAcc._inHead = false;
7290
+ acc.push(line);
7291
+ return acc;
7292
+ }
7293
+ if (linesAcc._inHead && lineIndent === 1 && lineType === "FILE") {
7294
+ linesAcc._hasFile = true;
7295
+ }
6910
7296
  if (lineIndent > 0 && lineIndent > prevLineIndent && lineValue && isId(lineValue)) {
6911
7297
  const refLines = lineValue.split(/,\s*/).map((id) => line.replace(lineValue, id));
6912
7298
  if (refLines.length > 1) {
@@ -7121,12 +7507,6 @@ function formatDate(date) {
7121
7507
  }
7122
7508
  return chalk.white(date);
7123
7509
  }
7124
- function formatPlace(place) {
7125
- if (!place) {
7126
- return chalk.gray("(unknown)");
7127
- }
7128
- return chalk.white(place);
7129
- }
7130
7510
  function formatName(name) {
7131
7511
  if (!name) {
7132
7512
  return chalk.gray("(unnamed)");
@@ -7309,102 +7689,327 @@ function registerExtractCommand(program2) {
7309
7689
  }
7310
7690
 
7311
7691
  // src/cli/commands/find.ts
7312
- function registerFindCommand(program2) {
7313
- program2.command("find <file> [query]").description("Find individuals in a GEDCOM file").option("--id <id>", "Find by GEDCOM ID").option("--name <name>", "Find by name (substring search)").option("--birth-year <year>", "Filter by birth year").option("--death-year <year>", "Filter by death year").option("-j, --json", "Output in JSON format").action((file, query, options) => {
7314
- try {
7315
- const content = readGedcomFile(file);
7316
- const { gedcom: tree } = parser_default.parse(content);
7317
- const individuals = tree.indis();
7318
- const results = [];
7319
- individuals.forEach((indi) => {
7320
- let matches = true;
7321
- if (options.id && indi.id !== options.id) {
7322
- matches = false;
7323
- }
7324
- if ((options.name || query) && matches) {
7325
- const searchName = (options.name || query || "").toLowerCase();
7326
- const name = cleanGedcomName(indi.NAME?.toValue()).toLowerCase();
7327
- if (!name.includes(searchName)) {
7328
- matches = false;
7329
- }
7692
+ function findIndividuals(tree, options) {
7693
+ const individuals = tree.indis();
7694
+ const results = [];
7695
+ individuals.forEach((indi) => {
7696
+ let matches = true;
7697
+ if (options.id && indi.id !== options.id) {
7698
+ matches = false;
7699
+ }
7700
+ if ((options.name || options.query) && matches) {
7701
+ const searchName = (options.name || options.query || "").toLowerCase();
7702
+ const name = cleanGedcomName(indi?.toNaturalName()).toLowerCase();
7703
+ if (!name.includes(searchName)) {
7704
+ matches = false;
7705
+ }
7706
+ }
7707
+ if (options.birthYear && matches) {
7708
+ const year = options.birthYear;
7709
+ const birthDate = indi.BIRT?.DATE?.toValue();
7710
+ if (!birthDate?.includes(String(year))) {
7711
+ matches = false;
7712
+ }
7713
+ }
7714
+ if (options.deathYear && matches) {
7715
+ const year = options.deathYear;
7716
+ const deathDate = indi.DEAT?.DATE?.toValue();
7717
+ if (!deathDate?.includes(String(year))) {
7718
+ matches = false;
7719
+ }
7720
+ }
7721
+ if (matches) {
7722
+ results.push(indi);
7723
+ }
7724
+ });
7725
+ return results;
7726
+ }
7727
+ function formatFindResults(results, json = false) {
7728
+ if (json) {
7729
+ const jsonResults = results.map((indi) => ({
7730
+ id: indi.id,
7731
+ name: cleanGedcomName(indi.NAME?.toValue()),
7732
+ birthDate: indi.BIRT?.DATE?.toValue() || null,
7733
+ birthPlace: indi.BIRT?.PLAC?.value || null,
7734
+ deathDate: indi.DEAT?.DATE?.toValue() || null,
7735
+ deathPlace: indi.DEAT?.PLAC?.value || null,
7736
+ sex: indi.SEX?.value || null
7737
+ }));
7738
+ console.log(
7739
+ formatJson({ count: jsonResults.length, individuals: jsonResults })
7740
+ );
7741
+ } else {
7742
+ if (results.length === 0) {
7743
+ console.log(
7744
+ formatWarning("No individuals found matching the criteria")
7745
+ );
7746
+ } else {
7747
+ console.log(
7748
+ formatHeader(`Found ${results.length} individual(s)
7749
+ `)
7750
+ );
7751
+ results.forEach((indi) => {
7752
+ const name = cleanGedcomName(indi.NAME?.toValue());
7753
+ const birthDate = indi.BIRT?.DATE?.toValue();
7754
+ const deathDate = indi.DEAT?.DATE?.toValue();
7755
+ const lifespan = formatLifespan(birthDate, deathDate);
7756
+ console.log(
7757
+ formatListItem(
7758
+ `${formatId(indi.id)} ${formatName(name)} ${lifespan}`
7759
+ )
7760
+ );
7761
+ const birthPlace = indi.BIRT?.PLAC?.value;
7762
+ if (birthPlace) {
7763
+ console.log(
7764
+ formatListItem(
7765
+ `Birth: ${formatDate(birthDate)} in ${birthPlace}`,
7766
+ 1
7767
+ )
7768
+ );
7330
7769
  }
7331
- if (options.birthYear && matches) {
7332
- const year = options.birthYear;
7333
- const birthDate = indi.BIRT?.DATE?.toValue();
7334
- if (!birthDate?.includes(String(year))) {
7335
- matches = false;
7336
- }
7770
+ const deathPlace = indi.DEAT?.PLAC?.value;
7771
+ if (deathPlace) {
7772
+ console.log(
7773
+ formatListItem(
7774
+ `Death: ${formatDate(deathDate)} in ${deathPlace}`,
7775
+ 1
7776
+ )
7777
+ );
7337
7778
  }
7338
- if (options.deathYear && matches) {
7339
- const year = options.deathYear;
7340
- const deathDate = indi.DEAT?.DATE?.toValue();
7341
- if (!deathDate?.includes(String(year))) {
7342
- matches = false;
7779
+ console.log();
7780
+ });
7781
+ }
7782
+ }
7783
+ }
7784
+ function registerFindCommand(program2) {
7785
+ program2.command("find <file> [query]").description("Find individuals in a GEDCOM file").option("--id <id>", "Find by GEDCOM ID").option("--name <name>", "Find by name (substring search)").option("--birth-year <year>", "Filter by birth year").option("--death-year <year>", "Filter by death year").option("-j, --json", "Output in JSON format").action(
7786
+ (file, query, options) => {
7787
+ try {
7788
+ const content = readGedcomFile(file);
7789
+ const { gedcom: tree } = parser_default.parse(content);
7790
+ const results = findIndividuals(tree, {
7791
+ ...options,
7792
+ query
7793
+ });
7794
+ formatFindResults(results, options.json);
7795
+ } catch (error) {
7796
+ handleError(error, "Failed to search GEDCOM file");
7797
+ }
7798
+ }
7799
+ );
7800
+ }
7801
+
7802
+ // src/cli/commands/get.ts
7803
+ function getValueByPath(record, path) {
7804
+ return record.get(path);
7805
+ }
7806
+ function formatOutput(value, options) {
7807
+ if (value === void 0 || value === null) {
7808
+ return "(not found)";
7809
+ }
7810
+ if (value instanceof List) {
7811
+ if (options.json) {
7812
+ const items2 = [];
7813
+ value.forEach((item) => {
7814
+ if (item) {
7815
+ try {
7816
+ items2.push(JSON.parse(item.toJson()));
7817
+ } catch {
7818
+ items2.push(item.toValue());
7343
7819
  }
7344
7820
  }
7345
- if (matches) {
7346
- results.push(indi);
7821
+ });
7822
+ return JSON.stringify(items2, null, 2);
7823
+ }
7824
+ if (options.raw) {
7825
+ const items2 = [];
7826
+ value.forEach((item) => {
7827
+ if (item) {
7828
+ items2.push(item.toGedcom());
7347
7829
  }
7348
7830
  });
7349
- if (options.json) {
7350
- const jsonResults = results.map((indi) => ({
7351
- id: indi.id,
7352
- name: cleanGedcomName(indi.NAME?.toValue()),
7353
- birthDate: indi.BIRT?.DATE?.toValue() || null,
7354
- birthPlace: indi.BIRT?.PLAC?.value || null,
7355
- deathDate: indi.DEAT?.DATE?.toValue() || null,
7356
- deathPlace: indi.DEAT?.PLAC?.value || null,
7357
- sex: indi.SEX?.value || null
7358
- }));
7359
- console.log(formatJson({ count: jsonResults.length, individuals: jsonResults }));
7360
- } else {
7361
- if (results.length === 0) {
7362
- console.log(formatWarning("No individuals found matching the criteria"));
7363
- } else {
7364
- console.log(formatHeader(`Found ${results.length} individual(s)
7365
- `));
7366
- results.forEach((indi) => {
7367
- const name = cleanGedcomName(indi.NAME?.toValue());
7368
- const birthDate = indi.BIRT?.DATE?.toValue();
7369
- const deathDate = indi.DEAT?.DATE?.toValue();
7370
- const lifespan = formatLifespan(birthDate, deathDate);
7371
- console.log(
7372
- formatListItem(
7373
- `${formatId(indi.id)} ${formatName(name)} ${lifespan}`
7374
- )
7375
- );
7376
- const birthPlace = indi.BIRT?.PLAC?.value;
7377
- if (birthPlace) {
7378
- console.log(formatListItem(`Birth: ${formatDate(birthDate)} in ${birthPlace}`, 1));
7379
- }
7380
- const deathPlace = indi.DEAT?.PLAC?.value;
7381
- if (deathPlace) {
7382
- console.log(formatListItem(`Death: ${formatDate(deathDate)} in ${deathPlace}`, 1));
7383
- }
7384
- console.log();
7385
- });
7831
+ return items2.join("\n");
7832
+ }
7833
+ const items = [];
7834
+ let index = 0;
7835
+ value.forEach((item) => {
7836
+ if (item) {
7837
+ const exportedValue = item.exportValue();
7838
+ items.push(
7839
+ `[${index}] ${exportedValue || item.toValue() || "(empty)"}`
7840
+ );
7841
+ }
7842
+ index++;
7843
+ });
7844
+ return items.join("\n");
7845
+ }
7846
+ if (value instanceof Common) {
7847
+ if (options.json) {
7848
+ return value.toJson();
7849
+ }
7850
+ if (options.raw) {
7851
+ return value.toValue() || "(empty)";
7852
+ }
7853
+ return value.exportValue() || value.toValue() || "(empty)";
7854
+ }
7855
+ return String(value);
7856
+ }
7857
+ function getValue(tree, id, path, json = false, raw = false) {
7858
+ let record = null;
7859
+ let recordType = "";
7860
+ if (id.startsWith("@I")) {
7861
+ record = tree.indi(id);
7862
+ recordType = "Individual";
7863
+ } else if (id.startsWith("@F")) {
7864
+ record = tree.fam(id);
7865
+ recordType = "Family";
7866
+ } else if (id.startsWith("@S")) {
7867
+ record = tree.sour(id);
7868
+ recordType = "Source";
7869
+ } else {
7870
+ record = tree.indi(id);
7871
+ recordType = "Individual";
7872
+ }
7873
+ if (!record) {
7874
+ throw new Error(`Record ${id} not found`);
7875
+ }
7876
+ if (!path) {
7877
+ if (json) {
7878
+ const simplified = {
7879
+ id: record.id || id,
7880
+ type: recordType
7881
+ };
7882
+ if (recordType === "Individual") {
7883
+ if (record.NAME) {
7884
+ simplified.name = record.toNaturalName();
7885
+ }
7886
+ if (record.BIRT) {
7887
+ simplified.birth = {
7888
+ date: record.BIRT.DATE?.toValue(),
7889
+ place: record.BIRT.PLAC?.value
7890
+ };
7891
+ }
7892
+ if (record.DEAT) {
7893
+ simplified.death = {
7894
+ date: record.DEAT.DATE?.toValue(),
7895
+ place: record.DEAT.PLAC?.value
7896
+ };
7897
+ }
7898
+ if (record.SEX) {
7899
+ simplified.sex = record.SEX.value;
7386
7900
  }
7387
7901
  }
7388
- } catch (error) {
7389
- handleError(error, "Failed to search GEDCOM file");
7902
+ return formatJson(simplified);
7903
+ } else {
7904
+ let result = `${recordType}: ${id}`;
7905
+ if (recordType === "Individual" && record.NAME) {
7906
+ result += `
7907
+ Name: ${record.NAME.toValue()}`;
7908
+ }
7909
+ return result;
7390
7910
  }
7391
- });
7911
+ }
7912
+ const value = getValueByPath(record, path);
7913
+ if (value === void 0) {
7914
+ throw new Error(`Path "${path}" not found in ${recordType} ${id}`);
7915
+ }
7916
+ return formatOutput(value, { json, raw });
7392
7917
  }
7393
-
7394
- // src/cli/commands/info.ts
7395
- function registerInfoCommand(program2) {
7396
- program2.command("info <file>").description("Display basic information about a GEDCOM file").option("-j, --json", "Output in JSON format").option("-v, --verbose", "Show detailed information").action((file, options) => {
7918
+ function registerGetCommand(program2) {
7919
+ program2.command("get <file> <id>").description("Get a value from a GEDCOM record").option(
7920
+ "-p, --path <path>",
7921
+ 'Dot-separated path to the value (e.g., "BIRT.PLAC", "NAME")'
7922
+ ).option("-j, --json", "Output in JSON format").option("-r, --raw", "Output raw value only (no formatting)").action((file, id, options) => {
7397
7923
  try {
7398
7924
  const content = readGedcomFile(file);
7399
7925
  const { gedcom: tree } = parser_default.parse(content);
7400
- const individuals = tree.indis();
7401
- const families = tree.fams();
7402
- const sources = tree.sours();
7403
- const repos = tree.repos();
7404
- const objes = tree.objes();
7405
- const submitters = tree.subms();
7406
- const header = tree.HEAD;
7407
- const version = header?.GEDC?.VERS?.value || "Unknown";
7926
+ let record = null;
7927
+ let recordType = "";
7928
+ if (id.startsWith("@I")) {
7929
+ record = tree.indi(id);
7930
+ recordType = "Individual";
7931
+ } else if (id.startsWith("@F")) {
7932
+ record = tree.fam(id);
7933
+ recordType = "Family";
7934
+ } else if (id.startsWith("@S")) {
7935
+ record = tree.sour(id);
7936
+ recordType = "Source";
7937
+ } else if (id.startsWith("@N")) {
7938
+ record = tree.note(id);
7939
+ recordType = "Note";
7940
+ } else {
7941
+ record = tree.indi(id);
7942
+ recordType = "Individual";
7943
+ }
7944
+ if (!record) {
7945
+ console.error(formatError(`Record ${id} not found`));
7946
+ process.exit(1);
7947
+ }
7948
+ if (!options.path) {
7949
+ if (options.json) {
7950
+ const simplified = {
7951
+ id: record.id || id,
7952
+ type: recordType
7953
+ };
7954
+ if (recordType === "Individual") {
7955
+ if (record.NAME) {
7956
+ simplified.name = record.NAME.toValue();
7957
+ }
7958
+ if (record.BIRT) {
7959
+ simplified.birth = {
7960
+ date: record.BIRT.DATE?.toValue(),
7961
+ place: record.BIRT.PLAC?.value
7962
+ };
7963
+ }
7964
+ if (record.DEAT) {
7965
+ simplified.death = {
7966
+ date: record.DEAT.DATE?.toValue(),
7967
+ place: record.DEAT.PLAC?.value
7968
+ };
7969
+ }
7970
+ if (record.SEX) {
7971
+ simplified.sex = record.SEX.value;
7972
+ }
7973
+ }
7974
+ console.log(formatJson(simplified));
7975
+ } else {
7976
+ console.log(`${recordType}: ${id}`);
7977
+ if (recordType === "Individual" && record.NAME) {
7978
+ console.log(`Name: ${record.NAME.toValue()}`);
7979
+ }
7980
+ }
7981
+ return;
7982
+ }
7983
+ const value = getValueByPath(record, options.path);
7984
+ if (value === void 0) {
7985
+ console.error(
7986
+ formatError(
7987
+ `Path "${options.path}" not found in ${recordType} ${id}`
7988
+ )
7989
+ );
7990
+ process.exit(1);
7991
+ }
7992
+ console.log(formatOutput(value, options));
7993
+ } catch (error) {
7994
+ handleError(error);
7995
+ }
7996
+ });
7997
+ }
7998
+
7999
+ // src/cli/commands/info.ts
8000
+ function registerInfoCommand(program2) {
8001
+ program2.command("info <file>").description("Display basic information about a GEDCOM file").option("-j, --json", "Output in JSON format").option("-v, --verbose", "Show detailed information").action((file, options) => {
8002
+ try {
8003
+ const content = readGedcomFile(file);
8004
+ const { gedcom: tree } = parser_default.parse(content);
8005
+ const individuals = tree.indis();
8006
+ const families = tree.fams();
8007
+ const sources = tree.sours();
8008
+ const repos = tree.repos();
8009
+ const objes = tree.objes();
8010
+ const submitters = tree.subms();
8011
+ const header = tree.HEAD;
8012
+ const version = header?.GEDC?.VERS?.value || "Unknown";
7408
8013
  const info = {
7409
8014
  file,
7410
8015
  version,
@@ -7514,6 +8119,862 @@ function registerMergeCommand(program2) {
7514
8119
  }
7515
8120
  });
7516
8121
  }
8122
+
8123
+ // src/cli/commands/select.ts
8124
+ function selectIndividual(tree, input, searchResults) {
8125
+ if (/^\d+$/.test(input) && searchResults && searchResults.length > 0) {
8126
+ const index = parseInt(input) - 1;
8127
+ if (index >= 0 && index < searchResults.length) {
8128
+ return searchResults[index];
8129
+ }
8130
+ return void 0;
8131
+ }
8132
+ return tree.indi(input);
8133
+ }
8134
+ function formatSelectResult(individual, input) {
8135
+ if (individual) {
8136
+ const name = cleanGedcomName(individual.NAME?.toValue());
8137
+ console.log(
8138
+ formatSuccess(
8139
+ `Selected: ${formatId(individual.id)} ${formatName(name)}`
8140
+ )
8141
+ );
8142
+ } else {
8143
+ console.log(formatError(`Individual ${input} not found`));
8144
+ }
8145
+ }
8146
+ function registerSelectCommand(program2) {
8147
+ program2.command("select <file> <id>").description("Select an individual by ID").action((file, id) => {
8148
+ try {
8149
+ const content = readGedcomFile(file);
8150
+ const { gedcom: tree } = parser_default.parse(content);
8151
+ const individual = selectIndividual(tree, id);
8152
+ formatSelectResult(individual, id);
8153
+ if (!individual) {
8154
+ process.exit(1);
8155
+ }
8156
+ } catch (error) {
8157
+ handleError(error, "Failed to select individual");
8158
+ }
8159
+ });
8160
+ }
8161
+
8162
+ // src/cli/commands/show.ts
8163
+ function showIndividual(tree, individual) {
8164
+ const name = cleanGedcomName(individual.NAME?.toValue());
8165
+ console.log(
8166
+ formatHeader(`
8167
+ ${formatId(individual.id)} ${formatName(name)}`)
8168
+ );
8169
+ console.log("");
8170
+ if (individual.SEX?.value) {
8171
+ console.log(formatListItem(`Sex: ${individual.SEX.value}`));
8172
+ }
8173
+ if (individual.BIRT) {
8174
+ const date = individual.BIRT.DATE?.toValue();
8175
+ const place = individual.BIRT.PLAC?.value;
8176
+ console.log(
8177
+ formatListItem(
8178
+ `Birth: ${date || "?"}${place ? ` at ${place}` : ""}`
8179
+ )
8180
+ );
8181
+ }
8182
+ if (individual.DEAT) {
8183
+ const date = individual.DEAT.DATE?.toValue();
8184
+ const place = individual.DEAT.PLAC?.value;
8185
+ console.log(
8186
+ formatListItem(
8187
+ `Death: ${date || "?"}${place ? ` at ${place}` : ""}`
8188
+ )
8189
+ );
8190
+ }
8191
+ const parentFams = individual.FAMC;
8192
+ if (parentFams) {
8193
+ const parentList = parentFams instanceof List ? parentFams.values() : [parentFams];
8194
+ console.log(formatListItem("\nParents:"));
8195
+ parentList.forEach((famRef) => {
8196
+ if (famRef) {
8197
+ const fam = tree.fam(famRef.value);
8198
+ if (fam) {
8199
+ const father = fam.HUSB ? tree.indi(fam.HUSB.value) : null;
8200
+ const mother = fam.WIFE ? tree.indi(fam.WIFE.value) : null;
8201
+ if (father) {
8202
+ const fatherName = cleanGedcomName(
8203
+ father.NAME?.toValue()
8204
+ );
8205
+ console.log(
8206
+ formatListItem(
8207
+ ` ${formatId(father.id)} ${formatName(fatherName)}`
8208
+ )
8209
+ );
8210
+ }
8211
+ if (mother) {
8212
+ const motherName = cleanGedcomName(
8213
+ mother.NAME?.toValue()
8214
+ );
8215
+ console.log(
8216
+ formatListItem(
8217
+ ` ${formatId(mother.id)} ${formatName(motherName)}`
8218
+ )
8219
+ );
8220
+ }
8221
+ }
8222
+ }
8223
+ });
8224
+ }
8225
+ const spouseFams = individual.FAMS;
8226
+ if (spouseFams) {
8227
+ const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
8228
+ console.log(formatListItem("\nSpouses:"));
8229
+ famList.forEach((famRef) => {
8230
+ if (famRef) {
8231
+ const fam = tree.fam(famRef.value);
8232
+ if (fam) {
8233
+ const spouseRef = fam.HUSB?.value === individual.id ? fam.WIFE : fam.HUSB;
8234
+ if (spouseRef) {
8235
+ const spouse = tree.indi(spouseRef.value);
8236
+ if (spouse) {
8237
+ const spouseName = cleanGedcomName(
8238
+ spouse.NAME?.toValue()
8239
+ );
8240
+ console.log(
8241
+ formatListItem(
8242
+ ` ${formatId(spouse.id)} ${formatName(spouseName)}`
8243
+ )
8244
+ );
8245
+ }
8246
+ }
8247
+ }
8248
+ }
8249
+ });
8250
+ }
8251
+ if (spouseFams) {
8252
+ const famList = spouseFams instanceof List ? spouseFams.values() : [spouseFams];
8253
+ let hasChildren = false;
8254
+ famList.forEach((famRef) => {
8255
+ if (famRef) {
8256
+ const fam = tree.fam(famRef.value);
8257
+ if (fam && fam.CHIL) {
8258
+ if (!hasChildren) {
8259
+ console.log(formatListItem("\nChildren:"));
8260
+ hasChildren = true;
8261
+ }
8262
+ const children = fam.CHIL instanceof List ? fam.CHIL.values() : [fam.CHIL];
8263
+ children.forEach((childRef) => {
8264
+ if (childRef) {
8265
+ const child = tree.indi(childRef.value);
8266
+ if (child) {
8267
+ const childName = cleanGedcomName(
8268
+ child.NAME?.toValue()
8269
+ );
8270
+ console.log(
8271
+ formatListItem(
8272
+ ` ${formatId(child.id)} ${formatName(childName)}`
8273
+ )
8274
+ );
8275
+ }
8276
+ }
8277
+ });
8278
+ }
8279
+ }
8280
+ });
8281
+ }
8282
+ console.log("");
8283
+ }
8284
+ function registerShowCommand(program2) {
8285
+ program2.command("show <file> <id>").description("Display detailed information about an individual").action((file, id) => {
8286
+ try {
8287
+ const content = readGedcomFile(file);
8288
+ const { gedcom: tree } = parser_default.parse(content);
8289
+ const individual = tree.indi(id);
8290
+ if (!individual) {
8291
+ console.error(formatError(`Individual ${id} not found`));
8292
+ process.exit(1);
8293
+ }
8294
+ showIndividual(tree, individual);
8295
+ } catch (error) {
8296
+ handleError(error, "Failed to show individual");
8297
+ }
8298
+ });
8299
+ }
8300
+
8301
+ // src/cli/commands/stats.ts
8302
+ function displayStats(tree, json = false) {
8303
+ const stats = tree.stats();
8304
+ if (json) {
8305
+ console.log(formatJson(stats));
8306
+ } else {
8307
+ console.log(formatHeader("GEDCOM File Statistics\n"));
8308
+ console.log(formatLabel("Total Individuals"));
8309
+ console.log(` ${formatCount(stats.totalIndividuals)}`);
8310
+ console.log(formatLabel("Total Families"));
8311
+ console.log(` ${formatCount(stats.totalFamilies)}`);
8312
+ console.log();
8313
+ console.log(formatLabel("By Gender"));
8314
+ console.log(` Males: ${formatCount(stats.byGender.males)}`);
8315
+ console.log(` Females: ${formatCount(stats.byGender.females)}`);
8316
+ console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
8317
+ console.log();
8318
+ if (stats.dateRange.earliest && stats.dateRange.latest) {
8319
+ console.log(formatLabel("Date Range"));
8320
+ console.log(
8321
+ ` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`
8322
+ );
8323
+ console.log();
8324
+ }
8325
+ if (stats.averageLifespan) {
8326
+ console.log(formatLabel("Average Lifespan"));
8327
+ console.log(` ${stats.averageLifespan.toFixed(1)} years`);
8328
+ console.log();
8329
+ }
8330
+ if (stats.topSurnames.length > 0) {
8331
+ console.log(formatLabel("Most Common Surnames"));
8332
+ stats.topSurnames.forEach(({ surname, count }) => {
8333
+ console.log(` ${surname}: ${formatCount(count)}`);
8334
+ });
8335
+ console.log();
8336
+ }
8337
+ if (stats.topBirthPlaces.length > 0) {
8338
+ console.log(formatLabel("Most Common Birth Places"));
8339
+ stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
8340
+ console.log(` ${place}: ${formatCount(count)}`);
8341
+ });
8342
+ }
8343
+ }
8344
+ }
8345
+ function registerStatsCommand(program2) {
8346
+ program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
8347
+ try {
8348
+ const content = readGedcomFile(file);
8349
+ const { gedcom: tree } = parser_default.parse(content);
8350
+ displayStats(tree, options.json);
8351
+ } catch (error) {
8352
+ handleError(error, "Failed to generate statistics");
8353
+ }
8354
+ });
8355
+ }
8356
+
8357
+ // src/cli/repl.ts
8358
+ function parseArgs(line) {
8359
+ const args = [];
8360
+ let current = "";
8361
+ let inQuotes = false;
8362
+ let quoteChar = "";
8363
+ for (let i = 0; i < line.length; i++) {
8364
+ const char = line[i];
8365
+ if ((char === '"' || char === "'") && !inQuotes) {
8366
+ inQuotes = true;
8367
+ quoteChar = char;
8368
+ } else if (char === quoteChar && inQuotes) {
8369
+ inQuotes = false;
8370
+ quoteChar = "";
8371
+ } else if (char === " " && !inQuotes) {
8372
+ if (current) {
8373
+ args.push(current);
8374
+ current = "";
8375
+ }
8376
+ } else {
8377
+ current += char;
8378
+ }
8379
+ }
8380
+ if (current) {
8381
+ args.push(current);
8382
+ }
8383
+ return args;
8384
+ }
8385
+ var GedcomRepl = class {
8386
+ constructor(tree) {
8387
+ this.context = { tree };
8388
+ this.rl = readline.createInterface({
8389
+ input: process.stdin,
8390
+ output: process.stdout,
8391
+ prompt: chalk.blue("gedcom> ")
8392
+ });
8393
+ this.setupHandlers();
8394
+ }
8395
+ setupHandlers() {
8396
+ this.rl.on("line", (line) => {
8397
+ this.handleCommand(line.trim());
8398
+ });
8399
+ this.rl.on("close", () => {
8400
+ console.log("\nGoodbye!");
8401
+ process.exit(0);
8402
+ });
8403
+ }
8404
+ handleCommand(line) {
8405
+ if (!line) {
8406
+ this.rl.prompt();
8407
+ return;
8408
+ }
8409
+ const args = parseArgs(line);
8410
+ const command = args[0];
8411
+ const commandArgs = args.slice(1);
8412
+ switch (command.toLowerCase()) {
8413
+ case "help":
8414
+ this.showHelp(commandArgs[0]);
8415
+ break;
8416
+ case "stats":
8417
+ displayStats(this.context.tree, commandArgs.includes("--json"));
8418
+ break;
8419
+ case "find":
8420
+ this.handleFind(commandArgs);
8421
+ break;
8422
+ case "select":
8423
+ this.handleSelect(commandArgs);
8424
+ break;
8425
+ case "show":
8426
+ this.handleShow(commandArgs);
8427
+ break;
8428
+ case "get":
8429
+ this.handleGet(commandArgs);
8430
+ break;
8431
+ case "relatives":
8432
+ this.handleRelatives(commandArgs);
8433
+ break;
8434
+ case "validate":
8435
+ this.handleValidate(commandArgs);
8436
+ break;
8437
+ case "clear":
8438
+ console.clear();
8439
+ break;
8440
+ case "exit":
8441
+ case "quit":
8442
+ this.rl.close();
8443
+ return;
8444
+ default:
8445
+ console.log(formatError(`Unknown command: ${command}`));
8446
+ console.log('Type "help" for available commands');
8447
+ }
8448
+ this.rl.prompt();
8449
+ }
8450
+ showHelp(command) {
8451
+ if (command) {
8452
+ switch (command.toLowerCase()) {
8453
+ case "stats":
8454
+ console.log(formatHeader("\nstats"));
8455
+ console.log("Show GEDCOM file statistics\n");
8456
+ console.log("Usage: stats [--json]");
8457
+ console.log("\nOptions:");
8458
+ console.log(" --json Output in JSON format");
8459
+ break;
8460
+ case "find":
8461
+ console.log(formatHeader("\nfind"));
8462
+ console.log("Search for individuals in the tree\n");
8463
+ console.log("Usage: find <query> [options]");
8464
+ console.log("\nArguments:");
8465
+ console.log(" <query> Search query (name or ID)");
8466
+ console.log("\nOptions:");
8467
+ console.log(" --birth-year <year> Filter by birth year");
8468
+ console.log(" --death-year <year> Filter by death year");
8469
+ console.log(
8470
+ " --json Output in JSON format"
8471
+ );
8472
+ break;
8473
+ case "select":
8474
+ console.log(formatHeader("\nselect"));
8475
+ console.log("Select an individual by ID or index\n");
8476
+ console.log("Usage: select <id|index>");
8477
+ console.log("\nArguments:");
8478
+ console.log(
8479
+ " <id|index> Individual ID (e.g., @I123@) or search result index (e.g., 1)"
8480
+ );
8481
+ break;
8482
+ case "show":
8483
+ console.log(formatHeader("\nshow"));
8484
+ console.log(
8485
+ "Show detailed information about an individual\n"
8486
+ );
8487
+ console.log("Usage: show [id]");
8488
+ console.log("\nArguments:");
8489
+ console.log(
8490
+ " [id] Optional individual ID. If not provided, shows currently selected person"
8491
+ );
8492
+ break;
8493
+ case "get":
8494
+ console.log(formatHeader("\nget"));
8495
+ console.log("Get a value from a GEDCOM record\n");
8496
+ console.log("Usage: get [id] [options]");
8497
+ console.log("\nArguments:");
8498
+ console.log(
8499
+ " [id] Record ID (e.g., @I123@, @F456@). If not provided, uses selected person"
8500
+ );
8501
+ console.log("\nOptions:");
8502
+ console.log(
8503
+ ' --path, -p <path> Dot-separated path (e.g., "BIRT.PLAC", "NAME")'
8504
+ );
8505
+ console.log(" --json, -j Output in JSON format");
8506
+ console.log(
8507
+ " --raw, -r Output raw value only (no formatting)"
8508
+ );
8509
+ console.log("\nExamples:");
8510
+ console.log(' get --path "BIRT.PLAC"');
8511
+ console.log(' get @I123@ --path "NAME"');
8512
+ console.log(" get @I123@ --json");
8513
+ break;
8514
+ case "relatives":
8515
+ console.log(formatHeader("\nrelatives"));
8516
+ console.log(
8517
+ "Get ancestors and/or descendants of the selected individual\n"
8518
+ );
8519
+ console.log("Usage: relatives [options]");
8520
+ console.log("\nOptions:");
8521
+ console.log(" --ancestors, -a Include ancestors");
8522
+ console.log(" --descendants, -d Include descendants");
8523
+ console.log(
8524
+ " --tree, -t Include both ancestors and descendants"
8525
+ );
8526
+ console.log(
8527
+ " --depth <n> Limit depth (generations, default: 999)"
8528
+ );
8529
+ console.log(" --json, -j Output in JSON format");
8530
+ console.log(
8531
+ "\nNote: You must select an individual first using 'select'"
8532
+ );
8533
+ console.log("\nExamples:");
8534
+ console.log(" relatives --ancestors");
8535
+ console.log(" relatives --descendants --depth 3");
8536
+ console.log(" relatives --tree --json");
8537
+ break;
8538
+ case "validate":
8539
+ console.log(formatHeader("\nvalidate"));
8540
+ console.log("Validate the GEDCOM file\n");
8541
+ console.log("Usage: validate [options]");
8542
+ console.log("\nOptions:");
8543
+ console.log(" --json, -j Output in JSON format");
8544
+ console.log(" --strict, -s Enable strict validation");
8545
+ console.log("\nExamples:");
8546
+ console.log(" validate");
8547
+ console.log(" validate --strict --json");
8548
+ break;
8549
+ case "clear":
8550
+ console.log(formatHeader("\nclear"));
8551
+ console.log("Clear the screen\n");
8552
+ console.log("Usage: clear");
8553
+ break;
8554
+ case "exit":
8555
+ case "quit":
8556
+ console.log(formatHeader("\nexit"));
8557
+ console.log("Exit the REPL\n");
8558
+ console.log("Usage: exit");
8559
+ console.log("Alias: quit");
8560
+ break;
8561
+ default:
8562
+ console.log(formatError(`Unknown command: ${command}`));
8563
+ console.log('Type "help" for available commands');
8564
+ }
8565
+ console.log("");
8566
+ return;
8567
+ }
8568
+ console.log(formatHeader("\nAvailable Commands:"));
8569
+ console.log("");
8570
+ console.log(
8571
+ chalk.cyan(" stats [--json]") + " - Show tree statistics"
8572
+ );
8573
+ console.log(
8574
+ chalk.cyan(" find <query> [options]") + " - Search for individuals"
8575
+ );
8576
+ console.log(
8577
+ chalk.cyan(" select <id|index>") + " - Select an individual"
8578
+ );
8579
+ console.log(
8580
+ chalk.cyan(" show [id]") + " - Show details (current or specified person)"
8581
+ );
8582
+ console.log(
8583
+ chalk.cyan(" get [id] [options]") + " - Get a value from a record"
8584
+ );
8585
+ console.log(
8586
+ chalk.cyan(" relatives [options]") + " - Get ancestors/descendants of selected person"
8587
+ );
8588
+ console.log(
8589
+ chalk.cyan(" validate [options]") + " - Validate the GEDCOM file"
8590
+ );
8591
+ console.log(
8592
+ chalk.cyan(" clear") + " - Clear screen"
8593
+ );
8594
+ console.log(
8595
+ chalk.cyan(" help [command]") + " - Show this help or help for a specific command"
8596
+ );
8597
+ console.log(
8598
+ chalk.cyan(" exit") + " - Exit REPL"
8599
+ );
8600
+ console.log("");
8601
+ }
8602
+ handleFind(args) {
8603
+ if (args.length === 0) {
8604
+ console.log(formatError("Usage: find <query> [options]"));
8605
+ console.log('Type "help find" for more information');
8606
+ return;
8607
+ }
8608
+ const isJson = args.includes("--json");
8609
+ let birthYear;
8610
+ let deathYear;
8611
+ const birthYearIndex = args.indexOf("--birth-year");
8612
+ if (birthYearIndex !== -1 && birthYearIndex + 1 < args.length) {
8613
+ birthYear = args[birthYearIndex + 1];
8614
+ }
8615
+ const deathYearIndex = args.indexOf("--death-year");
8616
+ if (deathYearIndex !== -1 && deathYearIndex + 1 < args.length) {
8617
+ deathYear = args[deathYearIndex + 1];
8618
+ }
8619
+ const query = args.filter((a, i) => {
8620
+ if (a.startsWith("--")) return false;
8621
+ if (i > 0 && args[i - 1].startsWith("--")) return false;
8622
+ return true;
8623
+ }).join(" ");
8624
+ const results = findIndividuals(this.context.tree, {
8625
+ query: query || void 0,
8626
+ birthYear,
8627
+ deathYear
8628
+ });
8629
+ this.context.searchResults = results;
8630
+ formatFindResults(results, isJson);
8631
+ if (results.length > 0 && !isJson) {
8632
+ console.log(
8633
+ chalk.dim(
8634
+ '\nTip: Use "select <number>" to select an individual from results'
8635
+ )
8636
+ );
8637
+ }
8638
+ }
8639
+ handleSelect(args) {
8640
+ if (args.length === 0) {
8641
+ console.log(formatError("Usage: select <id|index>"));
8642
+ return;
8643
+ }
8644
+ const input = args[0];
8645
+ const individual = selectIndividual(
8646
+ this.context.tree,
8647
+ input,
8648
+ this.context.searchResults
8649
+ );
8650
+ formatSelectResult(individual, input);
8651
+ if (individual) {
8652
+ this.context.selectedPerson = individual;
8653
+ console.log(chalk.dim('Tip: Use "show" to see details'));
8654
+ }
8655
+ }
8656
+ handleShow(args) {
8657
+ let individual;
8658
+ if (args.length > 0) {
8659
+ individual = this.context.tree.indi(args[0]);
8660
+ if (!individual) {
8661
+ console.log(formatError(`Individual ${args[0]} not found`));
8662
+ return;
8663
+ }
8664
+ } else {
8665
+ individual = this.context.selectedPerson;
8666
+ if (!individual) {
8667
+ console.log(
8668
+ formatError(
8669
+ 'No person selected. Use "select <id>" first or "show <id>"'
8670
+ )
8671
+ );
8672
+ return;
8673
+ }
8674
+ }
8675
+ showIndividual(this.context.tree, individual);
8676
+ }
8677
+ handleGet(args) {
8678
+ if (args.length === 0) {
8679
+ console.log(
8680
+ formatError("Usage: get <id> [--path <path>] [--json] [--raw]")
8681
+ );
8682
+ console.log('Type "help get" for more information');
8683
+ return;
8684
+ }
8685
+ let id;
8686
+ let path;
8687
+ let json = false;
8688
+ let raw = false;
8689
+ for (let i = 0; i < args.length; i++) {
8690
+ const arg = args[i];
8691
+ if (arg === "--path" || arg === "-p") {
8692
+ if (i + 1 < args.length) {
8693
+ path = args[i + 1];
8694
+ i++;
8695
+ }
8696
+ } else if (arg === "--json" || arg === "-j") {
8697
+ json = true;
8698
+ } else if (arg === "--raw" || arg === "-r") {
8699
+ raw = true;
8700
+ } else if (!arg.startsWith("-") && !id) {
8701
+ id = arg;
8702
+ }
8703
+ }
8704
+ if (!id) {
8705
+ if (this.context.selectedPerson) {
8706
+ id = this.context.selectedPerson.id;
8707
+ } else {
8708
+ console.log(
8709
+ formatError("No ID specified and no person selected")
8710
+ );
8711
+ return;
8712
+ }
8713
+ }
8714
+ try {
8715
+ const result = getValue(this.context.tree, id, path, json, raw);
8716
+ console.log(result);
8717
+ } catch (error) {
8718
+ console.log(formatError(error.message));
8719
+ }
8720
+ }
8721
+ handleRelatives(args) {
8722
+ if (!this.context.selectedPerson) {
8723
+ console.log(
8724
+ formatError('No person selected. Use "select <id>" first')
8725
+ );
8726
+ return;
8727
+ }
8728
+ const isJson = args.includes("--json") || args.includes("-j");
8729
+ const includeAncestors = args.includes("--ancestors") || args.includes("-a") || args.includes("--tree") || args.includes("-t");
8730
+ const includeDescendants = args.includes("--descendants") || args.includes("-d") || args.includes("--tree") || args.includes("-t");
8731
+ if (!includeAncestors && !includeDescendants) {
8732
+ console.log(
8733
+ formatError(
8734
+ "Must specify --ancestors, --descendants, or --tree"
8735
+ )
8736
+ );
8737
+ console.log('Type "help relatives" for more information');
8738
+ return;
8739
+ }
8740
+ let maxDepth = 999;
8741
+ const depthIndex = args.findIndex((arg) => arg === "--depth");
8742
+ if (depthIndex !== -1 && depthIndex + 1 < args.length) {
8743
+ maxDepth = parseInt(args[depthIndex + 1], 10);
8744
+ if (isNaN(maxDepth) || maxDepth < 1) {
8745
+ console.log(formatError("Invalid depth value"));
8746
+ return;
8747
+ }
8748
+ }
8749
+ const individual = this.context.selectedPerson;
8750
+ const relatives = /* @__PURE__ */ new Set();
8751
+ relatives.add(individual.id);
8752
+ if (includeAncestors) {
8753
+ const getAncestors = (indi, depth) => {
8754
+ if (depth > maxDepth) return;
8755
+ const parents = indi.getParents();
8756
+ parents?.forEach((parent) => {
8757
+ relatives.add(parent.id);
8758
+ getAncestors(parent, depth + 1);
8759
+ });
8760
+ };
8761
+ getAncestors(individual, 1);
8762
+ }
8763
+ if (includeDescendants) {
8764
+ const getDescendants = (indi, depth) => {
8765
+ if (depth > maxDepth) return;
8766
+ const children = indi.getChildren();
8767
+ children?.forEach((child) => {
8768
+ relatives.add(child.id);
8769
+ getDescendants(child, depth + 1);
8770
+ });
8771
+ };
8772
+ getDescendants(individual, 1);
8773
+ }
8774
+ const allRelatives = Array.from(relatives).map((relId) => this.context.tree.indi(relId)).filter((indi) => indi !== null);
8775
+ if (isJson) {
8776
+ const jsonData = allRelatives.map((indi) => ({
8777
+ id: indi.id,
8778
+ name: cleanGedcomName(indi.NAME?.toValue()),
8779
+ birthDate: indi.BIRT?.DATE?.toValue() || null,
8780
+ deathDate: indi.DEAT?.DATE?.toValue() || null
8781
+ }));
8782
+ console.log(
8783
+ formatJson({ count: jsonData.length, individuals: jsonData })
8784
+ );
8785
+ } else {
8786
+ console.log(
8787
+ formatHeader(`Found ${allRelatives.length} relative(s)
8788
+ `)
8789
+ );
8790
+ allRelatives.forEach((indi) => {
8791
+ const name = cleanGedcomName(indi.NAME?.toValue());
8792
+ const lifespan = formatLifespan(
8793
+ indi.BIRT?.DATE?.toValue(),
8794
+ indi.DEAT?.DATE?.toValue()
8795
+ );
8796
+ console.log(
8797
+ formatListItem(
8798
+ `${formatId(indi.id)} ${formatName(name)} ${lifespan}`
8799
+ )
8800
+ );
8801
+ });
8802
+ }
8803
+ }
8804
+ handleValidate(args) {
8805
+ const isJson = args.includes("--json") || args.includes("-j");
8806
+ const isStrict = args.includes("--strict") || args.includes("-s");
8807
+ const errors = [];
8808
+ const warnings = [];
8809
+ const header = this.context.tree.HEAD;
8810
+ const version = header?.GEDC?.VERS?.value || "Unknown";
8811
+ const individuals = this.context.tree.indis();
8812
+ const families = this.context.tree.fams();
8813
+ individuals.forEach((indi) => {
8814
+ if (!indi.NAME?.toValue()) {
8815
+ warnings.push(`Individual ${indi.id} is missing a name`);
8816
+ }
8817
+ });
8818
+ let missingBirthDates = 0;
8819
+ individuals.forEach((indi) => {
8820
+ if (!indi.BIRT?.DATE?.toValue()) {
8821
+ missingBirthDates++;
8822
+ }
8823
+ });
8824
+ let missingDeathDates = 0;
8825
+ individuals.forEach((indi) => {
8826
+ if (indi.DEAT && !indi.DEAT.DATE?.toValue()) {
8827
+ missingDeathDates++;
8828
+ }
8829
+ });
8830
+ const seenIds = /* @__PURE__ */ new Set();
8831
+ const duplicateIds = [];
8832
+ const checkDuplicates = (item) => {
8833
+ if (seenIds.has(item.id)) {
8834
+ duplicateIds.push(item.id);
8835
+ errors.push(`Duplicate ID found: ${item.id}`);
8836
+ }
8837
+ seenIds.add(item.id);
8838
+ };
8839
+ individuals.forEach(checkDuplicates);
8840
+ families.forEach(checkDuplicates);
8841
+ families.forEach((fam) => {
8842
+ const husb = fam.HUSB?.value;
8843
+ const wife = fam.WIFE?.value;
8844
+ if (!husb && !wife) {
8845
+ warnings.push(`Family ${fam.id} has no husband or wife`);
8846
+ }
8847
+ if (husb && !this.context.tree.indi(husb)) {
8848
+ errors.push(
8849
+ `Family ${fam.id} references non-existent husband ${husb}`
8850
+ );
8851
+ }
8852
+ if (wife && !this.context.tree.indi(wife)) {
8853
+ errors.push(
8854
+ `Family ${fam.id} references non-existent wife ${wife}`
8855
+ );
8856
+ }
8857
+ });
8858
+ if (isStrict) {
8859
+ individuals.forEach((indi) => {
8860
+ const birthDate = indi.BIRT?.DATE?.toValue();
8861
+ const deathDate = indi.DEAT?.DATE?.toValue();
8862
+ if (birthDate && birthDate.includes("INVALID")) {
8863
+ errors.push(`Invalid birth date format for ${indi.id}`);
8864
+ }
8865
+ if (deathDate && deathDate.includes("INVALID")) {
8866
+ errors.push(`Invalid death date format for ${indi.id}`);
8867
+ }
8868
+ });
8869
+ }
8870
+ const result = {
8871
+ valid: errors.length === 0,
8872
+ version,
8873
+ errors,
8874
+ warnings
8875
+ };
8876
+ if (isJson) {
8877
+ console.log(formatJson(result));
8878
+ } else {
8879
+ if (result.valid) {
8880
+ console.log(formatSuccess(`Valid GEDCOM ${version} file`));
8881
+ } else {
8882
+ console.log(
8883
+ formatError(
8884
+ `Invalid GEDCOM file - ${errors.length} error(s) found`
8885
+ )
8886
+ );
8887
+ }
8888
+ console.log();
8889
+ console.log(formatHeader("Validation Summary"));
8890
+ console.log(
8891
+ `${formatError("Errors:")} ${formatCount(errors.length)}`
8892
+ );
8893
+ console.log(
8894
+ `${formatWarning("Warnings:")} ${formatCount(warnings.length)}`
8895
+ );
8896
+ if (errors.length > 0) {
8897
+ console.log();
8898
+ console.log(formatHeader("Errors"));
8899
+ errors.slice(0, 10).forEach((error) => {
8900
+ console.log(formatListItem(formatError(error)));
8901
+ });
8902
+ if (errors.length > 10) {
8903
+ console.log(
8904
+ formatListItem(
8905
+ `... and ${errors.length - 10} more errors`
8906
+ )
8907
+ );
8908
+ }
8909
+ }
8910
+ if (warnings.length > 0) {
8911
+ console.log();
8912
+ console.log(formatHeader("Warnings"));
8913
+ if (missingBirthDates > 0) {
8914
+ console.log(
8915
+ formatListItem(
8916
+ formatWarning(
8917
+ `Missing birth dates: ${missingBirthDates} individuals`
8918
+ )
8919
+ )
8920
+ );
8921
+ }
8922
+ if (missingDeathDates > 0) {
8923
+ console.log(
8924
+ formatListItem(
8925
+ formatWarning(
8926
+ `Missing death dates: ${missingDeathDates} individuals`
8927
+ )
8928
+ )
8929
+ );
8930
+ }
8931
+ if (duplicateIds.length > 0) {
8932
+ console.log(
8933
+ formatListItem(
8934
+ formatWarning(
8935
+ `Duplicate IDs: ${duplicateIds.length}`
8936
+ )
8937
+ )
8938
+ );
8939
+ }
8940
+ const otherWarnings = warnings.filter(
8941
+ (w) => !w.includes("birth") && !w.includes("death")
8942
+ );
8943
+ otherWarnings.slice(0, 5).forEach((warning) => {
8944
+ console.log(formatListItem(formatWarning(warning)));
8945
+ });
8946
+ if (otherWarnings.length > 5) {
8947
+ console.log(
8948
+ formatListItem(
8949
+ `... and ${otherWarnings.length - 5} more warnings`
8950
+ )
8951
+ );
8952
+ }
8953
+ }
8954
+ }
8955
+ }
8956
+ start() {
8957
+ console.log(formatHeader("GEDCOM Interactive Explorer"));
8958
+ console.log("");
8959
+ console.log(chalk.dim('Type "help" for available commands'));
8960
+ console.log("");
8961
+ this.rl.prompt();
8962
+ }
8963
+ };
8964
+
8965
+ // src/cli/commands/open.ts
8966
+ function registerOpenCommand(program2) {
8967
+ program2.command("open <file>").description("Open GEDCOM file in interactive mode").action((file) => {
8968
+ try {
8969
+ const content = readGedcomFile(file);
8970
+ const { gedcom: tree } = parser_default.parse(content);
8971
+ const repl = new GedcomRepl(tree);
8972
+ repl.start();
8973
+ } catch (error) {
8974
+ handleError(error, "Failed to open GEDCOM file");
8975
+ }
8976
+ });
8977
+ }
7517
8978
  function registerRelativesCommand(program2) {
7518
8979
  program2.command("relatives <file> <id>").description("Get ancestors and/or descendants of an individual").option("-a, --ancestors", "Include ancestors").option("-d, --descendants", "Include descendants").option("-t, --tree", "Include both ancestors and descendants").option("--depth <n>", "Limit depth (generations)", "999").option("-o, --output <file>", "Save to new GEDCOM file").option("-j, --json", "Output in JSON format").action((file, id, options) => {
7519
8980
  try {
@@ -7598,242 +9059,6 @@ function createSubsetGedcom(tree, individuals) {
7598
9059
  return lines.join("\n");
7599
9060
  }
7600
9061
 
7601
- // src/cli/commands/show.ts
7602
- function registerShowCommand(program2) {
7603
- program2.command("show <file> <id>").description("Display detailed information about an individual").option("-j, --json", "Output in JSON format").option("-f, --format <format>", "Output format: text, json, markdown", "text").option("--include-events", "Include all life events").option("--include-sources", "Include sources").action((file, id, options) => {
7604
- try {
7605
- const content = readGedcomFile(file);
7606
- const { gedcom: tree } = parser_default.parse(content);
7607
- const individual = tree.indi(id);
7608
- if (!individual) {
7609
- console.error(formatError(`Individual ${id} not found`));
7610
- process.exit(1);
7611
- }
7612
- const name = cleanGedcomName(individual.NAME?.toValue());
7613
- const birthDate = individual.BIRT?.DATE?.toValue();
7614
- const birthPlace = individual.BIRT?.PLAC?.value;
7615
- const deathDate = individual.DEAT?.DATE?.toValue();
7616
- const deathPlace = individual.DEAT?.PLAC?.value;
7617
- const sex = individual.SEX?.value;
7618
- const parents = individual.getParents();
7619
- const father = parents?.find((p) => p.SEX?.value === "M");
7620
- const mother = parents?.find((p) => p.SEX?.value === "F");
7621
- const spouses = individual.getSpouses();
7622
- const children = individual.getChildren();
7623
- if (options.json || options.format === "json") {
7624
- const jsonData = {
7625
- id: individual.id,
7626
- name,
7627
- sex,
7628
- birth: {
7629
- date: birthDate || null,
7630
- place: birthPlace || null
7631
- },
7632
- death: {
7633
- date: deathDate || null,
7634
- place: deathPlace || null
7635
- },
7636
- parents: {
7637
- father: father ? {
7638
- id: father.id,
7639
- name: cleanGedcomName(father.NAME?.toValue())
7640
- } : null,
7641
- mother: mother ? {
7642
- id: mother.id,
7643
- name: cleanGedcomName(mother.NAME?.toValue())
7644
- } : null
7645
- },
7646
- spouses: spouses.map((spouse) => ({
7647
- id: spouse.id,
7648
- name: cleanGedcomName(spouse.NAME?.toValue()),
7649
- birthDate: spouse.BIRT?.DATE?.toValue() || null,
7650
- deathDate: spouse.DEAT?.DATE?.toValue() || null
7651
- })),
7652
- children: children.map((child) => ({
7653
- id: child.id,
7654
- name: cleanGedcomName(child.NAME?.toValue()),
7655
- birthDate: child.BIRT?.DATE?.toValue() || null,
7656
- deathDate: child.DEAT?.DATE?.toValue() || null
7657
- }))
7658
- };
7659
- console.log(formatJson(jsonData));
7660
- } else if (options.format === "markdown") {
7661
- console.log(`# ${formatId(individual.id)} ${name}
7662
- `);
7663
- if (birthDate || birthPlace) {
7664
- console.log(`**Born:** ${birthDate || "?"}`);
7665
- if (birthPlace) console.log(` in ${birthPlace}`);
7666
- console.log();
7667
- }
7668
- if (deathDate || deathPlace) {
7669
- console.log(`**Died:** ${deathDate || "?"}`);
7670
- if (deathPlace) console.log(` in ${deathPlace}`);
7671
- console.log();
7672
- }
7673
- if (father || mother) {
7674
- console.log("## Parents\n");
7675
- if (father) {
7676
- const fatherName = cleanGedcomName(father.NAME?.toValue());
7677
- const fatherLifespan = formatLifespan(
7678
- father.BIRT?.DATE?.toValue(),
7679
- father.DEAT?.DATE?.toValue()
7680
- );
7681
- console.log(`- **Father:** ${father.id} ${fatherName} ${fatherLifespan}`);
7682
- }
7683
- if (mother) {
7684
- const motherName = cleanGedcomName(mother.NAME?.toValue());
7685
- const motherLifespan = formatLifespan(
7686
- mother.BIRT?.DATE?.toValue(),
7687
- mother.DEAT?.DATE?.toValue()
7688
- );
7689
- console.log(`- **Mother:** ${mother.id} ${motherName} ${motherLifespan}`);
7690
- }
7691
- console.log();
7692
- }
7693
- if (spouses.length > 0) {
7694
- console.log("## Spouses\n");
7695
- spouses.forEach((spouse) => {
7696
- const spouseName = cleanGedcomName(spouse.NAME?.toValue());
7697
- const spouseLifespan = formatLifespan(
7698
- spouse.BIRT?.DATE?.toValue(),
7699
- spouse.DEAT?.DATE?.toValue()
7700
- );
7701
- console.log(`- ${spouse.id} ${spouseName} ${spouseLifespan}`);
7702
- });
7703
- console.log();
7704
- }
7705
- if (children.length > 0) {
7706
- console.log("## Children\n");
7707
- children.forEach((child) => {
7708
- const childName = cleanGedcomName(child.NAME?.toValue());
7709
- const childLifespan = formatLifespan(
7710
- child.BIRT?.DATE?.toValue(),
7711
- child.DEAT?.DATE?.toValue()
7712
- );
7713
- console.log(`- ${child.id} ${childName} ${childLifespan}`);
7714
- });
7715
- console.log();
7716
- }
7717
- } else {
7718
- console.log(formatHeader(`${formatId(individual.id)} ${formatName(name)}
7719
- `));
7720
- if (sex) {
7721
- console.log(`${formatLabel("Sex")} ${formatValue(sex === "M" ? "Male" : sex === "F" ? "Female" : sex)}`);
7722
- }
7723
- if (birthDate || birthPlace) {
7724
- console.log(`${formatLabel("Born")} ${formatDate(birthDate)}`);
7725
- if (birthPlace) {
7726
- console.log(`${formatLabel("Birth Place")} ${formatPlace(birthPlace)}`);
7727
- }
7728
- }
7729
- if (deathDate || deathPlace) {
7730
- console.log(`${formatLabel("Died")} ${formatDate(deathDate)}`);
7731
- if (deathPlace) {
7732
- console.log(`${formatLabel("Death Place")} ${formatPlace(deathPlace)}`);
7733
- }
7734
- }
7735
- if (father || mother) {
7736
- console.log();
7737
- console.log(formatHeader("Parents"));
7738
- if (father) {
7739
- const fatherName = cleanGedcomName(father.NAME?.toValue());
7740
- const fatherLifespan = formatLifespan(
7741
- father.BIRT?.DATE?.toValue(),
7742
- father.DEAT?.DATE?.toValue()
7743
- );
7744
- console.log(formatListItem(`${formatLabel("Father")} ${formatId(father.id)} ${formatName(fatherName)} ${fatherLifespan}`));
7745
- }
7746
- if (mother) {
7747
- const motherName = cleanGedcomName(mother.NAME?.toValue());
7748
- const motherLifespan = formatLifespan(
7749
- mother.BIRT?.DATE?.toValue(),
7750
- mother.DEAT?.DATE?.toValue()
7751
- );
7752
- console.log(formatListItem(`${formatLabel("Mother")} ${formatId(mother.id)} ${formatName(motherName)} ${motherLifespan}`));
7753
- }
7754
- }
7755
- if (spouses.length > 0) {
7756
- console.log();
7757
- console.log(formatHeader("Spouses"));
7758
- spouses.forEach((spouse) => {
7759
- const spouseName = cleanGedcomName(spouse.NAME?.toValue());
7760
- const spouseLifespan = formatLifespan(
7761
- spouse.BIRT?.DATE?.toValue(),
7762
- spouse.DEAT?.DATE?.toValue()
7763
- );
7764
- console.log(formatListItem(`${formatId(spouse.id)} ${formatName(spouseName)} ${spouseLifespan}`));
7765
- });
7766
- }
7767
- if (children.length > 0) {
7768
- console.log();
7769
- console.log(formatHeader("Children"));
7770
- children.forEach((child) => {
7771
- const childName = cleanGedcomName(child.NAME?.toValue());
7772
- const childLifespan = formatLifespan(
7773
- child.BIRT?.DATE?.toValue(),
7774
- child.DEAT?.DATE?.toValue()
7775
- );
7776
- console.log(formatListItem(`${formatId(child.id)} ${formatName(childName)} ${childLifespan}`));
7777
- });
7778
- }
7779
- }
7780
- } catch (error) {
7781
- handleError(error, "Failed to show individual details");
7782
- }
7783
- });
7784
- }
7785
-
7786
- // src/cli/commands/stats.ts
7787
- function registerStatsCommand(program2) {
7788
- program2.command("stats <file>").description("Generate statistics about a GEDCOM file").option("-j, --json", "Output in JSON format").action((file, options) => {
7789
- try {
7790
- const content = readGedcomFile(file);
7791
- const { gedcom: tree } = parser_default.parse(content);
7792
- const stats = tree.stats();
7793
- if (options.json) {
7794
- console.log(formatJson(stats));
7795
- } else {
7796
- console.log(formatHeader("GEDCOM File Statistics\n"));
7797
- console.log(formatLabel("Total Individuals"));
7798
- console.log(` ${formatCount(stats.totalIndividuals)}`);
7799
- console.log(formatLabel("Total Families"));
7800
- console.log(` ${formatCount(stats.totalFamilies)}`);
7801
- console.log();
7802
- console.log(formatLabel("By Gender"));
7803
- console.log(` Males: ${formatCount(stats.byGender.males)}`);
7804
- console.log(` Females: ${formatCount(stats.byGender.females)}`);
7805
- console.log(` Unknown: ${formatCount(stats.byGender.unknown)}`);
7806
- console.log();
7807
- if (stats.dateRange.earliest && stats.dateRange.latest) {
7808
- console.log(formatLabel("Date Range"));
7809
- console.log(` ${stats.dateRange.earliest} - ${stats.dateRange.latest}`);
7810
- console.log();
7811
- }
7812
- if (stats.averageLifespan) {
7813
- console.log(formatLabel("Average Lifespan"));
7814
- console.log(` ${stats.averageLifespan.toFixed(1)} years`);
7815
- console.log();
7816
- }
7817
- if (stats.topSurnames.length > 0) {
7818
- console.log(formatLabel("Most Common Surnames"));
7819
- stats.topSurnames.forEach(({ surname, count }) => {
7820
- console.log(` ${surname}: ${formatCount(count)}`);
7821
- });
7822
- console.log();
7823
- }
7824
- if (stats.topBirthPlaces.length > 0) {
7825
- console.log(formatLabel("Most Common Birth Places"));
7826
- stats.topBirthPlaces.slice(0, 5).forEach(({ place, count }) => {
7827
- console.log(` ${place}: ${formatCount(count)}`);
7828
- });
7829
- }
7830
- }
7831
- } catch (error) {
7832
- handleError(error, "Failed to generate statistics");
7833
- }
7834
- });
7835
- }
7836
-
7837
9062
  // src/cli/commands/validate.ts
7838
9063
  function registerValidateCommand(program2) {
7839
9064
  program2.command("validate <file>").description("Validate a GEDCOM file").option("-j, --json", "Output in JSON format").option("-s, --strict", "Enable strict validation").option("--fix", "Attempt to fix common issues (not implemented)").action((file, options) => {
@@ -7970,6 +9195,7 @@ var program = new Command();
7970
9195
  program.name("gedcom-parser").description("CLI tool for parsing and manipulating GEDCOM files").version(packageJson.version);
7971
9196
  registerInfoCommand(program);
7972
9197
  registerFindCommand(program);
9198
+ registerSelectCommand(program);
7973
9199
  registerShowCommand(program);
7974
9200
  registerValidateCommand(program);
7975
9201
  registerRelativesCommand(program);
@@ -7977,6 +9203,8 @@ registerExtractCommand(program);
7977
9203
  registerStatsCommand(program);
7978
9204
  registerMergeCommand(program);
7979
9205
  registerConvertCommand(program);
9206
+ registerGetCommand(program);
9207
+ registerOpenCommand(program);
7980
9208
  program.parse(process.argv);
7981
9209
  if (!process.argv.slice(2).length) {
7982
9210
  program.outputHelp();