strom-research 1.1.1 → 1.3.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/README.md +6 -1
- package/assets/lang/cs.json +177 -6
- package/assets/lang/de.json +177 -6
- package/assets/method/core.md +6 -2
- package/assets/method/reading.md +2 -1
- package/assets/plugins/README.md +16 -2
- package/assets/plugins/gates/README.md +95 -0
- package/assets/plugins/gates/claude-usage/gate.json +10 -0
- package/assets/plugins/gates/claude-usage/gate.ts +125 -0
- package/assets/plugins/hooks/README.md +91 -0
- package/dist/agents/global.js +10 -0
- package/dist/agents/launch.js +1 -1
- package/dist/brief/brief.js +2 -0
- package/dist/cli/main.js +43 -2
- package/dist/cli/menu-parts.js +140 -0
- package/dist/cli/menu-research.js +194 -0
- package/dist/cli/menu-settings.js +273 -0
- package/dist/cli/menu-waiting.js +79 -0
- package/dist/cli/menu.js +113 -99
- package/dist/cli/ui.js +175 -5
- package/dist/cli/wizard.js +48 -2
- package/dist/commands/browse.js +4 -3
- package/dist/commands/connectors.js +25 -2
- package/dist/commands/gates.js +58 -0
- package/dist/commands/hooks.js +141 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/intake.js +2 -2
- package/dist/commands/research.js +60 -17
- package/dist/commands/session.js +161 -18
- package/dist/commands/setup.js +57 -1
- package/dist/commands/sources.js +24 -0
- package/dist/commands/start.js +5 -1
- package/dist/commands/tasks.js +41 -4
- package/dist/core/browser.js +50 -0
- package/dist/core/clock.js +55 -0
- package/dist/core/config.js +18 -0
- package/dist/core/gate.js +175 -0
- package/dist/core/hooks.js +210 -0
- package/dist/core/media.js +21 -7
- package/dist/core/overview.js +2 -0
- package/dist/core/phrases.js +1 -0
- package/dist/core/relocate.js +111 -0
- package/dist/core/tree.js +19 -2
- package/dist/runners/antigravity.js +2 -1
- package/dist/runners/claude.js +160 -114
- package/dist/runners/codex.js +14 -1
- package/dist/runners/jsonl.js +63 -37
- package/dist/runners/opencode.js +7 -2
- package/dist/runners/script.js +56 -47
- package/package.json +1 -1
package/dist/cli/menu.js
CHANGED
|
@@ -14,6 +14,7 @@ import { liveHolder } from "../core/lock.js";
|
|
|
14
14
|
import { newerVersion } from "../core/update.js";
|
|
15
15
|
import { taskQueue, waitingForUser } from "../commands/tasks.js";
|
|
16
16
|
import { humanTask } from "../commands/browse.js";
|
|
17
|
+
import { loadGate } from "../core/gate.js";
|
|
17
18
|
import { lacksImages } from "../core/queue.js";
|
|
18
19
|
import { othersAtWork } from "../core/session.js";
|
|
19
20
|
import { DEFAULT_RUN_MINUTES } from "../core/config.js";
|
|
@@ -23,7 +24,12 @@ import { chromiumBrowser } from "../core/chromium.js";
|
|
|
23
24
|
import { liveWorkers, runsAtWork } from "../core/workers.js";
|
|
24
25
|
import { askStromApp } from "./wizard.js";
|
|
25
26
|
import { openForUser } from "../core/open.js";
|
|
26
|
-
import { displayName
|
|
27
|
+
import { displayName } from "../core/people.js";
|
|
28
|
+
import { agentReady, guarded, pause as partsPause, pickPerson, subMenu } from "./menu-parts.js";
|
|
29
|
+
import { offerAgent } from "./fixes.js";
|
|
30
|
+
import { addToResearch } from "./menu-research.js";
|
|
31
|
+
import { waitingForYou } from "./menu-waiting.js";
|
|
32
|
+
import { settingsMenu } from "./menu-settings.js";
|
|
27
33
|
import { mainPerson } from "../core/kin.js";
|
|
28
34
|
import { AGENTS, findAgent } from "../core/which.js";
|
|
29
35
|
import { agentsHere } from "../core/apps.js";
|
|
@@ -71,10 +77,13 @@ export async function runMenu(ctx, run) {
|
|
|
71
77
|
if (outOfAnswers())
|
|
72
78
|
return;
|
|
73
79
|
const name = await ctx.ask(t("ui.tree.name"), t("ui.tree.default"));
|
|
74
|
-
|
|
80
|
+
const made = await createTree(ctx, run, lang, name);
|
|
81
|
+
if (!made)
|
|
75
82
|
return;
|
|
76
83
|
reload();
|
|
77
|
-
|
|
84
|
+
// Straight into the first conversation — with an agent here; without one, the menu says how to get it.
|
|
85
|
+
if (agentReady(ctx, Tree.open(made, ctx.env).config))
|
|
86
|
+
await run(["chat"]);
|
|
78
87
|
if (outOfAnswers())
|
|
79
88
|
return;
|
|
80
89
|
continue;
|
|
@@ -82,26 +91,48 @@ export async function runMenu(ctx, run) {
|
|
|
82
91
|
const items = [];
|
|
83
92
|
let appItem;
|
|
84
93
|
let appTip;
|
|
94
|
+
let other;
|
|
85
95
|
out();
|
|
86
96
|
out(t("ui.menu.title", { version: VERSION }));
|
|
87
97
|
const newer = await newerVersion(ctx.settings, ctx.env);
|
|
88
|
-
if (newer)
|
|
89
|
-
out(t("ui.menu.update.line", { version: newer }));
|
|
90
98
|
if (root) {
|
|
91
99
|
const tree = Tree.open(root, ctx.env);
|
|
92
100
|
const waiting = waitingForUser(tree).length;
|
|
93
101
|
const started = tree.list("research").length > 0;
|
|
94
102
|
out(t("ui.menu.tree", { name: tree.config.name, persons: tree.count("person") }));
|
|
103
|
+
// No agent yet (none installed, or not the one chosen): the research waits for it, and item 1 gets one.
|
|
104
|
+
const ready = agentReady(ctx, tree.config);
|
|
105
|
+
if (!ready)
|
|
106
|
+
out(t(agentsHere(ctx.env).length ? "ui.menu.noagent.chosen" : "ui.menu.noagent"));
|
|
95
107
|
if (waiting)
|
|
96
108
|
out(t("ui.menu.waiting", { count: waiting }));
|
|
97
109
|
const runs = runsAtWork(root).length;
|
|
98
110
|
if (runs)
|
|
99
111
|
out(t(runs > 1 ? "ui.menu.working.more" : "ui.menu.working", { n: runs }));
|
|
100
|
-
items.push(
|
|
112
|
+
items.push(ready
|
|
113
|
+
? { key: "1", label: t(started ? "ui.menu.chat" : "ui.menu.start"), act: async () => void (await run(["chat"])) }
|
|
114
|
+
: {
|
|
115
|
+
key: "1",
|
|
116
|
+
label: t("ui.menu.getagent"),
|
|
117
|
+
act: async () => {
|
|
118
|
+
// Nothing here at all: Claude Code offered (or the page about agents); another one here: the wizard lets them pick it.
|
|
119
|
+
if (agentsHere(ctx.env).length)
|
|
120
|
+
await run(["setup"]);
|
|
121
|
+
else
|
|
122
|
+
await offerAgent(ctx, out);
|
|
123
|
+
await pause(lang);
|
|
124
|
+
},
|
|
125
|
+
}, {
|
|
101
126
|
key: "2",
|
|
102
127
|
label: t("ui.menu.run"),
|
|
103
128
|
act: async () => {
|
|
104
129
|
// Working alone is the agent's CLI; with only its app on this computer, strom says so.
|
|
130
|
+
// No agent here yet: said, and item 1 gets one — the item stays, so the numbers never move.
|
|
131
|
+
if (!ready) {
|
|
132
|
+
out(t(agentsHere(ctx.env).length ? "ui.menu.noagent.chosen" : "ui.menu.noagent"));
|
|
133
|
+
await pause(lang);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
105
136
|
const who = PROFILES[ctx.settings.agent(tree.config).value];
|
|
106
137
|
if (who && !findAgent(who.command, ctx.env)) {
|
|
107
138
|
out(t("ui.run.needscli", { agent: who.name }));
|
|
@@ -129,10 +160,15 @@ export async function runMenu(ctx, run) {
|
|
|
129
160
|
out(` ${t("ui.plan.more", { n: queue.length - shown.length })}`);
|
|
130
161
|
}
|
|
131
162
|
out(t("ui.run.limit", { minutes: ctx.settings.number("run.minutes", fresh.config, DEFAULT_RUN_MINUTES) }));
|
|
132
|
-
|
|
163
|
+
// With a gate of the user's (run.gate): on for as long as there is work and the gate lets it.
|
|
164
|
+
const gate = gateTitle(ctx);
|
|
165
|
+
const ways = [{ label: t("ui.run.queue") }, { label: t("ui.run.pick") }, ...(gate ? [{ label: t("ui.run.loop", { name: gate }) }] : [])];
|
|
166
|
+
const how = shown.length ? await ctx.choose(t("ui.run.what"), ways, 0, { back: t("ui.browse.back") }) : 0;
|
|
133
167
|
if (how === undefined)
|
|
134
168
|
return;
|
|
135
|
-
if (how ===
|
|
169
|
+
if (how === 2)
|
|
170
|
+
args = ["--loop"];
|
|
171
|
+
else if (how === 0) {
|
|
136
172
|
let n = -1;
|
|
137
173
|
while (!(Number.isInteger(n) && n >= 0 && n <= 100)) {
|
|
138
174
|
if (outOfAnswers())
|
|
@@ -176,44 +212,28 @@ export async function runMenu(ctx, run) {
|
|
|
176
212
|
key: "3",
|
|
177
213
|
label: `${t("ui.menu.waitlist")}${waiting ? ` (${waiting})` : ""}`,
|
|
178
214
|
act: async () => {
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await run(["task", "list", "--state", "waiting"]);
|
|
183
|
-
const inbox = ctx.settings.shared() ? path.join(ctx.settings.shared().value, "inbox") : undefined;
|
|
184
|
-
if (inbox && (await ctx.confirm(t("ui.waiting.open"), false)))
|
|
185
|
-
openForUser(inbox, ctx.env);
|
|
186
|
-
}
|
|
215
|
+
if (waiting)
|
|
216
|
+
return waitingForYou(ctx, run, lang, root);
|
|
217
|
+
out(t("ui.waiting.none"));
|
|
187
218
|
await pause(lang);
|
|
188
219
|
},
|
|
220
|
+
}, {
|
|
221
|
+
key: "4",
|
|
222
|
+
label: t("ui.menu.more"),
|
|
223
|
+
act: async () => addToResearch(ctx, run, lang, root),
|
|
189
224
|
}, {
|
|
190
225
|
key: "4",
|
|
191
226
|
label: t("ui.menu.browse"),
|
|
192
227
|
act: async () => browse(ctx, run, lang, root),
|
|
193
|
-
}
|
|
194
|
-
key: "4",
|
|
195
|
-
label: t("ui.menu.review"),
|
|
196
|
-
act: async () => {
|
|
197
|
-
// one person, looked at again: what the tree says of them elsewhere, what to read whole, what to check
|
|
198
|
-
const person = await pickPerson(ctx, lang, root, t("ui.review.who"));
|
|
199
|
-
if (!person)
|
|
200
|
-
return;
|
|
201
|
-
const family = await ctx.confirm(t("ui.review.family"), false);
|
|
202
|
-
await run(["review", person.id, ...(family ? ["--scope", "family"] : [])]);
|
|
203
|
-
const research = Tree.open(root, ctx.env).list("research").find((r) => r.direction === "person" && r.focus === person.id && r.review);
|
|
204
|
-
const open = research ? Tree.open(root, ctx.env).list("task").filter((x) => x.research === research.id && x.state === "open").length : 0;
|
|
205
|
-
if (research && open && (await ctx.confirm(t("ui.review.run", { n: open }), false)))
|
|
206
|
-
await run(["run", "--research", research.id, "--max", String(open)]);
|
|
207
|
-
await pause(lang);
|
|
208
|
-
},
|
|
209
|
-
}]));
|
|
228
|
+
});
|
|
210
229
|
// Several agents on this computer: this conversation with another one (the default stays).
|
|
211
230
|
const current = ctx.settings.agent(tree.config).value;
|
|
212
231
|
const others = agentsHere(ctx.env)
|
|
213
232
|
.filter((a) => a.id !== current)
|
|
214
233
|
.map((a) => AGENTS.find((x) => x.id === a.id));
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
other = !others.length
|
|
235
|
+
? undefined
|
|
236
|
+
: {
|
|
217
237
|
key: "9",
|
|
218
238
|
label: t("ui.menu.otheragent"),
|
|
219
239
|
act: async () => {
|
|
@@ -222,7 +242,7 @@ export async function runMenu(ctx, run) {
|
|
|
222
242
|
if (i !== undefined)
|
|
223
243
|
await run(["chat", "--agent", others[i].id]);
|
|
224
244
|
},
|
|
225
|
-
}
|
|
245
|
+
};
|
|
226
246
|
// The Strom app: open the research in it (live while an agent is at work); not known yet: what it is, and whether they want it.
|
|
227
247
|
const app = stromAppState(ctx.settings);
|
|
228
248
|
if (app !== "no") {
|
|
@@ -231,13 +251,7 @@ export async function runMenu(ctx, run) {
|
|
|
231
251
|
appItem = {
|
|
232
252
|
key: "5",
|
|
233
253
|
label: t(!hasApp ? "ui.menu.app.new" : atWork ? "ui.menu.app.watch" : "ui.menu.app"),
|
|
234
|
-
act: async () =>
|
|
235
|
-
if (hasApp)
|
|
236
|
-
return void (await run(["app"]));
|
|
237
|
-
const answer = await askStromApp(ctx, lang);
|
|
238
|
-
if (answer === "install" && (await ctx.confirm(t("ui.app.open.now"), true)))
|
|
239
|
-
await run(["app"]);
|
|
240
|
-
},
|
|
254
|
+
act: async () => openStromApp(ctx, run, lang, hasApp),
|
|
241
255
|
};
|
|
242
256
|
appTip = !hasApp ? "ui.menu.app.tip" : atWork ? "ui.menu.app.live" : undefined;
|
|
243
257
|
items.push(appItem);
|
|
@@ -247,31 +261,28 @@ export async function runMenu(ctx, run) {
|
|
|
247
261
|
// Several trees and none chosen: pick one first.
|
|
248
262
|
items.push({ key: "1", label: t("ui.menu.trees"), act: async () => pickTree(ctx, run, lang) });
|
|
249
263
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const code = await run(["doctor"]);
|
|
255
|
-
if (code !== 0 && (await ctx.confirm(t("ui.menu.fix"), true)))
|
|
256
|
-
await run(["doctor", "--fix"]);
|
|
257
|
-
await pause(lang);
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
if (newer)
|
|
261
|
-
items.push({ key: "9", label: t("ui.menu.update", { version: newer }), act: async () => void (await run(["update"])) });
|
|
264
|
+
// What changes nothing of the research: the newer strom, the settings of this computer (with the check), another tree.
|
|
265
|
+
// At most nine: the newer strom is taken in the settings, the line above says where.
|
|
266
|
+
const settings = { key: "6", label: t("ui.menu.settings"), act: async () => settingsMenu(ctx, run, lang, root, newer) };
|
|
267
|
+
items.push(settings);
|
|
262
268
|
if (root)
|
|
263
269
|
items.push({ key: "8", label: known.length > 1 ? t("ui.menu.trees") : t("ui.menu.newtree"), act: async () => pickTree(ctx, run, lang) });
|
|
270
|
+
// Only with several agents here: last, so that it moves no other number.
|
|
271
|
+
if (other)
|
|
272
|
+
items.push(other);
|
|
264
273
|
items.push({ key: "0", label: t("ui.menu.quit"), act: async () => true });
|
|
265
274
|
// One quiet line on the Strom app: what it can do while the person has not said, or that the agent can be watched now.
|
|
266
275
|
const numbered = items.filter((it) => it.key !== "0");
|
|
267
276
|
if (appItem && appTip)
|
|
268
277
|
out(t(appTip, { key: numbered.indexOf(appItem) + 1 }));
|
|
278
|
+
if (newer)
|
|
279
|
+
out(t("ui.menu.update.where", { version: newer, key: numbered.indexOf(settings) + 1 }));
|
|
269
280
|
// Numbered in the order shown; 0 always quits.
|
|
270
281
|
let n = 0;
|
|
271
282
|
const i = await ctx.choose("", items.map((it) => ({ key: it.key === "0" ? "0" : String(++n), label: it.label })), 0);
|
|
272
283
|
if (i === undefined)
|
|
273
284
|
return;
|
|
274
|
-
const quit = await items[i].act
|
|
285
|
+
const quit = await guarded(ctx, lang, items[i].act);
|
|
275
286
|
if (quit === true) {
|
|
276
287
|
out(t("ui.menu.bye"));
|
|
277
288
|
return;
|
|
@@ -280,6 +291,20 @@ export async function runMenu(ctx, run) {
|
|
|
280
291
|
}
|
|
281
292
|
/** How many tasks of the queue the menu lists before the agent works alone. */
|
|
282
293
|
const RUN_LIST = 15;
|
|
294
|
+
/** The title of the user's gate (run.gate), when there is one to ask. */
|
|
295
|
+
function gateTitle(ctx) {
|
|
296
|
+
const name = ctx.settings.runGate();
|
|
297
|
+
const shared = ctx.settings.shared()?.value;
|
|
298
|
+
if (!name || !shared)
|
|
299
|
+
return undefined;
|
|
300
|
+
try {
|
|
301
|
+
const g = loadGate(shared, name);
|
|
302
|
+
return g.manifest.title ?? g.name;
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return name; // the run says what is wrong with it
|
|
306
|
+
}
|
|
307
|
+
}
|
|
283
308
|
/** "2", "1,4", "2-5", "1 3 6-8" → those numbers (1…max), in the order given, each once; undefined when anything else. */
|
|
284
309
|
export function pickNumbers(answer, max) {
|
|
285
310
|
const out = [];
|
|
@@ -296,27 +321,6 @@ export function pickNumbers(answer, max) {
|
|
|
296
321
|
}
|
|
297
322
|
return out.length ? out : undefined;
|
|
298
323
|
}
|
|
299
|
-
/** Ask for a person by name or ID; several match: pick one. Nobody (Enter, or no answers left): undefined. */
|
|
300
|
-
async function pickPerson(ctx, lang, root, question, suggested) {
|
|
301
|
-
const tree = Tree.open(root, ctx.env);
|
|
302
|
-
for (;;) {
|
|
303
|
-
if (ctx.io.answers !== undefined && ctx.io.answers.length === 0)
|
|
304
|
-
return undefined;
|
|
305
|
-
const who = (await ctx.ask(question, suggested)).trim();
|
|
306
|
-
if (!who || who === "0")
|
|
307
|
-
return undefined;
|
|
308
|
-
const byId = /^P\d+$/i.test(who) ? tree.get(who.toUpperCase()) : undefined;
|
|
309
|
-
const hits = byId && !byId.retracted ? [byId] : findPersons(tree, who);
|
|
310
|
-
if (!hits.length)
|
|
311
|
-
ctx.io.stdout(ui(lang, "ui.review.none", { who }) + "\n");
|
|
312
|
-
else if (hits.length === 1)
|
|
313
|
-
return hits[0];
|
|
314
|
-
else {
|
|
315
|
-
const i = await ctx.choose(ui(lang, "ui.review.pick"), hits.slice(0, 9).map((p) => ({ label: label(p) })), 0, { back: ui(lang, "ui.browse.back") });
|
|
316
|
-
return i === undefined ? undefined : hits[i];
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
324
|
/**
|
|
321
325
|
* Look through the family tree: the research at a glance, what is new, what the agent does next, one person, the
|
|
322
326
|
* ancestors of one, and the result files — each an ordinary command (stats, recent, plan, person card, pedigree),
|
|
@@ -325,28 +329,27 @@ async function pickPerson(ctx, lang, root, question, suggested) {
|
|
|
325
329
|
async function browse(ctx, run, lang, root) {
|
|
326
330
|
const t = (key, values = {}) => ui(lang, key, values);
|
|
327
331
|
const out = (line = "") => ctx.io.stdout(line + "\n");
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
332
|
+
// A long answer waits for Enter before the menu comes again; 0 anywhere goes back without it.
|
|
333
|
+
const show = async (argv) => {
|
|
334
|
+
await run(argv);
|
|
335
|
+
await partsPause(ctx, lang);
|
|
331
336
|
};
|
|
332
|
-
|
|
333
|
-
if (ctx.io.answers !== undefined && ctx.io.answers.length === 0)
|
|
334
|
-
return;
|
|
337
|
+
await subMenu(ctx, lang, () => {
|
|
335
338
|
const tree = Tree.open(root, ctx.env);
|
|
336
339
|
const people = tree.count("person") > 0;
|
|
337
340
|
const items = [
|
|
338
341
|
...(people
|
|
339
342
|
? [
|
|
340
|
-
{ key: "1", label: t("ui.browse.stats"), act: async () =>
|
|
341
|
-
{ key: "2", label: t("ui.browse.recent"), act: async () =>
|
|
342
|
-
{ key: "2", label: t("ui.browse.plan"), act: async () =>
|
|
343
|
+
{ key: "1", label: t("ui.browse.stats"), act: async () => show(["stats"]) },
|
|
344
|
+
{ key: "2", label: t("ui.browse.recent"), act: async () => show(["recent"]) },
|
|
345
|
+
{ key: "2", label: t("ui.browse.plan"), act: async () => show(["plan"]) },
|
|
343
346
|
{
|
|
344
347
|
key: "3",
|
|
345
348
|
label: t("ui.browse.card"),
|
|
346
349
|
act: async () => {
|
|
347
350
|
const p = await pickPerson(ctx, lang, root, t("ui.review.who"));
|
|
348
351
|
if (p)
|
|
349
|
-
await
|
|
352
|
+
await show(["person", "card", p.id]);
|
|
350
353
|
},
|
|
351
354
|
},
|
|
352
355
|
{
|
|
@@ -356,7 +359,7 @@ async function browse(ctx, run, lang, root) {
|
|
|
356
359
|
const main = mainPerson(tree);
|
|
357
360
|
const p = await pickPerson(ctx, lang, root, t("ui.browse.whose"), main ? displayName(tree.get(main)) : undefined);
|
|
358
361
|
if (p)
|
|
359
|
-
await
|
|
362
|
+
await show(["pedigree", p.id]);
|
|
360
363
|
},
|
|
361
364
|
},
|
|
362
365
|
]
|
|
@@ -384,18 +387,28 @@ async function browse(ctx, run, lang, root) {
|
|
|
384
387
|
}
|
|
385
388
|
},
|
|
386
389
|
},
|
|
387
|
-
...(stromAppState(ctx.settings) === "no" ? [] : [{ key: "6", label: t("ui.menu.app"), act: async () =>
|
|
388
|
-
{ key: "0", label: t("ui.browse.back"), act: async () => true },
|
|
390
|
+
...(stromAppState(ctx.settings) === "no" ? [] : [{ key: "6", label: t("ui.menu.app"), act: async () => openStromApp(ctx, run, lang, stromAppState(ctx.settings) !== "unknown") }]),
|
|
389
391
|
];
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
392
|
+
return { items };
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The Strom app: not asked about yet — what it is, and whether they want it; wanted but not installed here — installing
|
|
397
|
+
* it from the browser (an app of its own, PWA) is offered first, else it opens in the browser; installed — it opens.
|
|
398
|
+
*/
|
|
399
|
+
async function openStromApp(ctx, run, lang, said) {
|
|
400
|
+
const t = (key) => ui(lang, key);
|
|
401
|
+
if (!said) {
|
|
402
|
+
const answer = await askStromApp(ctx, lang);
|
|
403
|
+
if (answer === "install" && (await ctx.confirm(t("ui.app.open.now"), true)))
|
|
404
|
+
await run(["app"]);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!installedStromApp(ctx.env, process.platform, stromAppUrl(ctx.settings)) && (await ctx.confirm(t("ui.app.notinstalled"), true))) {
|
|
408
|
+
await run(["app", "install"]);
|
|
409
|
+
return;
|
|
398
410
|
}
|
|
411
|
+
await run(["app"]);
|
|
399
412
|
}
|
|
400
413
|
/** Choose another family tree, or create a new one (and start its research). The one worked on now is suggested; 0 stays with it. */
|
|
401
414
|
async function pickTree(ctx, run, lang) {
|
|
@@ -424,5 +437,6 @@ async function pickTree(ctx, run, lang) {
|
|
|
424
437
|
if (!made)
|
|
425
438
|
return;
|
|
426
439
|
await run(["trees", "use", made], true);
|
|
427
|
-
|
|
440
|
+
if (agentReady(ctx, Tree.open(made, ctx.env).config))
|
|
441
|
+
await run(["chat"]);
|
|
428
442
|
}
|
package/dist/cli/ui.js
CHANGED
|
@@ -38,7 +38,7 @@ export const UI = {
|
|
|
38
38
|
"ui.menu.otheragent": "Talk with another agent this time",
|
|
39
39
|
"ui.menu.run": "Let the agent work on its own",
|
|
40
40
|
"ui.menu.waitlist": "What waits for you",
|
|
41
|
-
"ui.menu.settings": "Settings",
|
|
41
|
+
"ui.menu.settings": "Settings: the agent, working alone, messages, archives, the check",
|
|
42
42
|
"ui.menu.doctor": "Check the installation",
|
|
43
43
|
"ui.menu.trees": "Another family tree",
|
|
44
44
|
"ui.menu.newtree": "Create a family tree",
|
|
@@ -348,9 +348,7 @@ export const UI = {
|
|
|
348
348
|
"ui.card.partner": "Partner: {name}",
|
|
349
349
|
"ui.card.unknown": "not known yet",
|
|
350
350
|
"ui.card.children": "Children: {names}",
|
|
351
|
-
"ui.card.story": "Story
|
|
352
|
-
"ui.card.story.draft": "a draft",
|
|
353
|
-
"ui.card.story.final": "approved",
|
|
351
|
+
"ui.card.story": "Story:",
|
|
354
352
|
"ui.stats.title": "{name} – the research at a glance",
|
|
355
353
|
"ui.stats.counts": "People: {persons} · families: {families} · records from sources: {sources} · scans: {images} · places: {places}",
|
|
356
354
|
"ui.stats.facts": "Facts: proven {proven} · probable {probable} · possible {possible} · only leads {lead}",
|
|
@@ -361,7 +359,8 @@ export const UI = {
|
|
|
361
359
|
"ui.stats.tasks": "Tasks: in the queue {queued} · waiting for you {waiting} · done {done}",
|
|
362
360
|
"ui.stats.sessions": "Sessions of the agent: {n} · the last {last}",
|
|
363
361
|
"ui.stats.cost": " · cost so far {cost}",
|
|
364
|
-
"ui.stats.
|
|
362
|
+
"ui.stats.cost.partial": " (without sessions stopped before they said what they cost: {n})",
|
|
363
|
+
"ui.stats.stories": "Stories: {n}",
|
|
365
364
|
"ui.ped.noparents": "parents not known yet",
|
|
366
365
|
"ui.ped.nofather": "father not known yet",
|
|
367
366
|
"ui.ped.nomother": "mother not known yet",
|
|
@@ -378,6 +377,20 @@ export const UI = {
|
|
|
378
377
|
"ui.recent.sessions": "What the agent did:",
|
|
379
378
|
"ui.recent.more": "… and {n} more",
|
|
380
379
|
"ui.browse.plan": "What the agent will do next",
|
|
380
|
+
"ui.review.new": "{name} — {n} new task(s):",
|
|
381
|
+
"ui.review.nothing": "{name} — nothing new to review",
|
|
382
|
+
"ui.review.would": " would add: {level} · {what}",
|
|
383
|
+
"ui.review.dry": "(dry run — nothing written)",
|
|
384
|
+
"ui.review.covered": " {name}: already covered by {task} ({state}) — {what}",
|
|
385
|
+
"ui.review.wake": " to go on with it: strom task wake {task}",
|
|
386
|
+
"ui.review.exhausted": " {name}: searched in vain ({tasks}) — what next is your decision",
|
|
387
|
+
"ui.review.open": "{n} task(s) open in {research}, about as many sessions",
|
|
388
|
+
"ui.review.cost": " — a session here costs ${cost} on average",
|
|
389
|
+
"ui.review.next": "next: strom run --research {research} (the agent alone) · or in a conversation: strom session start --research {research}",
|
|
390
|
+
"ui.review.state.open": "in the queue",
|
|
391
|
+
"ui.review.state.doing": "being worked on",
|
|
392
|
+
"ui.review.state.parked": "parked",
|
|
393
|
+
"ui.review.state.waiting": "waiting for you",
|
|
381
394
|
"ui.plan.title": "What the agent will do next – {name}:",
|
|
382
395
|
"ui.plan.now": "now",
|
|
383
396
|
"ui.plan.images": " (needs the scans first)",
|
|
@@ -408,6 +421,163 @@ export const UI = {
|
|
|
408
421
|
"ui.run.beside": "⚠ An agent is already working on its own here (runs: {n}). Another one beside it takes other tasks — the costs add up.",
|
|
409
422
|
"ui.run.beside.sure": "Start another one beside it?",
|
|
410
423
|
"ui.run.others": "⚠ Another run works in this family tree already (since {since}): this one takes other tasks — the costs add up.",
|
|
424
|
+
"ui.run.gate": "· condition: {name} — asked before each session; it may make the run wait",
|
|
425
|
+
"ui.run.gate.wait": "⏸ waiting until {at} — {reason} (Ctrl-C ends the run)",
|
|
426
|
+
"ui.run.stop.gate": "the condition “{name}” said stop — {reason}",
|
|
427
|
+
"ui.run.stop.gate.error": "the condition “{name}” gave no proper answer — {reason} (strom gate test)",
|
|
428
|
+
"ui.run.gate.once": "· condition: {name} — these tasks are yours to start: it is asked once, and if it would not start, you decide",
|
|
429
|
+
"ui.run.gate.anyway": "The condition “{name}” would not start now — {reason}. Start anyway?",
|
|
430
|
+
"ui.run.gate.later": "not yet",
|
|
431
|
+
"ui.run.gate.no": "no",
|
|
432
|
+
"ui.run.gate.noanswer": "no proper answer (strom gate test)",
|
|
433
|
+
"ui.run.stop.gate.declined": "not started — the condition “{name}”: {reason}",
|
|
434
|
+
"ui.run.loop": "Let it work on while there is work and the condition “{name}” allows it",
|
|
435
|
+
"ui.consent.gate.set": "Let the condition “{name}” decide when the agent working alone goes on?",
|
|
436
|
+
"ui.consent.gate.unset": "Remove the condition “{name}” — the agent working alone no longer asks it?",
|
|
437
|
+
"ui.consent.gate.skip": "Let the agent work on its own without the condition “{name}”?",
|
|
438
|
+
"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?",
|
|
439
|
+
"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.",
|
|
440
|
+
"ui.consent.hook.off": "Stop telling the hook “{name}” what is saved?",
|
|
441
|
+
"ui.menu.more": "Add to the research: material, a new direction, one person reviewed",
|
|
442
|
+
"ui.more.title": "Add to the research",
|
|
443
|
+
"ui.more.intake": "Material from the family: documents, photos, a family tree from another program, what you know",
|
|
444
|
+
"ui.more.research": "A new direction: the ancestors or descendants of someone else, or one question",
|
|
445
|
+
"ui.intake.what": "What do you bring?",
|
|
446
|
+
"ui.intake.files": "Files or a folder from this computer",
|
|
447
|
+
"ui.intake.text": "What you know — names, dates, places, family memories",
|
|
448
|
+
"ui.intake.write": "Write what you know (Enter or 0 goes back)",
|
|
449
|
+
"ui.intake.path": "The folder or file — you can drag it into this window (Enter or 0 goes back)",
|
|
450
|
+
"ui.intake.missing": "Not found: {path} — check the path, or drag the folder in.",
|
|
451
|
+
"ui.intake.scans": "The folder {folder} holds {n} images. Are they family documents and photos? (no: scans of a register or another book)",
|
|
452
|
+
"ui.intake.book": "{folder}: scans of a register or another book go with that book — tell the agent in a conversation where they come from, and it registers them.",
|
|
453
|
+
"ui.intake.known": "This material is in the research already — nothing added.",
|
|
454
|
+
"ui.intake.done": "✓ Added to the research: {n}. The agent goes through it in its next work.",
|
|
455
|
+
"ui.intake.chat": "Go through it with the agent now?",
|
|
456
|
+
"ui.intake.say": "I added new material to the research — let us go through it.",
|
|
457
|
+
"ui.research.kind": "What do you want to find out?",
|
|
458
|
+
"ui.research.ancestors": "The ancestors of someone",
|
|
459
|
+
"ui.research.descendants": "The descendants of someone",
|
|
460
|
+
"ui.research.question": "One question (e.g. who was Mary's father?)",
|
|
461
|
+
"ui.research.ask": "The question (Enter or 0 goes back)",
|
|
462
|
+
"ui.research.whom": "Who is it about?",
|
|
463
|
+
"ui.research.known": "Someone already in the family tree",
|
|
464
|
+
"ui.research.newperson": "Someone new",
|
|
465
|
+
"ui.research.name": "Their name (e.g. John Smith; 0 goes back)",
|
|
466
|
+
"ui.research.sex": "A man or a woman?",
|
|
467
|
+
"ui.research.male": "A man",
|
|
468
|
+
"ui.research.female": "A woman",
|
|
469
|
+
"ui.research.unknown": "I don't know",
|
|
470
|
+
"ui.research.title": "The research's name",
|
|
471
|
+
"ui.research.title.ancestors": "Ancestors: {name}",
|
|
472
|
+
"ui.research.title.descendants": "Descendants: {name}",
|
|
473
|
+
"ui.research.knows": "What do you know of it? Dates, places, stories of the family help the agent (Enter: nothing)",
|
|
474
|
+
"ui.research.done": "✓ The research “{name}” is started.",
|
|
475
|
+
"ui.research.chat": "Start on it with the agent now?",
|
|
476
|
+
"ui.research.say": "I started a new research, “{name}” — let us begin with it.",
|
|
477
|
+
"ui.settings.title": "Settings (on this computer)",
|
|
478
|
+
"ui.settings.wizard": "Language, agent, model, what the agent may do alone, stories… (the setup)",
|
|
479
|
+
"ui.settings.alone": "The agent working alone: {minutes} min a task · condition: {gate}",
|
|
480
|
+
"ui.settings.gate.none": "none",
|
|
481
|
+
"ui.settings.hooks": "Messages of what is saved (hooks): {state}",
|
|
482
|
+
"ui.settings.on": "on",
|
|
483
|
+
"ui.settings.off": "off",
|
|
484
|
+
"ui.settings.remote": "Watch the agent from your phone (Remote Control): {state}",
|
|
485
|
+
"ui.settings.remote.about": "With it on you can follow and steer the Claude Code sessions strom starts — conversations and working alone — from claude.ai or the Claude app on your phone.",
|
|
486
|
+
"ui.settings.remote.on": "Turn it on?",
|
|
487
|
+
"ui.settings.remote.off": "Turn it off?",
|
|
488
|
+
"ui.settings.archives": "Archives: your logins, downloaders asking first",
|
|
489
|
+
"ui.alone.title": "The agent working alone",
|
|
490
|
+
"ui.alone.minutes": "Time for one task: {minutes} min",
|
|
491
|
+
"ui.alone.minutes.ask": "How many minutes may one task take? (5–600; 0 goes back)",
|
|
492
|
+
"ui.alone.minutes.bad": "Please a number of minutes from 5 to 600.",
|
|
493
|
+
"ui.alone.gate": "Working on and on under a condition: {gate}",
|
|
494
|
+
"ui.alone.gate.about": "A condition is asked before each session while the agent works on and on by itself; the menu offers that only with one set. Tasks you start yourself are your choice.",
|
|
495
|
+
"ui.alone.gate.pick": "Which condition?",
|
|
496
|
+
"ui.alone.gate.claude": "By how many points should the week's usage of your Claude subscription stay behind the part of the week gone? (a day ≈ 14.3; 0: just not ahead of the week)",
|
|
497
|
+
"ui.alone.gate.args": "What to give it (Enter: nothing)",
|
|
498
|
+
"ui.alone.test": "What would the condition say now?",
|
|
499
|
+
"ui.alone.asking": "Asking the condition…",
|
|
500
|
+
"ui.alone.said.go": "It would start now{reason}",
|
|
501
|
+
"ui.alone.said.wait": "It would wait until {at}{reason}",
|
|
502
|
+
"ui.alone.said.stop": "It would stop{reason}",
|
|
503
|
+
"ui.alone.said.error": "No proper answer{reason}",
|
|
504
|
+
"ui.hooks.title": "Hooks — a number turns one on or off:",
|
|
505
|
+
"ui.hooks.on": "{name}: on",
|
|
506
|
+
"ui.hooks.off": "{name}: off",
|
|
507
|
+
"ui.hooks.none": "No hooks here yet. A hook is your own program told of what is saved (e.g. a message to your phone) — how: {readme}",
|
|
508
|
+
"ui.archives.title": "Downloaders of the archives here:",
|
|
509
|
+
"ui.archives.empty": "No downloaders here yet — the agent builds one when the research needs an archive.",
|
|
510
|
+
"ui.archives.login.saved": "your login saved {date}",
|
|
511
|
+
"ui.archives.login.none": "can use your account — no login saved",
|
|
512
|
+
"ui.archives.consent": "Downloaders ask you before they run: {state}",
|
|
513
|
+
"ui.archives.consent.about": "Off (usual): downloaders run by themselves — slowly, and only to their archive. On: each needs your yes first, here in the terminal, and so does each archive it reaches.",
|
|
514
|
+
"ui.archives.consent.on": "Turn it on?",
|
|
515
|
+
"ui.archives.consent.off": "Turn it off?",
|
|
516
|
+
"ui.hooks.nothing": "none",
|
|
517
|
+
"ui.menu.noagent": "No AI agent on this computer yet — the research is its work. Item 1 installs one.",
|
|
518
|
+
"ui.menu.getagent": "Install or choose the AI agent",
|
|
519
|
+
"ui.menu.update.where": "A new version of strom is out: {version} — update it in the settings (item {key}).",
|
|
520
|
+
"ui.more.noagent": "The agent takes it up once it is on this computer (item 1 of the menu).",
|
|
521
|
+
"ui.menu.noagent.chosen": "The AI agent this research uses is not on this computer — item 1 chooses another.",
|
|
522
|
+
"ui.waiting.title": "The agent needs you for {n} thing(s):",
|
|
523
|
+
"ui.waiting.do": "What to do: {what}",
|
|
524
|
+
"ui.waiting.ask": "the agent will tell you in a conversation",
|
|
525
|
+
"ui.waiting.images": "Save by hand: images {images} of {book}",
|
|
526
|
+
"ui.waiting.more": "…and {n} more — the agent goes through them with you in a conversation.",
|
|
527
|
+
"ui.waiting.pick": "Have you got it? Choose the number and write what you found:",
|
|
528
|
+
"ui.waiting.answer": "Answer {k}: {what}",
|
|
529
|
+
"ui.waiting.folder": "Open the folder for the images of {k}",
|
|
530
|
+
"ui.waiting.opened": "The folder: {dir} — once the images are there, the next work of the agent takes them in by itself.",
|
|
531
|
+
"ui.waiting.write": "Your answer — a link, a book's number, what you found out (Enter or 0 goes back)",
|
|
532
|
+
"ui.waiting.back": "✓ The task goes back to the agent with your answer.",
|
|
533
|
+
"ui.waiting.chat": "Go on with the agent now?",
|
|
534
|
+
"ui.waiting.say": "I answered what you asked of me: {what} — let us go on with it.",
|
|
535
|
+
"ui.intake.empty": "There is nothing in it to take in.",
|
|
536
|
+
"ui.intake.ours": "{path} is strom's own folder (the research itself), not material of the family.",
|
|
537
|
+
"ui.research.exists": "This research is there already: “{name}” — the agent goes on with it.",
|
|
538
|
+
"ui.alone.gate.bad": "Please a number of points from 0 to 100 (e.g. 10).",
|
|
539
|
+
"ui.waiting.long": "That is long for an answer here (at most {n} characters) — a link or a sentence; the rest tell the agent in a conversation.",
|
|
540
|
+
"ui.settings.remote.chats": "conversations yes, working alone no",
|
|
541
|
+
"ui.settings.remote.own": "Conversations with the agent you can watch from your phone already — Claude Code does it itself (its own setting). The agent working alone (item 2 of the menu) only once this is on too.",
|
|
542
|
+
"ui.settings.remote.both": "conversations and working alone",
|
|
543
|
+
"ui.settings.remote.onalone": "Turn it on for working alone too?",
|
|
544
|
+
"ui.archives.login.nobody": "None of them needs a login: the archives are open. A login shows here only for a downloader of a portal with an account (perhaps a paid one).",
|
|
545
|
+
"ui.archives.login.how": "Your login is typed in here and kept on this computer only; strom puts it into the downloader's requests — neither the downloader nor the agent sees it.",
|
|
546
|
+
"ui.archives.login.save": "Save your login to {title}",
|
|
547
|
+
"ui.archives.login.change": "Change your login to {title}",
|
|
548
|
+
"ui.home.what": "The research is in {from} (family trees: {trees}), with the shared folder — downloaders, hooks, the inbox, big images. It moves to {to} as a whole.",
|
|
549
|
+
"ui.home.sure": "Move it there?",
|
|
550
|
+
"ui.home.moved": "✓ The research is in {to} now.",
|
|
551
|
+
"ui.home.copied": "✓ The research is copied to {to} (another disk) and works from there. The old folder {from} is left as it was — delete it once you have seen all is well.",
|
|
552
|
+
"ui.home.stays": "The research stays in {from}.",
|
|
553
|
+
"ui.home.inside": "{to} is inside the research's folder {from} (or the other way round) — pick a folder beside it.",
|
|
554
|
+
"ui.home.notEmpty": "{to} holds something already — the research moves only into an empty or new folder.",
|
|
555
|
+
"ui.home.busy": "Somebody is at work in {trees} right now (an agent, a run, or the Strom app following it live) — the research can move once that has finished.",
|
|
556
|
+
"ui.home.failed": "It could not be moved ({reason}) — nothing changed.",
|
|
557
|
+
"ui.app.notinstalled": "The Strom app is not installed on this computer yet. Install it now as an app from the browser (free; strom shows where to click)?",
|
|
558
|
+
"ui.browser.agent": "{names}: through the browser — that only Claude Code can (the Claude in Chrome extension); this research is done by {agent}. Save those images by hand, or let Claude Code do the research (strom agents use claude).",
|
|
559
|
+
"ui.browser.noext": "{names}: through the browser — Chrome needs the Claude in Chrome extension, signed in to the account Claude Code uses; it is not in a browser here: {url}",
|
|
560
|
+
"ui.doc.browser": "browser (Claude in Chrome)",
|
|
561
|
+
"ui.doc.browser.ok": "the extension in {browsers} — Chrome must run while the agent works",
|
|
562
|
+
"ui.doc.browser.noext": "the extension is not installed (needed by {names})",
|
|
563
|
+
"ui.doc.browser.agent": "{names} go through the browser — only Claude Code can (now: {agent})",
|
|
564
|
+
"ui.archives.via.direct": "images directly, paced by strom",
|
|
565
|
+
"ui.archives.via.browser": "images through your browser",
|
|
566
|
+
"ui.archives.via.manual": "images saved by hand (the archive allows no automation)",
|
|
567
|
+
"ui.archives.browser.ok": "Through the browser: the Claude in Chrome extension is in {browsers}; Chrome must run while the agent works, signed in to the account Claude Code uses.",
|
|
568
|
+
"ui.archives.to.browser": "{title}: fetch through your browser instead",
|
|
569
|
+
"ui.archives.to.direct": "{title}: fetch directly instead",
|
|
570
|
+
"ui.archives.browser.about": "Through your browser the agent fetches the images in your own Chrome (where your login to the archive is), with the Claude in Chrome extension — only Claude Code can. strom still plans and paces every request.",
|
|
571
|
+
"ui.archives.direct.about": "Directly, strom fetches the images itself, paced, without a browser — any agent can.",
|
|
572
|
+
"ui.archives.switch": "Switch it?",
|
|
573
|
+
"ui.archives.now.browser": "✓ {title}: through your browser from now on.",
|
|
574
|
+
"ui.archives.now.direct": "✓ {title}: directly from now on.",
|
|
575
|
+
"ui.setup.home.again": "Where to keep the research (0 keeps it where it is)",
|
|
576
|
+
"ui.home.env": "The research's folder {from} is set from outside (STROM_HOME) — it stays; change it there.",
|
|
577
|
+
"ui.menu.failed": "That did not work: {reason}",
|
|
578
|
+
"ui.review.next.human": "The agent takes them up in a conversation or when it works alone.",
|
|
579
|
+
"ui.review.wake.human": " to go on with it, tell the agent in a conversation",
|
|
580
|
+
"ui.waiting.gone": "This one no longer waits for you — the agent has taken it up meanwhile.",
|
|
411
581
|
};
|
|
412
582
|
/** A text for the person, in their language. */
|
|
413
583
|
export function ui(lang, key, values = {}) {
|