strom-research 1.4.1 → 1.6.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/assets/lang/cs.json +160 -1
- package/assets/lang/de.json +160 -1
- package/assets/method/narrate.md +2 -1
- package/assets/method/recording.md +3 -1
- package/dist/agents/files.js +8 -1
- package/dist/cli/human.js +18 -0
- package/dist/cli/main.js +9 -0
- package/dist/cli/menu-links.js +249 -0
- package/dist/cli/menu-parts.js +6 -3
- package/dist/cli/menu-research.js +185 -7
- package/dist/cli/menu-settings.js +17 -3
- package/dist/cli/menu-waiting.js +61 -47
- package/dist/cli/menu.js +3 -2
- package/dist/cli/ui.js +159 -1
- package/dist/cli/wizard.js +34 -1
- package/dist/commands/analysis.js +6 -2
- package/dist/commands/browse.js +5 -19
- package/dist/commands/checks.js +7 -1
- package/dist/commands/guide.js +15 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/intake.js +3 -0
- package/dist/commands/links.js +367 -0
- package/dist/commands/output.js +7 -1
- package/dist/commands/people.js +58 -1
- package/dist/commands/research.js +113 -24
- package/dist/commands/setup.js +13 -2
- package/dist/commands/start.js +6 -2
- package/dist/commands/story.js +17 -1
- package/dist/commands/sync.js +243 -0
- package/dist/core/actions.js +15 -0
- package/dist/core/config.js +5 -0
- package/dist/core/dialog.js +25 -0
- package/dist/core/excerpt.js +53 -5
- package/dist/core/frontier.js +10 -1
- package/dist/core/links.js +357 -0
- package/dist/core/live.js +156 -7
- package/dist/core/phrases.js +1 -0
- package/dist/core/review.js +43 -5
- package/dist/core/schema.js +1 -0
- package/dist/core/self.js +17 -0
- package/dist/core/session.js +6 -0
- package/dist/core/stromapp.js +38 -0
- package/dist/core/sync.js +876 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/uninstall.js +3 -0
- package/dist/core/validate.js +4 -0
- package/dist/gedcom/export.js +66 -1
- package/dist/gedcom/validate.js +13 -4
- package/package.json +1 -1
package/dist/cli/menu-waiting.js
CHANGED
|
@@ -12,11 +12,63 @@ import { openForUser } from "../core/open.js";
|
|
|
12
12
|
const SHOWN = 9;
|
|
13
13
|
/** An answer is a note on the task (500 characters, with what it asked). */
|
|
14
14
|
const ANSWER_MAX = 300;
|
|
15
|
-
|
|
15
|
+
/** What to do for a task that waits: the images to save and where, or what it asks. */
|
|
16
|
+
function whatToDo(ctx, lang, tree, x) {
|
|
16
17
|
const t = translator(lang);
|
|
17
|
-
|
|
18
|
+
if (!x.awaits)
|
|
19
|
+
return [` ${t("ui.waiting.do", { what: x.waitingOn || t("ui.waiting.ask") })}`];
|
|
20
|
+
const b = tree.get(x.awaits.recordset);
|
|
21
|
+
return [
|
|
22
|
+
` ${t("ui.waiting.images", { images: x.awaits.images, book: b?.title ?? x.awaits.recordset })}${b?.url ? ` · ${b.url}` : ""}`,
|
|
23
|
+
` ${t("ui.wait.save", { dir: `${ctx.display(inbox(ctx, x) ?? `inbox/${x.awaits.folder}`)}${path.sep}` })}`,
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
function inbox(ctx, x) {
|
|
18
27
|
const shared = ctx.settings.shared()?.value;
|
|
19
|
-
|
|
28
|
+
return shared && x.awaits ? path.join(shared, "inbox", x.awaits.folder) : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** One task that waits, taken up: its folder opened for the images, or the person's answer and the task back in the queue. */
|
|
31
|
+
async function takeUp(ctx, run, lang, root, x) {
|
|
32
|
+
const t = translator(lang);
|
|
33
|
+
const out = (line) => ctx.io.stdout(line + "\n");
|
|
34
|
+
if (x.awaits) {
|
|
35
|
+
const dir = inbox(ctx, x);
|
|
36
|
+
if (dir)
|
|
37
|
+
openForUser(dir, ctx.env);
|
|
38
|
+
return void out(t("ui.waiting.opened", { dir: dir ? ctx.display(dir) : `inbox/${x.awaits.folder}` }));
|
|
39
|
+
}
|
|
40
|
+
let answer = "";
|
|
41
|
+
for (;;) {
|
|
42
|
+
answer = (await ctx.ask(t("ui.waiting.write"))).trim();
|
|
43
|
+
if (!answer || answer === "0")
|
|
44
|
+
return;
|
|
45
|
+
// kept as a note on the task, with what it asked: short — a longer story is for a conversation
|
|
46
|
+
if (answer.length <= ANSWER_MAX)
|
|
47
|
+
break;
|
|
48
|
+
out(t("ui.waiting.long", { n: ANSWER_MAX }));
|
|
49
|
+
if (ctx.io.answers !== undefined && ctx.io.answers.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// meanwhile taken up by an agent (done, dropped, back in the queue): said, nothing written
|
|
53
|
+
if (Tree.open(root, ctx.env).get(x.id)?.state !== "waiting")
|
|
54
|
+
return void out(t("ui.waiting.gone"));
|
|
55
|
+
if ((await run(["task", "wake", x.id, `--answer=${answer}`], true)) !== 0)
|
|
56
|
+
return;
|
|
57
|
+
out(t("ui.waiting.back"));
|
|
58
|
+
await offerChat(ctx, run, lang, root, t("ui.waiting.chat"), t("ui.waiting.say", { what: truncate(x.what, 80) }));
|
|
59
|
+
}
|
|
60
|
+
/** One task that waits, named by a link of the Strom app: what it asks, then taken up as in the menu. False: it waits no more. */
|
|
61
|
+
export async function waitingTask(ctx, run, lang, root, id) {
|
|
62
|
+
const tree = Tree.open(root, ctx.env);
|
|
63
|
+
const x = tree.get(id);
|
|
64
|
+
if (!x || x.state !== "waiting")
|
|
65
|
+
return false;
|
|
66
|
+
ctx.io.stdout([` ${x.what}`, ...whatToDo(ctx, lang, tree, x), ...(x.awaits ? ["", translator(lang)("ui.wait.how1"), translator(lang)("ui.wait.how2")] : []), ""].join("\n") + "\n");
|
|
67
|
+
await takeUp(ctx, run, lang, root, x);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
export async function waitingForYou(ctx, run, lang, root) {
|
|
71
|
+
const t = translator(lang);
|
|
20
72
|
await subMenu(ctx, lang, () => {
|
|
21
73
|
const tree = Tree.open(root, ctx.env);
|
|
22
74
|
const waiting = tree.list("task").filter((x) => x.state === "waiting");
|
|
@@ -25,55 +77,17 @@ export async function waitingForYou(ctx, run, lang, root) {
|
|
|
25
77
|
const shown = waiting.slice(0, SHOWN);
|
|
26
78
|
const text = [t("ui.waiting.title", { n: waiting.length })];
|
|
27
79
|
for (const [k, x] of shown.entries()) {
|
|
28
|
-
text.push("", ` ${k + 1}. ${x.what}
|
|
29
|
-
if (x.awaits) {
|
|
30
|
-
const b = tree.get(x.awaits.recordset);
|
|
31
|
-
text.push(` ${t("ui.waiting.images", { images: x.awaits.images, book: b?.title ?? x.awaits.recordset })}${b?.url ? ` · ${b.url}` : ""}`);
|
|
32
|
-
text.push(` ${t("ui.wait.save", { dir: `${ctx.display(inbox(x) ?? `inbox/${x.awaits.folder}`)}${path.sep}` })}`);
|
|
33
|
-
}
|
|
34
|
-
else
|
|
35
|
-
text.push(` ${t("ui.waiting.do", { what: x.waitingOn || t("ui.waiting.ask") })}`);
|
|
80
|
+
text.push("", ` ${k + 1}. ${x.what}`, ...whatToDo(ctx, lang, tree, x));
|
|
36
81
|
}
|
|
37
82
|
if (shown.some((x) => x.awaits))
|
|
38
83
|
text.push("", t("ui.wait.how1"), t("ui.wait.how2"));
|
|
39
84
|
if (waiting.length > SHOWN)
|
|
40
85
|
text.push("", t("ui.waiting.more", { n: waiting.length - SHOWN }));
|
|
41
|
-
const items = shown.map((x, k) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dir = inbox(x);
|
|
47
|
-
if (dir)
|
|
48
|
-
openForUser(dir, ctx.env);
|
|
49
|
-
out(t("ui.waiting.opened", { dir: dir ? ctx.display(dir) : `inbox/${x.awaits.folder}` }));
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
: {
|
|
53
|
-
key: String(k + 1),
|
|
54
|
-
label: t("ui.waiting.answer", { k: k + 1, what: truncate(x.what, 60) }),
|
|
55
|
-
act: async () => {
|
|
56
|
-
let answer = "";
|
|
57
|
-
for (;;) {
|
|
58
|
-
answer = (await ctx.ask(t("ui.waiting.write"))).trim();
|
|
59
|
-
if (!answer || answer === "0")
|
|
60
|
-
return;
|
|
61
|
-
// kept as a note on the task, with what it asked: short — a longer story is for a conversation
|
|
62
|
-
if (answer.length <= ANSWER_MAX)
|
|
63
|
-
break;
|
|
64
|
-
out(t("ui.waiting.long", { n: ANSWER_MAX }));
|
|
65
|
-
if (ctx.io.answers !== undefined && ctx.io.answers.length === 0)
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
// meanwhile taken up by an agent (done, dropped, back in the queue): said, nothing written
|
|
69
|
-
if (Tree.open(root, ctx.env).get(x.id)?.state !== "waiting")
|
|
70
|
-
return void out(t("ui.waiting.gone"));
|
|
71
|
-
if ((await run(["task", "wake", x.id, `--answer=${answer}`], true)) !== 0)
|
|
72
|
-
return;
|
|
73
|
-
out(t("ui.waiting.back"));
|
|
74
|
-
await offerChat(ctx, run, lang, root, t("ui.waiting.chat"), t("ui.waiting.say", { what: truncate(x.what, 80) }));
|
|
75
|
-
},
|
|
76
|
-
});
|
|
86
|
+
const items = shown.map((x, k) => ({
|
|
87
|
+
key: String(k + 1),
|
|
88
|
+
label: x.awaits ? t("ui.waiting.folder", { k: k + 1 }) : t("ui.waiting.answer", { k: k + 1, what: truncate(x.what, 60) }),
|
|
89
|
+
act: () => takeUp(ctx, run, lang, root, x),
|
|
90
|
+
}));
|
|
77
91
|
return { title: `${text.join("\n")}\n\n${t("ui.waiting.pick")}`, items };
|
|
78
92
|
});
|
|
79
93
|
}
|
package/dist/cli/menu.js
CHANGED
|
@@ -219,7 +219,8 @@ export async function runMenu(ctx, run) {
|
|
|
219
219
|
},
|
|
220
220
|
}, {
|
|
221
221
|
key: "4",
|
|
222
|
-
|
|
222
|
+
// the Strom app named only for someone who has not said no to it
|
|
223
|
+
label: t(stromAppState(ctx.settings) === "no" ? "ui.menu.more" : "ui.menu.more.app"),
|
|
223
224
|
act: async () => addToResearch(ctx, run, lang, root),
|
|
224
225
|
}, {
|
|
225
226
|
key: "4",
|
|
@@ -283,7 +284,7 @@ export async function runMenu(ctx, run) {
|
|
|
283
284
|
if (i === undefined)
|
|
284
285
|
return;
|
|
285
286
|
const quit = await guarded(ctx, lang, items[i].act);
|
|
286
|
-
if (quit === true) {
|
|
287
|
+
if (quit === true || quit === "quit") {
|
|
287
288
|
out(t("ui.menu.bye"));
|
|
288
289
|
return;
|
|
289
290
|
}
|
package/dist/cli/ui.js
CHANGED
|
@@ -7,6 +7,7 @@ export const UI = {
|
|
|
7
7
|
"ui.yes": "y",
|
|
8
8
|
"ui.no": "n",
|
|
9
9
|
"ui.enter": "Press Enter to go back to the menu.",
|
|
10
|
+
"ui.enter.close": "Press Enter to close this window.",
|
|
10
11
|
"ui.choose": "Choose",
|
|
11
12
|
"ui.bad.choice": "Please type one of the numbers.",
|
|
12
13
|
"ui.dialog.title": "Strom research",
|
|
@@ -88,6 +89,8 @@ export const UI = {
|
|
|
88
89
|
"ui.setup.level.sure": "Really allow everything?",
|
|
89
90
|
"ui.setup.shortcut": "Put a \"Strom research\" shortcut on the desktop?",
|
|
90
91
|
"ui.setup.shortcut.done": "✓ Shortcut: {file}",
|
|
92
|
+
"ui.setup.links": "Let the Strom app start the research on this computer? (One click sends your edits back, opens an excerpt in full quality, the agent or a person's review — each asked here first.)",
|
|
93
|
+
"ui.setup.links.done": "✓ Links from the Strom app lead to the research here.",
|
|
91
94
|
"ui.setup.skill": "✓ {agent} now knows strom in any folder.",
|
|
92
95
|
"ui.setup.app": "✓ The Strom app is on this computer — the menu opens the research in it, and while the agent works the app follows it live.",
|
|
93
96
|
"ui.setup.done": "All set.",
|
|
@@ -177,6 +180,10 @@ export const UI = {
|
|
|
177
180
|
"ui.doc.level": "the agent may",
|
|
178
181
|
"ui.doc.model": "model",
|
|
179
182
|
"ui.doc.shortcut": "desktop shortcut",
|
|
183
|
+
"ui.doc.links": "links from the Strom app",
|
|
184
|
+
"ui.doc.links.ours": "yes",
|
|
185
|
+
"ui.doc.links.other": "lead to another program (or where strom was before)",
|
|
186
|
+
"ui.doc.links.none": "none",
|
|
180
187
|
"ui.doc.app": "Strom app",
|
|
181
188
|
"ui.doc.tree": "this family tree",
|
|
182
189
|
"ui.doc.innode": "strom {version} in Node {node}",
|
|
@@ -241,6 +248,18 @@ export const UI = {
|
|
|
241
248
|
"ui.review.pick": "Which one?",
|
|
242
249
|
"ui.review.family": "With their spouse and children?",
|
|
243
250
|
"ui.review.run": "Let the agent work on it now, on its own? ({n} tasks)",
|
|
251
|
+
"ui.menu.unproven": "People without a record of their own ({n})",
|
|
252
|
+
"ui.unproven.title": "No record of their own proves these people yet:",
|
|
253
|
+
"ui.unproven.named": "named in a record",
|
|
254
|
+
"ui.unproven.leads": "only leads",
|
|
255
|
+
"ui.unproven.none": "no record",
|
|
256
|
+
"ui.unproven.tasks": "work on them is under way",
|
|
257
|
+
"ui.unproven.living": "Most likely living (born less than {years} years ago) — the family's to tell, no tasks: {names}",
|
|
258
|
+
"ui.unproven.how": "What should the agent do?",
|
|
259
|
+
"ui.unproven.all": "All of them, step by step ({n} at a time)",
|
|
260
|
+
"ui.unproven.pick": "Only the ones I pick",
|
|
261
|
+
"ui.unproven.numbers": "Their numbers from the list (e.g. 1 3 5-7, or their IDs; Enter or 0 goes back)",
|
|
262
|
+
"ui.unproven.bad": "Not in the list: {x}",
|
|
244
263
|
"ui.o.reread": "second reading",
|
|
245
264
|
"ui.o.reread.offer": "{n} facts rest only on readings by another model ({models}); the user reads with {model} now — offer it once, in a sentence: strom review <whom> --reread (a task per person; the user decides)",
|
|
246
265
|
"ui.o.stories": "stories",
|
|
@@ -254,6 +273,7 @@ export const UI = {
|
|
|
254
273
|
"ui.setup.stories.no": "No — just the family tree and its sources",
|
|
255
274
|
"ui.uninstall.agent": "what {agent} knows of strom",
|
|
256
275
|
"ui.uninstall.shortcut": "the shortcut",
|
|
276
|
+
"ui.uninstall.links": "the links from the Strom app",
|
|
257
277
|
"ui.uninstall.git": "strom's own git",
|
|
258
278
|
"ui.uninstall.path": "the installer's PATH entry",
|
|
259
279
|
"ui.uninstall.program": "the program",
|
|
@@ -278,6 +298,7 @@ export const UI = {
|
|
|
278
298
|
"ui.update.downloading": "Downloading strom {version}…",
|
|
279
299
|
"ui.update.failed": "The update did not work: {detail}. strom stays as it was.",
|
|
280
300
|
"ui.update.done": "✓ strom is updated: {previous} → {version}.",
|
|
301
|
+
"ui.update.restart": "strom is updated — this menu still runs the old version. Start strom again (or the shortcut \"{shortcut}\"), and the new version is at work.",
|
|
281
302
|
"ui.o.update": "update",
|
|
282
303
|
"ui.o.update.new": "{version} is out (this is {current}) — tell the user; strom update installs it with their yes",
|
|
283
304
|
"ui.app.where.installed": "the installed app",
|
|
@@ -380,6 +401,61 @@ export const UI = {
|
|
|
380
401
|
"ui.browse.plan": "What the agent will do next",
|
|
381
402
|
"ui.review.new": "{name} — {n} new task(s):",
|
|
382
403
|
"ui.review.nothing": "{name} — nothing new to review",
|
|
404
|
+
"ui.sync.title": "{file}: {n} change(s) against the research",
|
|
405
|
+
"ui.sync.base": "Compared with what the research gave the Strom app ({head}): only what was changed since counts as your edit.",
|
|
406
|
+
"ui.sync.nobase": "The file does not say which state of the research it came from: additions are taken, differences only shown (pick them with --only).",
|
|
407
|
+
"ui.sync.headmissing": "The research's history does not have the state {head} the file names: additions are taken, differences only shown (pick them with --only).",
|
|
408
|
+
"ui.sync.partial": "The file has far fewer people than it was given: nothing is taken as removed.",
|
|
409
|
+
"ui.sync.strangers": "These IDs are not the research's people of that name — taken as new people: {ids}",
|
|
410
|
+
"ui.sync.nothing": "{file}: nothing new — the research has everything in it.",
|
|
411
|
+
"ui.sync.person.new": "{n}. new person: {name}",
|
|
412
|
+
"ui.sync.fact.new": "{n}. {who}: new — {fact}",
|
|
413
|
+
"ui.sync.fact.changed": "{n}. {who}: {kind} changed — {was} → {now}",
|
|
414
|
+
"ui.sync.fact.differs": "{n}. {who}: {kind} differs — the research {was}, the file {now}",
|
|
415
|
+
"ui.sync.fact.gone": "{n}. {who}: no longer in it — {fact}",
|
|
416
|
+
"ui.sync.name.new": "{n}. {who}: another name — {text}",
|
|
417
|
+
"ui.sync.sex.changed": "{n}. {who}: sex {text}",
|
|
418
|
+
"ui.sync.note.new": "{n}. {who}: a note — “{text}”",
|
|
419
|
+
"ui.sync.family.new": "{n}. a new family: {who}{kids}",
|
|
420
|
+
"ui.sync.child.new": "{n}. {who}: a child — {child}",
|
|
421
|
+
"ui.sync.child.gone": "{n}. {who}: no longer their child — {child}",
|
|
422
|
+
"ui.sync.person.gone": "{n}. no longer in it: {name}",
|
|
423
|
+
"ui.sync.do.add": "added as a lead",
|
|
424
|
+
"ui.sync.do.correct": "corrects the research's lead",
|
|
425
|
+
"ui.sync.do.conflict": "a conflict for you to decide (the research has it from a record)",
|
|
426
|
+
"ui.sync.do.user": "your edit wins (the record's fact withdrawn with the reason)",
|
|
427
|
+
"ui.sync.do.pick": "only when you pick it (--only)",
|
|
428
|
+
"ui.sync.do.report": "nothing changes — tell the agent what you meant",
|
|
429
|
+
"ui.sync.edits": "Your edits to facts a record proves: {mode} (strom config set sync.edits conflict|user)",
|
|
430
|
+
"ui.sync.mode.conflict": "a conflict for you to decide",
|
|
431
|
+
"ui.sync.mode.user": "your edit wins",
|
|
432
|
+
"ui.sync.next": "To write it: strom sync {file} --apply · only some: --apply --only 1,3",
|
|
433
|
+
"ui.sync.done": "{n} change(s) from {file} written ({input}) — to take them back: strom sync undo {input}",
|
|
434
|
+
"ui.sync.undone": "Sync {input} taken back: {n} step(s).",
|
|
435
|
+
"ui.sync.wasundone": "Sync {input} was taken back already.",
|
|
436
|
+
"ui.sync.bridge.large": "the tree is too large to send — export it as GEDCOM and load it in the research (strom sync <file>)",
|
|
437
|
+
"ui.sync.bridge.other": "this tree is of another research",
|
|
438
|
+
"ui.sync.bridge.foreign": "this does not look like the research's family tree — export it and load it in the research as another family tree",
|
|
439
|
+
"ui.sync.bridge.empty": "the tree came empty or unreadable",
|
|
440
|
+
"ui.sync.wait": "The Strom app opens with this research — choose which tree to send, and confirm there. Waiting for it (at most {min} min; Ctrl+C stops) …",
|
|
441
|
+
"ui.sync.wait.open": "Nothing shows in the app (it was open already)? Open this address in its window's browser: {url}",
|
|
442
|
+
"ui.sync.waited": "The Strom app did not send the tree within {min} min — try again: strom sync --app, or export it as GEDCOM: strom sync <file>",
|
|
443
|
+
"ui.sync.got": "The Strom app sent the tree.",
|
|
444
|
+
"ui.sync.nothing.unchanged": "The Strom app sends nothing: the tree has not changed since it was last loaded from the research.",
|
|
445
|
+
"ui.sync.nothing.cancelled": "Nothing was sent from the Strom app (cancelled there).",
|
|
446
|
+
"ui.sync.nothing.no-tree": "The Strom app has no tree of this research — open it there first (the Strom app item of the menu), or export the tree and load the file.",
|
|
447
|
+
"ui.sync.nosend": "This copy of the Strom app cannot send the tree yet (it comes with a newer version) — export it in the app as GEDCOM and load it: strom sync <file>",
|
|
448
|
+
"ui.sync.from": "From where?",
|
|
449
|
+
"ui.sync.from.app": "Straight from the Strom app (it sends the tree itself)",
|
|
450
|
+
"ui.sync.from.file": "From a file (.ged, .json)",
|
|
451
|
+
"ui.sync.again": "The research took this file in already ({input}) — once more: --again",
|
|
452
|
+
"ui.sync.source.app": "Edits in the Strom app ({file}, {day})",
|
|
453
|
+
"ui.sync.source.file": "Family tree {file} ({day})",
|
|
454
|
+
"ui.review.living": "{who}: most likely alive (born less than {years} years ago, no death recorded). The family knows more than the registers, and those of recent years are mostly closed. Review all the same?",
|
|
455
|
+
"ui.review.living.no": "Nothing added.",
|
|
456
|
+
"ui.review.unproven.none": "Everyone in the tree rests on a record of their own — or strom has nothing more to propose for those who do not (strom person list --unproven).",
|
|
457
|
+
"ui.review.unproven.left": "{n} more without a record of their own: strom review --unproven again takes the next ones once these are done",
|
|
458
|
+
"ui.review.unproven.left.human": "{n} more people wait without a record of their own — the next ones come when these are done.",
|
|
383
459
|
"ui.review.would": " would add: {level} · {what}",
|
|
384
460
|
"ui.review.dry": "(dry run — nothing written)",
|
|
385
461
|
"ui.review.covered": " {name}: already covered by {task} ({state}) — {what}",
|
|
@@ -439,10 +515,92 @@ export const UI = {
|
|
|
439
515
|
"ui.consent.remote": "Start the Claude Code sessions strom opens with Remote Control, so that you can follow and steer them from claude.ai or the Claude app on your phone?",
|
|
440
516
|
"ui.consent.hook.on": "Let the hook “{name}” be told of everything saved into your researches? It is a program on this computer and does with it what it was written to do.",
|
|
441
517
|
"ui.consent.hook.off": "Stop telling the hook “{name}” what is saved?",
|
|
442
|
-
"ui.menu.more": "Add to the research: material, a new direction, one person reviewed",
|
|
518
|
+
"ui.menu.more": "Add to the research: material, a new direction, one person reviewed, another family tree",
|
|
519
|
+
"ui.menu.more.app": "Add to the research: material, a new direction, one person reviewed, edits from the Strom app",
|
|
443
520
|
"ui.more.title": "Add to the research",
|
|
444
521
|
"ui.more.intake": "Material from the family: documents, photos, a family tree from another program, what you know",
|
|
445
522
|
"ui.more.research": "A new direction: the ancestors or descendants of someone else, or one question",
|
|
523
|
+
"ui.more.sync": "Take in edits from the Strom app or another family tree (.ged, .json)",
|
|
524
|
+
"ui.link.ok": "OK",
|
|
525
|
+
"ui.link.bad": "This link from the Strom app could not be used ({why}). Nothing was done.",
|
|
526
|
+
"ui.link.notree": "The research this link is for is not on this computer (or strom does not know its folder). Nothing was done.",
|
|
527
|
+
"ui.link.which": "This research is here more than once. Which one should take the edits?",
|
|
528
|
+
"ui.link.noclip": "This excerpt of {source} is no longer in the research (it was made again or taken away). Open the Strom app from the research again to get the current ones.",
|
|
529
|
+
"ui.link.noscan": "The scan of this excerpt of {source} is not on this computer, and the research knows no page of it online.",
|
|
530
|
+
"ui.link.noterminal": "strom could not open a terminal window. Start strom and choose: {item} — Straight from the Strom app.",
|
|
531
|
+
"ui.link.on.done": "✓ Links from the Strom app lead to the research here.",
|
|
532
|
+
"ui.link.on.failed": "The links from the Strom app could not be set up on this computer.",
|
|
533
|
+
"ui.link.off.done": "Links from the Strom app no longer lead to the research here.",
|
|
534
|
+
"ui.link.off.failed": "The links from the Strom app could not be taken off this computer.",
|
|
535
|
+
"ui.link.which.one": "This research is here more than once. Which one?",
|
|
536
|
+
"ui.link.noterminal.any": "strom could not open a terminal window. Start strom (the research {name}) and go on from its menu.",
|
|
537
|
+
"ui.link.noterminal.new": "strom could not open a terminal window. Start strom, then try again from the Strom app.",
|
|
538
|
+
"ui.link.asks": "The Strom app asks for: {what}\nResearch: {name}",
|
|
539
|
+
"ui.link.go": "Go on?",
|
|
540
|
+
"ui.link.cost": "The agent works on your subscription or AI credit.",
|
|
541
|
+
"ui.link.tasks": "The research writes tasks for the agent — nothing is searched and nothing is paid now.",
|
|
542
|
+
"ui.link.what.chat": "a conversation with the agent ({agent}) about the research.",
|
|
543
|
+
"ui.link.what.chat.person": "a conversation with the agent ({agent}) about {person}.",
|
|
544
|
+
"ui.link.chat.say": "Let us look at {person}: tell me briefly what the research knows of this person, and ask me what I want to find out.",
|
|
545
|
+
"ui.link.what.review.person": "look at {person} again.",
|
|
546
|
+
"ui.link.what.review.family": "look at {person} again, with the partners and children.",
|
|
547
|
+
"ui.link.what.review.line": "look at {person} again, with the ancestors.",
|
|
548
|
+
"ui.link.what.research.ancestors": "search for the ancestors of {person} (a new direction of the research).",
|
|
549
|
+
"ui.link.what.research.descendants": "search for the descendants of {person} (a new direction of the research).",
|
|
550
|
+
"ui.link.noperson": "{person} is not in this research (or was taken out of it). Nothing was done.",
|
|
551
|
+
"ui.link.notask": "This task no longer waits for you (it was answered, or an agent took it up). Nothing was done.",
|
|
552
|
+
"ui.link.task.none": "Task {task} is not in this research. Nothing was done.",
|
|
553
|
+
"ui.link.task.cannot": "This task cannot be changed that way now (it changed meanwhile). Nothing was done.",
|
|
554
|
+
"ui.link.what.task.park": "put the task aside: {what}",
|
|
555
|
+
"ui.link.what.task.drop": "give up the task: {what}",
|
|
556
|
+
"ui.link.what.task.wake": "bring the task back into the queue: {what}",
|
|
557
|
+
"ui.link.task.why": "Why? (a few words, kept on the task)",
|
|
558
|
+
"ui.link.task.park.default": "Put aside in the Strom app.",
|
|
559
|
+
"ui.link.task.drop.default": "Given up in the Strom app.",
|
|
560
|
+
"ui.link.task.park.done": "✓ The task is put aside.",
|
|
561
|
+
"ui.link.task.drop.done": "✓ The task is given up (kept, with the reason).",
|
|
562
|
+
"ui.link.task.wake.done": "✓ The task is back in the agent's queue.",
|
|
563
|
+
"ui.link.sessions.title": "The agent's sessions — {name}",
|
|
564
|
+
"ui.link.sessions.none": "The agent has not worked on this research yet.",
|
|
565
|
+
"ui.link.sessions.month": "This month: {n} sessions, {cost}",
|
|
566
|
+
"ui.link.sessions.more": "… and {n} earlier",
|
|
567
|
+
"ui.link.conflict.none": "Conflict {id} is not in this research. Nothing was done.",
|
|
568
|
+
"ui.link.conflict.decided": "This conflict is decided already: {resolution}",
|
|
569
|
+
"ui.link.what.conflict.decide": "decide the conflict: {title}",
|
|
570
|
+
"ui.link.what.conflict.agent": "leave the conflict to the agent ({agent}): {title}",
|
|
571
|
+
"ui.link.conflict.pick": "Which is right?",
|
|
572
|
+
"ui.link.conflict.other": "Something else (I write it)",
|
|
573
|
+
"ui.link.conflict.own": "What is right?",
|
|
574
|
+
"ui.link.conflict.why": "Why? (what decides it)",
|
|
575
|
+
"ui.link.conflict.by": "{why} (the user's decision)",
|
|
576
|
+
"ui.link.conflict.done": "✓ The conflict is decided.",
|
|
577
|
+
"ui.link.conflict.say": "Look at the conflict {id} ({title}): weigh the records, decide it with strom conflict resolve, then tell me what you concluded and why.",
|
|
578
|
+
"ui.link.story.none": "{person} has no story yet. Nothing was done.",
|
|
579
|
+
"ui.link.story.final": "The story of {person} is approved already.",
|
|
580
|
+
"ui.link.what.story": "approve the story of {person} as it is (it will no longer be a draft).",
|
|
581
|
+
"ui.link.story.done": "✓ The story is approved. Open the research in the Strom app again and it is there without the draft label.",
|
|
582
|
+
"ui.link.sync.none": "{input} is not a sending from the Strom app in this research. Nothing was done.",
|
|
583
|
+
"ui.link.sync.was": "This sending was taken back already.",
|
|
584
|
+
"ui.link.what.sync": "take back what the research took from the Strom app on {day} ({n} changes).",
|
|
585
|
+
"ui.link.new.title": "The Strom app asks to start a research with one of its family trees.",
|
|
586
|
+
"ui.link.new.name": "Name of the research (the family, e.g. Novák)",
|
|
587
|
+
"ui.link.new.exists": "A research named “{name}” is here already, with people in it. Choose another name.",
|
|
588
|
+
"ui.link.new.wait": "Waiting for the Strom app to hand the tree over — confirm it there (at most {min} min; Ctrl+C stops).",
|
|
589
|
+
"ui.link.new.none": "The Strom app handed no tree over. The research “{name}” is here, still empty: start again from the app, or add to it from strom's menu.",
|
|
590
|
+
"ui.link.new.waited": "No tree came from the Strom app within {min} min. The research “{name}” is here, still empty.",
|
|
591
|
+
"ui.link.new.empty": "The tree the Strom app sent has no people, so there is nothing to research. The research “{name}” is here, still empty.",
|
|
592
|
+
"ui.link.new.failed": "The Strom app could not hand the tree over. The research “{name}” is here, still empty: try again from the app.",
|
|
593
|
+
"ui.link.new.taken": "✓ The family tree is in the research “{name}”: {persons} people, {families} families — as leads; what records prove, the agent finds.",
|
|
594
|
+
"ui.link.new.back": "The Strom app shows the tree now as the tree of the research.",
|
|
595
|
+
"ui.link.new.chat": "Start the conversation with the agent now?",
|
|
596
|
+
"ui.link.new.say": "I handed our family tree over from the Strom app — look at what is in it and suggest where to begin.",
|
|
597
|
+
"ui.more.sync.noapp": "Take in another family tree's changes (.ged, .json)",
|
|
598
|
+
"ui.sync.path.noapp": "Drag the family tree file (.ged or .json) here, or type its path (Enter or 0 goes back)",
|
|
599
|
+
"ui.sync.path": "Drag the file exported from the Strom app (.ged or .json) here, or type its path (Enter or 0 goes back)",
|
|
600
|
+
"ui.sync.how": "Write it into the research?",
|
|
601
|
+
"ui.sync.all": "Yes, everything as shown",
|
|
602
|
+
"ui.sync.some": "Only the ones I pick",
|
|
603
|
+
"ui.sync.app": "Open the research in the Strom app again (the Strom app item of the menu): it asks whether to update the tree — then it has your edits as the research took them.",
|
|
446
604
|
"ui.intake.what": "What do you bring?",
|
|
447
605
|
"ui.intake.files": "Files or a folder from this computer",
|
|
448
606
|
"ui.intake.text": "What you know — names, dates, places, family memories",
|
package/dist/cli/wizard.js
CHANGED
|
@@ -13,9 +13,10 @@ import { agentsHere, DESKTOP_APPS } from "../core/apps.js";
|
|
|
13
13
|
import { PROFILES } from "../agents/profiles.js";
|
|
14
14
|
import { writeStored, PERMISSION_LEVELS } from "../core/config.js";
|
|
15
15
|
import { globalTargets, installGlobal } from "../agents/global.js";
|
|
16
|
-
import { noticeStromApp, researchUrl, stromAppState, stromAppUrl } from "../core/stromapp.js";
|
|
16
|
+
import { appOpensLinks, noticeStromApp, researchUrl, stromAppState, stromAppUrl } from "../core/stromapp.js";
|
|
17
17
|
import { openForUser } from "../core/open.js";
|
|
18
18
|
import { createShortcut } from "../core/shortcut.js";
|
|
19
|
+
import { linkFiles, linkHandlerState, registerLinks } from "../core/links.js";
|
|
19
20
|
import { desktopDir } from "../core/paths.js";
|
|
20
21
|
import { ensureShared } from "../commands/setup.js";
|
|
21
22
|
import { moveHome, planMove, repointSettings, sameFolder } from "../core/relocate.js";
|
|
@@ -152,10 +153,42 @@ export async function setupWizard(ctx) {
|
|
|
152
153
|
out(ui(lang, "ui.setup.app"));
|
|
153
154
|
else
|
|
154
155
|
await askStromApp(ctx, lang, first ? undefined : ui(lang, "ui.keep"));
|
|
156
|
+
// 9. The Strom app may start the research here (strom-research:// links): asked while it is wanted; refreshed quietly after.
|
|
157
|
+
if (stromAppState(s) !== "no" && appOpensLinks(s))
|
|
158
|
+
await offerLinks(ctx, lang);
|
|
155
159
|
out();
|
|
156
160
|
out(`${ui(lang, "ui.setup.done")} (${langName(lang, lang)} · ${ctx.display(home)})`);
|
|
157
161
|
return { home, lang, ...(agent ? { agent } : {}), permissions: level };
|
|
158
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* strom-research:// links: set up on the person's yes, the answer kept (a no is not asked again unasked:
|
|
165
|
+
* `ask` — the wizard, doctor --fix — asks all the same, suggesting what they said). Set up before by strom: set up
|
|
166
|
+
* again quietly.
|
|
167
|
+
*/
|
|
168
|
+
export async function offerLinks(ctx, lang, opts = { ask: true }) {
|
|
169
|
+
if (linkHandlerState(ctx.env) === "ours")
|
|
170
|
+
return;
|
|
171
|
+
const said = ctx.settings.config.links;
|
|
172
|
+
const made = linkFiles(ctx.env).length > 0;
|
|
173
|
+
if (!made) {
|
|
174
|
+
if (said === "no" && !opts.ask)
|
|
175
|
+
return;
|
|
176
|
+
const yes = await ctx.confirm(ui(lang, "ui.setup.links"), said !== "no");
|
|
177
|
+
ctx.settings.reload();
|
|
178
|
+
ctx.settings.config.links = yes ? "yes" : "no";
|
|
179
|
+
ctx.settings.save();
|
|
180
|
+
if (!yes)
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
let done = false;
|
|
184
|
+
try {
|
|
185
|
+
done = registerLinks(ctx.env);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
done = false;
|
|
189
|
+
}
|
|
190
|
+
ctx.io.stdout(ui(lang, done ? "ui.setup.links.done" : "ui.link.on.failed") + "\n");
|
|
191
|
+
}
|
|
159
192
|
/**
|
|
160
193
|
* Does the person want the Strom app (not found on this computer)? Yes and
|
|
161
194
|
* install it now: it opens in the browser and strom says where to click; yes
|
|
@@ -261,9 +261,10 @@ register({
|
|
|
261
261
|
options: [
|
|
262
262
|
{ name: "about", type: "string", multiple: true, value: "<who>", description: "person/record concerned (repeatable)" },
|
|
263
263
|
{ name: "claim", type: "string", multiple: true, value: "<S…: value>", description: 'a claim, e.g. "S0027: aged 27 at marriage 1839" (repeat for each)' },
|
|
264
|
+
{ name: "fact", type: "string", value: "<TAG>", description: "the fact it is about, by its GEDCOM tag: BIRT, DEAT, BAPM, BURI, MARR, NAME, SEX… (the Strom app shows it there)" },
|
|
264
265
|
{ name: "note", type: "string", value: "<text>", description: "short note" },
|
|
265
266
|
],
|
|
266
|
-
examples: ['strom conflict add "Rok narození Josefa" --about P0002 --claim "S0001: 27 let při sňatku 1839" --claim "S0002: 70 let při úmrtí 1883"'],
|
|
267
|
+
examples: ['strom conflict add "Rok narození Josefa" --about P0002 --fact BIRT --claim "S0001: 27 let při sňatku 1839" --claim "S0002: 70 let při úmrtí 1883"'],
|
|
267
268
|
run(ctx, { args, opts }) {
|
|
268
269
|
const tree = ctx.tree();
|
|
269
270
|
const claims = listOpt(opts.claim).map((c) => {
|
|
@@ -277,7 +278,10 @@ register({
|
|
|
277
278
|
const subject = anyRefs(tree, listOpt(opts.about));
|
|
278
279
|
if (!subject.length)
|
|
279
280
|
throw new UsageError("--about is required");
|
|
280
|
-
const
|
|
281
|
+
const fact = typeof opts.fact === "string" ? opts.fact.trim().toUpperCase() : undefined;
|
|
282
|
+
if (fact !== undefined && !/^(?:[A-Z]{3,4}|_[A-Z]{2,6})$/.test(fact))
|
|
283
|
+
throw new UsageError(`--fact is a GEDCOM tag (BIRT, DEAT, NAME, SEX…), not "${opts.fact}"`);
|
|
284
|
+
const x = create(tree, "conflict", { title: args[0].trim(), ...(fact ? { fact } : {}), subject, claims, state: "open", note: opts.note }, (id) => `+${id} conflict "${truncate(args[0], 60)}"`, subject);
|
|
281
285
|
return { text: written(tree), data: { conflict: x } };
|
|
282
286
|
},
|
|
283
287
|
}, {
|
package/dist/commands/browse.js
CHANGED
|
@@ -10,12 +10,15 @@ import { mainPerson } from "../core/kin.js";
|
|
|
10
10
|
import { resolvePerson } from "../core/people.js";
|
|
11
11
|
import { knownGenerations, ownerName, pedigree, personCard, recent, treeStats } from "../core/overview.js";
|
|
12
12
|
import { displayName } from "../core/people.js";
|
|
13
|
+
import { humanTask } from "../cli/human.js";
|
|
14
|
+
export { humanTask };
|
|
13
15
|
import { lacksImages } from "../core/queue.js";
|
|
14
16
|
import { rankedQueue, waitingForUser } from "./tasks.js";
|
|
15
17
|
/** Most rows a section of `recent` lists; the rest is counted. */
|
|
16
18
|
const RECENT_ROWS = 15;
|
|
19
|
+
/** A person as the user reads them, with the ID to name them by later ("strom review P0012", the agent). */
|
|
17
20
|
function whoText(w) {
|
|
18
|
-
return w.years ? `${w.name} (${w.years})` : w.name
|
|
21
|
+
return `${w.years ? `${w.name} (${w.years})` : w.name} [${w.id}]`;
|
|
19
22
|
}
|
|
20
23
|
/** The person asked for, else the main person of the tree. */
|
|
21
24
|
function personOrMain(tree, ref) {
|
|
@@ -63,23 +66,6 @@ function drawPedigree(root, lang) {
|
|
|
63
66
|
walk(root, "");
|
|
64
67
|
return out;
|
|
65
68
|
}
|
|
66
|
-
/** What the agent wrote into a task, for the user: the records it names by ID by their names ("S0001" → its title). */
|
|
67
|
-
export function humanTask(tree, text, lang) {
|
|
68
|
-
const name = (id) => {
|
|
69
|
-
const r = tree.get(id);
|
|
70
|
-
if (!r)
|
|
71
|
-
return id;
|
|
72
|
-
if (r.type === "session")
|
|
73
|
-
return ui(lang, "ui.plan.session", { day: humanDay(r.started, lang) });
|
|
74
|
-
if (r.type === "person")
|
|
75
|
-
return displayName(r);
|
|
76
|
-
if (r.type === "family")
|
|
77
|
-
return r.partners.map((x) => tree.get(x)).filter((x) => !!x).map(displayName).join(" & ") || id;
|
|
78
|
-
return r.title;
|
|
79
|
-
};
|
|
80
|
-
// A list of IDs as a list of names, each once (two sessions of one day read as one).
|
|
81
|
-
return text.replace(/\b[PFSBN]\d{4,}\b(?:, [PFSBN]\d{4,}\b)*/g, (ids) => [...new Set(ids.split(", ").map(name))].join(", "));
|
|
82
|
-
}
|
|
83
69
|
function more(n, lang) {
|
|
84
70
|
return n > 0 ? ` ${ui(lang, "ui.recent.more", { n })}` : undefined;
|
|
85
71
|
}
|
|
@@ -192,7 +178,7 @@ register({
|
|
|
192
178
|
` ${t.state === "doing" ? ui(lang, "ui.plan.now") : `${++n}.`}`,
|
|
193
179
|
`${truncate(humanTask(tree, t.what, lang), 110)}${lacksImages(tree, t) ? ui(lang, "ui.plan.images") : ""}`,
|
|
194
180
|
]);
|
|
195
|
-
const storyNames = [...new Set(stories.map((t) => tree.get(t.subject[0] ?? "")).filter((p) => !!p).map(displayName))];
|
|
181
|
+
const storyNames = [...new Set(stories.map((t) => tree.get(t.subject[0] ?? "")).filter((p) => !!p).map((p) => `${displayName(p)} [${p.id}]`))];
|
|
196
182
|
const out = [ui(lang, "ui.plan.title", { name: tree.config.name })];
|
|
197
183
|
if (!queue.length)
|
|
198
184
|
out.push(` ${ui(lang, "ui.plan.none")}`);
|
package/dist/commands/checks.js
CHANGED
|
@@ -5,6 +5,7 @@ import { register } from "../cli/registry.js";
|
|
|
5
5
|
import { lines, table } from "../cli/format.js";
|
|
6
6
|
import { check } from "../core/check.js";
|
|
7
7
|
import { guard } from "../core/guard.js";
|
|
8
|
+
import { unprovenPeople } from "../core/review.js";
|
|
8
9
|
import { verifyFast, verifyFull } from "../core/integrity.js";
|
|
9
10
|
import { createKey, verifyCommitSeal } from "../core/seal.js";
|
|
10
11
|
import { NeedsConsentError, StromError } from "../core/errors.js";
|
|
@@ -23,7 +24,12 @@ register({
|
|
|
23
24
|
tree: true,
|
|
24
25
|
run(ctx) {
|
|
25
26
|
const tree = ctx.tree();
|
|
26
|
-
|
|
27
|
+
const out = report([...verifyFull(tree).findings, ...check(tree), ...guard(tree)], "ok — data consistent, sealed, nothing lost");
|
|
28
|
+
// not an error — research to do: who rests on no record of their own
|
|
29
|
+
const unproven = unprovenPeople(tree).filter((u) => !u.living).length;
|
|
30
|
+
if (!unproven)
|
|
31
|
+
return out;
|
|
32
|
+
return { ...out, text: lines(out.text, `note: ${unproven} people rest on no record of their own → strom person list --unproven`), data: { ...out.data, unproven } };
|
|
27
33
|
},
|
|
28
34
|
}, {
|
|
29
35
|
path: ["verify"],
|
package/dist/commands/guide.js
CHANGED
|
@@ -136,13 +136,27 @@ ONE PERSON, LOOKED AT AGAIN (the user asks you to check or complete someone; a n
|
|
|
136
136
|
diary say of them outside their data, entries whose images are here but were not read whole, facts
|
|
137
137
|
resting on one reading, conflicts left open (--reread: a second reading with the model the user reads
|
|
138
138
|
with now). Each task's brief lists its items. Tell the user how many and what they cost; they decide.
|
|
139
|
+
Several at once: strom review P… P… (one review). The people no record of their own proves (strom check
|
|
140
|
+
counts them): strom person list --unproven (how each stands; the living apart — no tasks for them);
|
|
141
|
+
strom review --unproven proposes their work a batch at a time — again for the next batch, nothing twice.
|
|
142
|
+
THE STROM APP'S EDITS, ANOTHER FAMILY TREE (the user changed the tree in the Strom app, or brings a .ged/.json)
|
|
143
|
+
strom sync <file> shows what it would take: what the file adds, what the user changed since the research
|
|
144
|
+
gave it (_STROM_HEAD), what is no longer in it. Show the user the list; write it on their yes (--apply, some:
|
|
145
|
+
--only 1,3). Additions become leads; a change to a fact a record proves becomes a conflict (sync.edits: the
|
|
146
|
+
user's choice). A file of another research, or of few of our people, is refused; strom sync undo I… takes a
|
|
147
|
+
sync back. After it: the user opens the research in the Strom app again to have the tree updated.
|
|
148
|
+
Straight from the app: strom sync --app (the user presses Send in the app). With strom-research:// links set
|
|
149
|
+
up on this computer (the user's yes: strom link on, the setup wizard; strom link status says) the app starts it
|
|
150
|
+
itself with one click, and opens an excerpt of an entry in full quality; its menus also open the research, a
|
|
151
|
+
conversation with you (of one person), a task that waits for the user, a person's review, a new direction — each
|
|
152
|
+
said and asked in a terminal first. Tell the user once when they use the app.
|
|
139
153
|
STORIES OF THE ANCESTORS (the setting stories — on by default; strom shows it)
|
|
140
154
|
Once records tell a person's life (a baptism and more facts from records), strom proposes a narrate
|
|
141
155
|
task: write the story for the family book — plain words, the research language, every statement on a
|
|
142
156
|
recorded fact (strom story set P… --text @notes/story-P….md --fact E… …). More facts later: it
|
|
143
157
|
proposes adding to it. Stories wait behind all the research; working alone
|
|
144
158
|
(strom run), every few sessions one comes first. In a conversation the user leads: write one when they
|
|
145
|
-
want it, or offer it when the research has a pause. A story is a draft until the user approves it (
|
|
159
|
+
want it, or offer it when the research has a pause. A story is a draft until the user approves it (strom story approve P…; the Strom app asks it too). When strom says to tell
|
|
146
160
|
the user (once, not in every conversation): in a sentence, and that they may say no (strom config set
|
|
147
161
|
stories no).
|
|
148
162
|
|
package/dist/commands/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import "./start.js";
|
|
|
4
4
|
import "./trees.js";
|
|
5
5
|
import "./research.js";
|
|
6
6
|
import "./intake.js";
|
|
7
|
+
import "./sync.js";
|
|
7
8
|
import "./tasks.js";
|
|
8
9
|
import "./people.js";
|
|
9
10
|
import "./batch.js";
|
|
@@ -20,5 +21,6 @@ import "./output.js";
|
|
|
20
21
|
import "./session.js";
|
|
21
22
|
import "./gates.js";
|
|
22
23
|
import "./hooks.js";
|
|
24
|
+
import "./links.js";
|
|
23
25
|
import "./checks.js";
|
|
24
26
|
import "./setup.js";
|
package/dist/commands/intake.js
CHANGED
|
@@ -149,6 +149,9 @@ register({
|
|
|
149
149
|
report.push(` ${input.id} tree: ${imported.persons} persons, ${imported.families} families, ${imported.events} facts as leads${imported.matched ? `, ${imported.matched} matched to existing` : ""}${imported.problems.length ? ` (${imported.problems.length} unreadable lines)` : ""}`);
|
|
150
150
|
if (imported.extended.length)
|
|
151
151
|
report.push(` added to what we have (leads — check them): ${imported.extended.slice(0, 6).join(" · ")}${imported.extended.length > 6 ? " …" : ""}`);
|
|
152
|
+
// the research's own tree coming back: sync sees what was changed in it too, not only what was added
|
|
153
|
+
if (imported.matched && imported.matched >= imported.persons)
|
|
154
|
+
report.push(` it is this research's tree coming back: strom sync "${ctx.display(file)}" shows what was changed in it too`);
|
|
152
155
|
const system = `gedcom:${sha.slice(0, 12)}`;
|
|
153
156
|
const dupes = likelyDuplicates(tree, tree.list("person").filter((p) => p.refs?.some((r) => r.system === system)).map((p) => p.id));
|
|
154
157
|
if (dupes.length)
|