memgineering 0.7.5 → 0.8.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/CHANGELOG.md +139 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-setup/SKILL.md +25 -0
- package/dist/index.js +701 -211
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,9 @@ import { join } from "path";
|
|
|
15
15
|
function brandHome() {
|
|
16
16
|
return process.env[BRAND.homeEnvVar] ?? join(homedir(), BRAND.homeDirName);
|
|
17
17
|
}
|
|
18
|
+
function agentHome() {
|
|
19
|
+
return process.env[BRAND.agentHomeEnvVar] ?? homedir();
|
|
20
|
+
}
|
|
18
21
|
var BRAND;
|
|
19
22
|
var init_brand = __esm({
|
|
20
23
|
"src/brand.ts"() {
|
|
@@ -28,6 +31,16 @@ var init_brand = __esm({
|
|
|
28
31
|
* real `~/.memgineering`.
|
|
29
32
|
*/
|
|
30
33
|
homeEnvVar: "MEMGINEERING_HOME",
|
|
34
|
+
/**
|
|
35
|
+
* Where `setup` installs its guidance — the AGENT tools' homes, not ours.
|
|
36
|
+
*
|
|
37
|
+
* A separate variable from `MEMGINEERING_HOME` because they are separate
|
|
38
|
+
* things: that one moves this tool's own state, and this one moves the files
|
|
39
|
+
* it writes into somebody else's `~/.claude` and `~/.codex`. Conflating them
|
|
40
|
+
* would mean anyone relocating their state dir silently stopped installing
|
|
41
|
+
* into their real agents.
|
|
42
|
+
*/
|
|
43
|
+
agentHomeEnvVar: "MEMGINEERING_AGENT_HOME",
|
|
31
44
|
/** Force `--json` framing without passing the flag (used by wrappers). */
|
|
32
45
|
jsonEnvVar: "MEMGINEERING_JSON",
|
|
33
46
|
/**
|
|
@@ -173,6 +186,9 @@ function oneLine(text, max) {
|
|
|
173
186
|
if (chars.length <= max) return flat;
|
|
174
187
|
return `${chars.slice(0, Math.max(1, max - 1)).join("").trimEnd()}\u2026`;
|
|
175
188
|
}
|
|
189
|
+
function printBlock(text) {
|
|
190
|
+
printHuman(stripControl(text));
|
|
191
|
+
}
|
|
176
192
|
function progress(text) {
|
|
177
193
|
if (isInteractive()) process.stderr.write(kleur.gray(` ${text}
|
|
178
194
|
`));
|
|
@@ -187,13 +203,23 @@ function undoHint(opId, vault) {
|
|
|
187
203
|
function quoteArg(value) {
|
|
188
204
|
return /^[A-Za-z0-9._\/-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
189
205
|
}
|
|
190
|
-
var jsonMode, CONTROL_INTRODUCER, c;
|
|
206
|
+
var jsonMode, CONTROL_INTRODUCER, WIDTH, c;
|
|
191
207
|
var init_ui = __esm({
|
|
192
208
|
"src/ui.ts"() {
|
|
193
209
|
"use strict";
|
|
194
210
|
init_brand();
|
|
195
211
|
jsonMode = false;
|
|
196
212
|
CONTROL_INTRODUCER = /[\u001b\u009b\u009d]/;
|
|
213
|
+
WIDTH = {
|
|
214
|
+
/** A brain name, a note title — a heading-shaped thing. */
|
|
215
|
+
title: 72,
|
|
216
|
+
/** A vault path. Longer, because the tail is the part that identifies it. */
|
|
217
|
+
path: 120,
|
|
218
|
+
/** An id or an `open:` handle. */
|
|
219
|
+
handle: 64,
|
|
220
|
+
/** A claim, a summary, a server hint — a sentence rather than a label. */
|
|
221
|
+
text: 200
|
|
222
|
+
};
|
|
197
223
|
c = kleur;
|
|
198
224
|
}
|
|
199
225
|
});
|
|
@@ -4430,9 +4456,11 @@ function linkCommand() {
|
|
|
4430
4456
|
Nothing was indexed. This is what WOULD be stored:
|
|
4431
4457
|
`));
|
|
4432
4458
|
for (const r of rows) {
|
|
4433
|
-
printHuman(` ${c.bold(
|
|
4434
|
-
printHuman(
|
|
4435
|
-
|
|
4459
|
+
printHuman(` ${c.bold(oneLine(r.title, WIDTH.title))}`);
|
|
4460
|
+
printHuman(
|
|
4461
|
+
c.gray(` ${oneLine(r.summary ?? "(no summary \u2014 title only)", WIDTH.text)}`)
|
|
4462
|
+
);
|
|
4463
|
+
printHuman(c.gray(` from ${oneLine(r.path, WIDTH.path)}`));
|
|
4436
4464
|
}
|
|
4437
4465
|
printHuman(
|
|
4438
4466
|
c.gray(`
|
|
@@ -4705,8 +4733,8 @@ Brain: ${root}`));
|
|
|
4705
4733
|
if (samples.length > 0) {
|
|
4706
4734
|
printHuman(c.gray("\n For example, these exact lines would be stored:"));
|
|
4707
4735
|
for (const r of samples) {
|
|
4708
|
-
printHuman(c.gray(` ${
|
|
4709
|
-
printHuman(c.gray(` ${
|
|
4736
|
+
printHuman(c.gray(` ${oneLine(r.title, WIDTH.title)}`));
|
|
4737
|
+
printHuman(c.gray(` ${oneLine(r.summary ?? "(no summary \u2014 title only)", WIDTH.text)}`));
|
|
4710
4738
|
}
|
|
4711
4739
|
if (notes.length > samples.length) {
|
|
4712
4740
|
printHuman(
|
|
@@ -4826,7 +4854,7 @@ function loginCommand() {
|
|
|
4826
4854
|
},
|
|
4827
4855
|
human: () => {
|
|
4828
4856
|
printHuman(
|
|
4829
|
-
`Already signed in${already.display_name ? ` as ${c.bold(
|
|
4857
|
+
`Already signed in${already.display_name ? ` as ${c.bold(oneLine(already.display_name, WIDTH.title))}` : ""}.`
|
|
4830
4858
|
);
|
|
4831
4859
|
printHuman(c.gray(" `memgineering logout` first if you want a different account."));
|
|
4832
4860
|
}
|
|
@@ -4899,13 +4927,13 @@ function emit(pending, ctx) {
|
|
|
4899
4927
|
},
|
|
4900
4928
|
human: () => {
|
|
4901
4929
|
printHuman("Show this code to whoever is signing in:\n");
|
|
4902
|
-
printHuman(` ${c.bold().yellow(
|
|
4930
|
+
printHuman(` ${c.bold().yellow(oneLine(pending.user_code, WIDTH.handle))}
|
|
4903
4931
|
`);
|
|
4904
4932
|
if (ctx.opened) {
|
|
4905
4933
|
printHuman("Opened the approval page in your browser.");
|
|
4906
4934
|
} else {
|
|
4907
4935
|
printHuman("Open this to approve:\n");
|
|
4908
|
-
printHuman(` ${c.cyan(
|
|
4936
|
+
printHuman(` ${c.cyan(oneLine(pending.verification_url, WIDTH.path))}`);
|
|
4909
4937
|
}
|
|
4910
4938
|
printHuman(
|
|
4911
4939
|
c.gray(
|
|
@@ -4913,7 +4941,7 @@ function emit(pending, ctx) {
|
|
|
4913
4941
|
)
|
|
4914
4942
|
);
|
|
4915
4943
|
if (ctx.opened && ctx.showUrl) {
|
|
4916
|
-
printHuman(c.gray(` ${
|
|
4944
|
+
printHuman(c.gray(` ${oneLine(pending.verification_url, WIDTH.path)}`));
|
|
4917
4945
|
}
|
|
4918
4946
|
printHuman(
|
|
4919
4947
|
c.gray(
|
|
@@ -4955,7 +4983,7 @@ async function waitForApproval(pending, base) {
|
|
|
4955
4983
|
human: () => {
|
|
4956
4984
|
printHuman(
|
|
4957
4985
|
`
|
|
4958
|
-
Signed in${claimed.display_name ? ` as ${c.bold(
|
|
4986
|
+
Signed in${claimed.display_name ? ` as ${c.bold(oneLine(claimed.display_name, WIDTH.title))}` : ""}.`
|
|
4959
4987
|
);
|
|
4960
4988
|
printHuman(c.gray(" The token is stored on this machine only, readable by you alone."));
|
|
4961
4989
|
}
|
|
@@ -5056,17 +5084,19 @@ function printSignInStep(step) {
|
|
|
5056
5084
|
switch (step.status) {
|
|
5057
5085
|
case "signed-in":
|
|
5058
5086
|
printHuman(
|
|
5059
|
-
c.gray(
|
|
5060
|
-
`)
|
|
5087
|
+
c.gray(
|
|
5088
|
+
` signed in${step.displayName ? ` as ${oneLine(step.displayName, WIDTH.title)}` : ""}
|
|
5089
|
+
`
|
|
5090
|
+
)
|
|
5061
5091
|
);
|
|
5062
5092
|
return;
|
|
5063
5093
|
case "started":
|
|
5064
5094
|
printHuman("Show this code to whoever is signing in:\n");
|
|
5065
|
-
printHuman(` ${c.bold().yellow(
|
|
5095
|
+
printHuman(` ${c.bold().yellow(oneLine(step.userCode, WIDTH.handle))}
|
|
5066
5096
|
`);
|
|
5067
5097
|
printHuman(
|
|
5068
5098
|
step.opened ? c.gray(" The approval page is open in the browser.") : `Open this to approve:
|
|
5069
|
-
${c.cyan(
|
|
5099
|
+
${c.cyan(oneLine(step.verificationUrl, WIDTH.path))}`
|
|
5070
5100
|
);
|
|
5071
5101
|
printHuman(
|
|
5072
5102
|
c.gray(" Not waiting \u2014 setup carries on, and the next command collects the token.\n")
|
|
@@ -5074,12 +5104,14 @@ function printSignInStep(step) {
|
|
|
5074
5104
|
return;
|
|
5075
5105
|
case "pending":
|
|
5076
5106
|
printHuman(
|
|
5077
|
-
c.gray(
|
|
5078
|
-
`)
|
|
5107
|
+
c.gray(
|
|
5108
|
+
` a sign-in is waiting for approval \u2014 code ${oneLine(step.userCode, WIDTH.handle)}
|
|
5109
|
+
`
|
|
5110
|
+
)
|
|
5079
5111
|
);
|
|
5080
5112
|
return;
|
|
5081
5113
|
case "unavailable":
|
|
5082
|
-
printHuman(c.yellow(` Could not start a sign-in: ${
|
|
5114
|
+
printHuman(c.yellow(` Could not start a sign-in: ${oneLine(step.because, WIDTH.text)}`));
|
|
5083
5115
|
printHuman(c.gray(" Setup continues \u2014 an account is not needed for any of it.\n"));
|
|
5084
5116
|
return;
|
|
5085
5117
|
case "would-start":
|
|
@@ -7387,11 +7419,11 @@ var init_setup_web = __esm({
|
|
|
7387
7419
|
// src/commands/setup.ts
|
|
7388
7420
|
import { createHash as createHash8 } from "crypto";
|
|
7389
7421
|
import { access, mkdir as mkdir13, readFile as readFile19 } from "fs/promises";
|
|
7390
|
-
import { homedir as homedir4 } from "os";
|
|
7391
7422
|
import { dirname as dirname5, join as join19 } from "path";
|
|
7392
7423
|
import { fileURLToPath } from "url";
|
|
7393
7424
|
import { Command as Command22 } from "commander";
|
|
7394
7425
|
import prompts3 from "prompts";
|
|
7426
|
+
import { homedir as homedir4 } from "os";
|
|
7395
7427
|
function setupCommand() {
|
|
7396
7428
|
return new Command22("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
|
|
7397
7429
|
"--tools <list>",
|
|
@@ -7463,6 +7495,18 @@ function setupCommand() {
|
|
|
7463
7495
|
...signInStepJson(signIn),
|
|
7464
7496
|
defaults_used: defaultsTaken,
|
|
7465
7497
|
dry_run: opts.dryRun === true,
|
|
7498
|
+
/*
|
|
7499
|
+
* The home these paths are relative TO, and it is only here when it
|
|
7500
|
+
* is not the ordinary one.
|
|
7501
|
+
*
|
|
7502
|
+
* Every path in `changes` is reported relative to the home being
|
|
7503
|
+
* installed into, so `.claude/skills/…` names a file without saying
|
|
7504
|
+
* whose. That ambiguity is why an agent writing into a real machine
|
|
7505
|
+
* from inside a sandbox had nothing in front of it that looked
|
|
7506
|
+
* wrong. Present only when scoped, so the common case stays quiet
|
|
7507
|
+
* and the unusual one cannot be mistaken for it.
|
|
7508
|
+
*/
|
|
7509
|
+
...agentHome() !== homedir4() ? { agent_home: agentHome() } : {},
|
|
7466
7510
|
tools: choice.tools.map((t) => t.id),
|
|
7467
7511
|
auto_update: choice.autoUpdate,
|
|
7468
7512
|
hook: choice.hook,
|
|
@@ -7486,9 +7530,27 @@ function setupCommand() {
|
|
|
7486
7530
|
if (opts.silent && changed.length === 0) return;
|
|
7487
7531
|
if (opts.dryRun) {
|
|
7488
7532
|
printSignInStep(signIn);
|
|
7489
|
-
|
|
7533
|
+
if (changed.length === 0) {
|
|
7534
|
+
printHuman(
|
|
7535
|
+
`Nothing to change \u2014 ${choice.tools.map((t) => t.label).join(", ")} ${choice.tools.length === 1 ? "is" : "are"} already up to date.
|
|
7536
|
+
`
|
|
7537
|
+
);
|
|
7538
|
+
printHuman(
|
|
7539
|
+
c.gray(
|
|
7540
|
+
` ${results.length} file(s) checked. Run without \`--dry-run\` to reinstall them anyway.`
|
|
7541
|
+
)
|
|
7542
|
+
);
|
|
7543
|
+
return;
|
|
7544
|
+
}
|
|
7545
|
+
printHuman(`Would change ${changed.length} of ${results.length} file(s):
|
|
7490
7546
|
`);
|
|
7491
7547
|
for (const r of changed) printHuman(c.gray(` ${r.tool} \xB7 ${r.what}`));
|
|
7548
|
+
printHuman(
|
|
7549
|
+
c.gray(
|
|
7550
|
+
`
|
|
7551
|
+
The rest are already up to date. Tools found: ${choice.tools.map((t) => t.label).join(", ")}.`
|
|
7552
|
+
)
|
|
7553
|
+
);
|
|
7492
7554
|
return;
|
|
7493
7555
|
}
|
|
7494
7556
|
printSignInStep(signIn);
|
|
@@ -7589,7 +7651,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7589
7651
|
const hub = await loadAsset(BRAND.hubFileName);
|
|
7590
7652
|
const installed = { ...(await loadConfig()).system.installed };
|
|
7591
7653
|
const mark = (path, hash) => {
|
|
7592
|
-
const key2 = path.startsWith(
|
|
7654
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7593
7655
|
installed[key2] = hash;
|
|
7594
7656
|
};
|
|
7595
7657
|
const persist = async () => {
|
|
@@ -7601,11 +7663,11 @@ async function applySetup(choice, dryRun) {
|
|
|
7601
7663
|
});
|
|
7602
7664
|
};
|
|
7603
7665
|
const wasEdited = (path, onDisk) => {
|
|
7604
|
-
const key2 = path.startsWith(
|
|
7666
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7605
7667
|
const known = installed[key2];
|
|
7606
7668
|
return known !== void 0 && onDisk !== null && fingerprint(onDisk) !== known;
|
|
7607
7669
|
};
|
|
7608
|
-
const keyOf = (path) => path.startsWith(
|
|
7670
|
+
const keyOf = (path) => path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7609
7671
|
try {
|
|
7610
7672
|
return await write();
|
|
7611
7673
|
} finally {
|
|
@@ -7613,7 +7675,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7613
7675
|
}
|
|
7614
7676
|
async function write() {
|
|
7615
7677
|
for (const tool of choice.tools) {
|
|
7616
|
-
const hubPath = join19(
|
|
7678
|
+
const hubPath = join19(agentHome(), tool.hubFile);
|
|
7617
7679
|
const written = await injectBlock(
|
|
7618
7680
|
hubPath,
|
|
7619
7681
|
hub + tailFor(tool),
|
|
@@ -7629,7 +7691,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7629
7691
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
7630
7692
|
for (const skill of BRAND.subSkills) {
|
|
7631
7693
|
const content = await loadAsset(join19(skill, "SKILL.md"));
|
|
7632
|
-
const path = join19(
|
|
7694
|
+
const path = join19(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
7633
7695
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
7634
7696
|
results.push({
|
|
7635
7697
|
tool: tool.label,
|
|
@@ -7699,7 +7761,7 @@ async function resolveMode(opts) {
|
|
|
7699
7761
|
return { kind: mode ?? "agent", chosen: mode !== void 0 };
|
|
7700
7762
|
}
|
|
7701
7763
|
async function detectTools() {
|
|
7702
|
-
const home =
|
|
7764
|
+
const home = agentHome();
|
|
7703
7765
|
const found = [];
|
|
7704
7766
|
for (const tool of TOOLS) {
|
|
7705
7767
|
if (await exists2(join19(home, tool.dir))) found.push(tool);
|
|
@@ -7832,7 +7894,7 @@ async function writeIfDifferent(filePath, content, dryRun) {
|
|
|
7832
7894
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
7833
7895
|
}
|
|
7834
7896
|
async function installHook(spec, tool, dryRun) {
|
|
7835
|
-
const settingsPath = join19(
|
|
7897
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7836
7898
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7837
7899
|
let settings = {};
|
|
7838
7900
|
if (raw !== null) {
|
|
@@ -7882,7 +7944,7 @@ async function installHook(spec, tool, dryRun) {
|
|
|
7882
7944
|
return raw === null ? "injected" : "updated";
|
|
7883
7945
|
}
|
|
7884
7946
|
async function removeHook(spec, tool, dryRun) {
|
|
7885
|
-
const settingsPath = join19(
|
|
7947
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7886
7948
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7887
7949
|
if (raw === null) return "unchanged";
|
|
7888
7950
|
let parsed;
|
|
@@ -8093,7 +8155,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
8093
8155
|
});
|
|
8094
8156
|
|
|
8095
8157
|
// src/program.ts
|
|
8096
|
-
import { Command as
|
|
8158
|
+
import { Command as Command28 } from "commander";
|
|
8097
8159
|
|
|
8098
8160
|
// src/help.ts
|
|
8099
8161
|
init_ui();
|
|
@@ -8117,7 +8179,7 @@ var COMMAND_GROUPS = [
|
|
|
8117
8179
|
{
|
|
8118
8180
|
title: "ACCOUNT",
|
|
8119
8181
|
blurb: "optional \u2014 connect a hosted brain; everything above works without it",
|
|
8120
|
-
commands: ["login", "logout", "whoami", "push", "pull"]
|
|
8182
|
+
commands: ["login", "logout", "whoami", "brains", "push", "pull"]
|
|
8121
8183
|
},
|
|
8122
8184
|
{
|
|
8123
8185
|
title: "SYSTEM",
|
|
@@ -8130,7 +8192,12 @@ var ENV_ROWS = [
|
|
|
8130
8192
|
["MEMGINEERING_HOME=<dir>", "state + derived index (default: ~/.memgineering)"],
|
|
8131
8193
|
["MEMGINEERING_JSON=1", "force --json framing without the flag"],
|
|
8132
8194
|
["MEMGINEERING_NO_UPDATE=1", "skip the update check for this run"],
|
|
8133
|
-
["MEMGINEERING_API_URL=<url>", "hosted brain server (default: https://api.memgineering.com)"]
|
|
8195
|
+
["MEMGINEERING_API_URL=<url>", "hosted brain server (default: https://api.memgineering.com)"],
|
|
8196
|
+
// The one that keeps `setup` off a real machine, and it was missing from this
|
|
8197
|
+
// list while the other four were here. An agent testing this CLI in a sandbox
|
|
8198
|
+
// had no documented handle for where `setup` writes — which is how one of them
|
|
8199
|
+
// overwrote the live skill files of the session testing it.
|
|
8200
|
+
["MEMGINEERING_AGENT_HOME=<dir>", "where `setup` installs (default: your home)"]
|
|
8134
8201
|
];
|
|
8135
8202
|
function commandLabel(sub) {
|
|
8136
8203
|
const args = sub.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
|
|
@@ -8283,12 +8350,13 @@ var cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
|
8283
8350
|
var cloudReindex = (brain, args = {}) => call(brain, "POST", "/reindex", {
|
|
8284
8351
|
...args.rebuild === true ? { rebuild: true } : {}
|
|
8285
8352
|
});
|
|
8286
|
-
async function listCloudBrains(token, baseUrl) {
|
|
8353
|
+
async function listCloudBrains(token, baseUrl, opts = {}) {
|
|
8287
8354
|
const res = await apiRequest({
|
|
8288
8355
|
method: "GET",
|
|
8289
8356
|
path: "/v1/brains",
|
|
8290
8357
|
token,
|
|
8291
|
-
baseUrl
|
|
8358
|
+
baseUrl,
|
|
8359
|
+
...opts.retries === void 0 ? {} : { retries: opts.retries }
|
|
8292
8360
|
});
|
|
8293
8361
|
return res.body?.brains ?? [];
|
|
8294
8362
|
}
|
|
@@ -8360,11 +8428,11 @@ function describeHeldBack(held) {
|
|
|
8360
8428
|
return [
|
|
8361
8429
|
...heldBackGroups(held).flatMap(
|
|
8362
8430
|
({ label, rows }) => rows.map(
|
|
8363
|
-
(r) => `${
|
|
8431
|
+
(r) => `${oneLine(r.path, WIDTH.path)} \u2014 ${label}${r.detail === null ? "" : ` (${oneLine(r.detail, WIDTH.text)})`}`
|
|
8364
8432
|
)
|
|
8365
8433
|
),
|
|
8366
8434
|
...held.folders.map(
|
|
8367
|
-
(dir) => `${
|
|
8435
|
+
(dir) => `${oneLine(dir, WIDTH.path)}/ \u2014 an excluded folder, whose contents were never walked`
|
|
8368
8436
|
)
|
|
8369
8437
|
];
|
|
8370
8438
|
}
|
|
@@ -8384,7 +8452,7 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8384
8452
|
for (const { label, rows } of chosen) {
|
|
8385
8453
|
if (rows.length === 0) continue;
|
|
8386
8454
|
printHuman(c.gray(` ${rows.length} \u2014 ${label}:`));
|
|
8387
|
-
for (const r of rows.slice(0, 10)) printHuman(c.gray(` ${
|
|
8455
|
+
for (const r of rows.slice(0, 10)) printHuman(c.gray(` ${oneLine(r.path, WIDTH.path)}`));
|
|
8388
8456
|
if (rows.length > 10) printHuman(c.gray(` \u2026 and ${rows.length - 10} more`));
|
|
8389
8457
|
}
|
|
8390
8458
|
if (held.folders.length > 0) {
|
|
@@ -8394,7 +8462,8 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8394
8462
|
inside them are not counted above:`
|
|
8395
8463
|
)
|
|
8396
8464
|
);
|
|
8397
|
-
for (const dir of held.folders.slice(0, 10))
|
|
8465
|
+
for (const dir of held.folders.slice(0, 10))
|
|
8466
|
+
printHuman(c.gray(` ${oneLine(dir, WIDTH.path)}/`));
|
|
8398
8467
|
if (held.folders.length > 10) {
|
|
8399
8468
|
printHuman(c.gray(` \u2026 and ${held.folders.length - 10} more`));
|
|
8400
8469
|
}
|
|
@@ -8408,7 +8477,11 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
8408
8477
|
)
|
|
8409
8478
|
);
|
|
8410
8479
|
for (const d of guessed.slice(0, 10)) {
|
|
8411
|
-
printHuman(
|
|
8480
|
+
printHuman(
|
|
8481
|
+
c.gray(
|
|
8482
|
+
` ${oneLine(d.path, WIDTH.path)} (matched "${oneLine(d.term ?? "?", WIDTH.title)}")`
|
|
8483
|
+
)
|
|
8484
|
+
);
|
|
8412
8485
|
}
|
|
8413
8486
|
if (guessed.length > 10) printHuman(c.gray(` \u2026 and ${guessed.length - 10} more`));
|
|
8414
8487
|
printHuman(
|
|
@@ -8456,13 +8529,13 @@ function isOpaqueId(id) {
|
|
|
8456
8529
|
return id.length === OPAQUE_ID_LENGTH && /^[0-9ABCDEFGHJKMNPQRSTVWXYZ]+$/.test(id);
|
|
8457
8530
|
}
|
|
8458
8531
|
function displayHandle(id, allIds) {
|
|
8459
|
-
if (!isOpaqueId(id)) return
|
|
8532
|
+
if (!isOpaqueId(id)) return oneLine(id, WIDTH.handle);
|
|
8460
8533
|
for (let len = MIN_HANDLE_LENGTH; len < id.length; len++) {
|
|
8461
8534
|
const prefix = id.slice(0, len);
|
|
8462
8535
|
const matches = allIds.filter((other) => other.startsWith(prefix));
|
|
8463
8536
|
if (matches.length === 1) return prefix;
|
|
8464
8537
|
}
|
|
8465
|
-
return
|
|
8538
|
+
return oneLine(id, WIDTH.handle);
|
|
8466
8539
|
}
|
|
8467
8540
|
function matchIds(ref, allIds) {
|
|
8468
8541
|
const exact = allIds.filter((id) => id === ref);
|
|
@@ -8689,14 +8762,83 @@ init_api_client();
|
|
|
8689
8762
|
init_credentials();
|
|
8690
8763
|
init_pending_login();
|
|
8691
8764
|
init_config();
|
|
8765
|
+
init_ui();
|
|
8692
8766
|
init_vault();
|
|
8693
8767
|
async function resolveTarget(opts = {}) {
|
|
8694
8768
|
if (opts.local !== true) {
|
|
8695
8769
|
const cloud = await cloudTarget();
|
|
8696
|
-
if (cloud
|
|
8770
|
+
if (cloud && cloud.kind === "cloud") {
|
|
8771
|
+
if (opts.vault !== void 0) {
|
|
8772
|
+
throw memgError(
|
|
8773
|
+
"invalid_input",
|
|
8774
|
+
`\`--vault\` names a folder on this machine, and this machine reads the hosted brain \`${oneLine(cloud.name, 72)}\``,
|
|
8775
|
+
`Nothing was read from \`${oneLine(opts.vault, 120)}\`, and nothing was changed.
|
|
8776
|
+
|
|
8777
|
+
To read that folder just this once: add \`--local\`
|
|
8778
|
+
To change which hosted brain this machine reads: \`memgineering use --brain <name>\`
|
|
8779
|
+
|
|
8780
|
+
This used to answer from the hosted brain without saying so, so a check of one
|
|
8781
|
+
brain silently reported another.`
|
|
8782
|
+
);
|
|
8783
|
+
}
|
|
8784
|
+
return cloud;
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
8787
|
+
try {
|
|
8788
|
+
const { brain, via } = await resolveBrain(opts.vault);
|
|
8789
|
+
return { kind: "local", brain, via };
|
|
8790
|
+
} catch (err) {
|
|
8791
|
+
if (!isNoVaultLinked(err) || opts.local === true) throw err;
|
|
8792
|
+
throw await accountAwareRefusal() ?? err;
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
var isNoVaultLinked = (err) => typeof err === "object" && err !== null && err.code === "no_vault_linked";
|
|
8796
|
+
async function accountAwareRefusal() {
|
|
8797
|
+
try {
|
|
8798
|
+
const base = apiUrl();
|
|
8799
|
+
const credentials = await readCredentials();
|
|
8800
|
+
if (!credentials || credentials.api_url !== base) {
|
|
8801
|
+
const cloud = (await loadConfig()).brain.cloud;
|
|
8802
|
+
if (!cloud || cloud.api_url !== base) return null;
|
|
8803
|
+
return memgError(
|
|
8804
|
+
"not_signed_in",
|
|
8805
|
+
`this machine is signed out of \`${oneLine(cloud.name, 72)}\``,
|
|
8806
|
+
"Your notes are on that account \u2014 nothing is lost, and nothing here needs creating.\n\nSign back in: `memgineering login`\n\nDo NOT run `init` or `link` to fix this. They make a NEW empty brain on this\nmachine and leave the notes where they are."
|
|
8807
|
+
);
|
|
8808
|
+
}
|
|
8809
|
+
const brains = await listCloudBrains(credentials.token, base, { retries: 0 });
|
|
8810
|
+
if (brains.length === 0) return null;
|
|
8811
|
+
const names = brains.map((b) => ` ${oneLine(b.name, 72)}`).join("\n");
|
|
8812
|
+
return memgError(
|
|
8813
|
+
"no_vault_linked",
|
|
8814
|
+
"this machine is not pointed at any brain yet",
|
|
8815
|
+
`Your notes are on your account \u2014 nothing is lost, and nothing here needs creating.
|
|
8816
|
+
This account holds:
|
|
8817
|
+
${names}
|
|
8818
|
+
|
|
8819
|
+
Point at one: \`memgineering use --brain <name>\`
|
|
8820
|
+
|
|
8821
|
+
Do NOT run \`init\` or \`link\` to fix this. They make a NEW empty brain on this
|
|
8822
|
+
machine, which leaves the notes above exactly where they are and adds an empty
|
|
8823
|
+
notebook that looks like the answer.`
|
|
8824
|
+
);
|
|
8825
|
+
} catch (err) {
|
|
8826
|
+
if (err instanceof MemgError && err.code !== "server_unreachable") {
|
|
8827
|
+
return memgError(
|
|
8828
|
+
"not_signed_in",
|
|
8829
|
+
"this machine cannot read the account it is signed in to",
|
|
8830
|
+
`The server refused: ${oneLine(err.message, 120)}
|
|
8831
|
+
|
|
8832
|
+
Your notes are on that account \u2014 nothing is lost, and nothing here needs creating.
|
|
8833
|
+
|
|
8834
|
+
Sign in again: \`memgineering login\`
|
|
8835
|
+
|
|
8836
|
+
Do NOT run \`init\` or \`link\` to fix this. They make a NEW empty brain on this
|
|
8837
|
+
machine and leave the notes where they are.`
|
|
8838
|
+
);
|
|
8839
|
+
}
|
|
8840
|
+
return null;
|
|
8697
8841
|
}
|
|
8698
|
-
const { brain, via } = await resolveBrain(opts.vault);
|
|
8699
|
-
return { kind: "local", brain, via };
|
|
8700
8842
|
}
|
|
8701
8843
|
async function hostedPointer() {
|
|
8702
8844
|
const base = apiUrl();
|
|
@@ -8788,7 +8930,9 @@ function reindexCommand() {
|
|
|
8788
8930
|
)
|
|
8789
8931
|
);
|
|
8790
8932
|
for (const d of byContent.slice(0, 10)) {
|
|
8791
|
-
printHuman(
|
|
8933
|
+
printHuman(
|
|
8934
|
+
c.gray(` ${oneLine(d.path, WIDTH.path)} (${oneLine(d.detail, WIDTH.text)})`)
|
|
8935
|
+
);
|
|
8792
8936
|
}
|
|
8793
8937
|
if (byContent.length > 10) {
|
|
8794
8938
|
printHuman(c.gray(` \u2026 and ${byContent.length - 10} more`));
|
|
@@ -8809,7 +8953,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8809
8953
|
// route documents, spelled the way the route spells it.
|
|
8810
8954
|
json: { brain: { hosted: true, name: target.name }, ...answered },
|
|
8811
8955
|
human: () => {
|
|
8812
|
-
printHuman(`Reindexed ${
|
|
8956
|
+
printHuman(`Reindexed ${oneLine(target.name, WIDTH.title)}.
|
|
8813
8957
|
`);
|
|
8814
8958
|
if (lexical === void 0) {
|
|
8815
8959
|
printHuman(c.yellow("\u26A0 this brain\u2019s server did not report the card index"));
|
|
@@ -8847,7 +8991,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8847
8991
|
if (answered.usable === false) {
|
|
8848
8992
|
printHuman(
|
|
8849
8993
|
c.yellow(
|
|
8850
|
-
` \u26A0 vector search is off for this brain (${
|
|
8994
|
+
` \u26A0 vector search is off for this brain (${oneLine(answered.unusable_reason ?? "reason not given", WIDTH.text)})`
|
|
8851
8995
|
)
|
|
8852
8996
|
);
|
|
8853
8997
|
}
|
|
@@ -8866,7 +9010,7 @@ function printFailures(failed, failures) {
|
|
|
8866
9010
|
for (const failure of (failures ?? []).slice(0, 3)) {
|
|
8867
9011
|
printHuman(
|
|
8868
9012
|
c.gray(
|
|
8869
|
-
` ${
|
|
9013
|
+
` ${oneLine(failure.path, WIDTH.path)} (${oneLine(failure.kind, WIDTH.title)}: ${oneLine(failure.detail, WIDTH.text)})`
|
|
8870
9014
|
)
|
|
8871
9015
|
);
|
|
8872
9016
|
}
|
|
@@ -8883,18 +9027,39 @@ function nextStep(stopReason) {
|
|
|
8883
9027
|
case "unusable_index":
|
|
8884
9028
|
return "This brain holds vectors of more than one width, so none of them can be compared. `memgineering reindex --rebuild` re-derives them from the model as it is configured now.";
|
|
8885
9029
|
default:
|
|
8886
|
-
return `The server stopped for \`${
|
|
9030
|
+
return `The server stopped for \`${oneLine(stopReason, WIDTH.title)}\`, which this version has no next step for. Running \`memgineering reindex\` again is safe.`;
|
|
8887
9031
|
}
|
|
8888
9032
|
}
|
|
8889
9033
|
function unlinkCommand() {
|
|
8890
|
-
return new Command2("unlink").description("stop reading a brain \u2014 your notes are not touched").argument("[path]", "which brain (default: the one this folder resolves to)").option("--purge", "also delete the local index, event log and undo snapshots").action(async (path, opts) => {
|
|
8891
|
-
|
|
9034
|
+
return new Command2("unlink").description("stop reading a brain \u2014 your notes are not touched").argument("[path]", "which brain (default: the one this folder resolves to)").option("--brain", "stop reading the HOSTED brain instead of a folder on this disk").option("--purge", "also delete the local index, event log and undo snapshots").action(async (path, opts) => {
|
|
9035
|
+
if (opts.brain === true) {
|
|
9036
|
+
await stopReadingHosted(path);
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
const hostedNow = await hostedPointer();
|
|
9040
|
+
const linkedNow = (await loadConfig()).brain.brains;
|
|
9041
|
+
if (hostedNow && linkedNow.length === 0) {
|
|
9042
|
+
throw memgError(
|
|
9043
|
+
"invalid_input",
|
|
9044
|
+
`nothing is linked on this disk \u2014 this machine reads the hosted brain "${oneLine(hostedNow.name, 72)}"`,
|
|
9045
|
+
"Nothing was changed, and nothing here creates a brain.\n`memgineering unlink --brain` stops reading the hosted one.\n`memgineering link <folder>` adds notes on this disk alongside it."
|
|
9046
|
+
);
|
|
9047
|
+
}
|
|
9048
|
+
const { brain } = await resolveBrain(path).catch(async (err) => {
|
|
9049
|
+
await resolveTarget({});
|
|
9050
|
+
throw err;
|
|
9051
|
+
});
|
|
8892
9052
|
const cfg = await loadConfig();
|
|
8893
9053
|
const remaining = cfg.brain.brains.filter((b) => b.root !== brain.root);
|
|
8894
9054
|
const nextDefault = cfg.brain.default === brain.root ? remaining[0]?.root : cfg.brain.default;
|
|
9055
|
+
const { default: _dropped, ...restOfBrain } = cfg.brain;
|
|
8895
9056
|
await saveConfig({
|
|
8896
9057
|
...cfg,
|
|
8897
|
-
brain: {
|
|
9058
|
+
brain: {
|
|
9059
|
+
...restOfBrain,
|
|
9060
|
+
brains: remaining,
|
|
9061
|
+
...nextDefault === void 0 ? {} : { default: nextDefault }
|
|
9062
|
+
}
|
|
8898
9063
|
});
|
|
8899
9064
|
if (opts.purge) {
|
|
8900
9065
|
await clearCache(cacheDirFor(brain.root, brandHome()));
|
|
@@ -8982,6 +9147,54 @@ async function readLines(root, name) {
|
|
|
8982
9147
|
const raw = await readFile10(join10(root, name), "utf8").catch(() => "");
|
|
8983
9148
|
return raw.split("\n").map((l) => l.replace(/\r$/, "").trim()).filter((l) => l !== "" && !l.startsWith("#"));
|
|
8984
9149
|
}
|
|
9150
|
+
async function stopReadingHosted(path) {
|
|
9151
|
+
if (path !== void 0) {
|
|
9152
|
+
throw memgError(
|
|
9153
|
+
"invalid_input",
|
|
9154
|
+
"`unlink --brain` does not take a path",
|
|
9155
|
+
"It clears the one hosted brain this machine reads, which is a single setting for the whole machine. To unlink a folder on this disk, run `memgineering unlink <path>` without `--brain`."
|
|
9156
|
+
);
|
|
9157
|
+
}
|
|
9158
|
+
const cfg = await loadConfig();
|
|
9159
|
+
const previous = cfg.brain.cloud ?? null;
|
|
9160
|
+
if (previous === null) {
|
|
9161
|
+
throw memgError(
|
|
9162
|
+
"invalid_input",
|
|
9163
|
+
"this machine is not reading a hosted brain",
|
|
9164
|
+
"Nothing was changed. `memgineering brains` lists what the account holds, and `memgineering use --brain <name>` points at one."
|
|
9165
|
+
);
|
|
9166
|
+
}
|
|
9167
|
+
await saveConfig({
|
|
9168
|
+
...cfg,
|
|
9169
|
+
// Rebuilt without `cloud` rather than set to undefined, so what lands on
|
|
9170
|
+
// disk is a config with no such key rather than one asserting null.
|
|
9171
|
+
brain: {
|
|
9172
|
+
brains: cfg.brain.brains,
|
|
9173
|
+
...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
|
|
9174
|
+
}
|
|
9175
|
+
});
|
|
9176
|
+
printDual({
|
|
9177
|
+
json: {
|
|
9178
|
+
stopped_reading: { id: previous.id, name: previous.name, api_url: previous.api_url },
|
|
9179
|
+
remaining_local: cfg.brain.brains.length
|
|
9180
|
+
},
|
|
9181
|
+
human: () => {
|
|
9182
|
+
printHuman(`No longer reading \`${oneLine(previous.name, 72)}\`.
|
|
9183
|
+
`);
|
|
9184
|
+
printHuman(
|
|
9185
|
+
c.gray(
|
|
9186
|
+
" Nothing on the server was touched \u2014 the brain, its notes and its ledger\n are exactly where they were. `memgineering use --brain <name>` points back."
|
|
9187
|
+
)
|
|
9188
|
+
);
|
|
9189
|
+
if (cfg.brain.brains.length === 0) {
|
|
9190
|
+
printHuman(
|
|
9191
|
+
c.yellow("\n No brain is linked on this disk either, so nothing answers here now.")
|
|
9192
|
+
);
|
|
9193
|
+
printHuman(c.gray(" `memgineering link <folder>` connects notes you already have."));
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
});
|
|
9197
|
+
}
|
|
8985
9198
|
|
|
8986
9199
|
// src/commands/consolidate.ts
|
|
8987
9200
|
init_src();
|
|
@@ -9279,17 +9492,23 @@ function openCommand() {
|
|
|
9279
9492
|
brain: { hosted: true, name: target.name }
|
|
9280
9493
|
},
|
|
9281
9494
|
human: () => {
|
|
9282
|
-
printHuman(
|
|
9283
|
-
|
|
9495
|
+
printHuman(
|
|
9496
|
+
`## ${oneLine(String(found.memory["title"] ?? found.path), WIDTH.title)}
|
|
9497
|
+
`
|
|
9498
|
+
);
|
|
9284
9499
|
const summary = found.memory["summary"];
|
|
9285
9500
|
if (typeof summary === "string" && summary !== "") {
|
|
9286
|
-
printHuman(`> ${
|
|
9501
|
+
printHuman(`> ${oneLine(summary, WIDTH.text)}
|
|
9287
9502
|
`);
|
|
9288
9503
|
}
|
|
9289
|
-
printHuman(
|
|
9504
|
+
printHuman(
|
|
9505
|
+
c.gray(
|
|
9506
|
+
`${oneLine(found.path, WIDTH.path)} \xB7 ${oneLine(target.name, WIDTH.title)}`
|
|
9507
|
+
)
|
|
9508
|
+
);
|
|
9290
9509
|
if (detail !== "card" && found.body !== "") {
|
|
9291
|
-
printHuman(
|
|
9292
|
-
|
|
9510
|
+
printHuman("\n");
|
|
9511
|
+
printBlock(found.body);
|
|
9293
9512
|
}
|
|
9294
9513
|
}
|
|
9295
9514
|
});
|
|
@@ -9326,7 +9545,7 @@ ${stripControl(found.body)}`);
|
|
|
9326
9545
|
...stage
|
|
9327
9546
|
},
|
|
9328
9547
|
human: () => {
|
|
9329
|
-
printHuman(`## ${
|
|
9548
|
+
printHuman(`## ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9330
9549
|
`);
|
|
9331
9550
|
if (scaffolding) {
|
|
9332
9551
|
printHuman(
|
|
@@ -9339,13 +9558,17 @@ ${stripControl(found.body)}`);
|
|
|
9339
9558
|
}
|
|
9340
9559
|
printHuman(
|
|
9341
9560
|
`
|
|
9342
|
-
\`id: ${
|
|
9561
|
+
\`id: ${oneLine(entry.memory.id, WIDTH.handle)}\` \xB7 \`${oneLine(entry.path, WIDTH.path)}\`
|
|
9343
9562
|
`
|
|
9344
9563
|
);
|
|
9345
9564
|
if (entry.memory.summary) printHuman(quoteBlock(entry.memory.summary) + "\n");
|
|
9346
9565
|
for (const claim of claims) {
|
|
9347
|
-
printHuman(`- ${
|
|
9348
|
-
printHuman(
|
|
9566
|
+
printHuman(`- ${oneLine(claim.text, WIDTH.text)}`);
|
|
9567
|
+
printHuman(
|
|
9568
|
+
c.gray(
|
|
9569
|
+
` ${oneLine(claim.confidence, WIDTH.title)} \xB7 ${oneLine(claim.status, WIDTH.title)}`
|
|
9570
|
+
)
|
|
9571
|
+
);
|
|
9349
9572
|
}
|
|
9350
9573
|
if (claims.length === 0 && scaffolding) {
|
|
9351
9574
|
printHuman(
|
|
@@ -9376,13 +9599,14 @@ makes the two surface together as a conflict.`
|
|
|
9376
9599
|
}
|
|
9377
9600
|
if (stage.outline && stage.outline.length > 0) {
|
|
9378
9601
|
printHuman("\n### sections\n");
|
|
9379
|
-
for (const heading of stage.outline)
|
|
9602
|
+
for (const heading of stage.outline)
|
|
9603
|
+
printHuman(`- ${oneLine(heading, WIDTH.title)}`);
|
|
9380
9604
|
printHuman("");
|
|
9381
9605
|
}
|
|
9382
9606
|
if (stage.sections) {
|
|
9383
9607
|
for (const section of stage.sections) {
|
|
9384
9608
|
printHuman(`
|
|
9385
|
-
### ${
|
|
9609
|
+
### ${oneLine(section.label, WIDTH.title)}
|
|
9386
9610
|
`);
|
|
9387
9611
|
printHuman(quoteBlock(section.body));
|
|
9388
9612
|
}
|
|
@@ -9942,11 +10166,11 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
9942
10166
|
after: preview.after
|
|
9943
10167
|
},
|
|
9944
10168
|
human: () => {
|
|
9945
|
-
printHuman(`## revise (dry run): ${
|
|
10169
|
+
printHuman(`## revise (dry run): ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9946
10170
|
`);
|
|
9947
|
-
printHuman(`\`${
|
|
10171
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
9948
10172
|
`);
|
|
9949
|
-
|
|
10173
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
9950
10174
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
9951
10175
|
}
|
|
9952
10176
|
});
|
|
@@ -10006,10 +10230,10 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
10006
10230
|
human: () => {
|
|
10007
10231
|
if (proposal.action === "conflict") {
|
|
10008
10232
|
printHuman(
|
|
10009
|
-
`Recorded a disagreement about **${
|
|
10233
|
+
`Recorded a disagreement about **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
10010
10234
|
`
|
|
10011
10235
|
);
|
|
10012
|
-
printHuman(`\`${
|
|
10236
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
10013
10237
|
`);
|
|
10014
10238
|
printHuman(
|
|
10015
10239
|
c.gray(
|
|
@@ -10017,12 +10241,12 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
10017
10241
|
)
|
|
10018
10242
|
);
|
|
10019
10243
|
} else {
|
|
10020
|
-
printHuman(`Revised **${
|
|
10244
|
+
printHuman(`Revised **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
10021
10245
|
`);
|
|
10022
|
-
printHuman(`\`${
|
|
10246
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
10023
10247
|
`);
|
|
10024
10248
|
}
|
|
10025
|
-
|
|
10249
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
10026
10250
|
if (drift) {
|
|
10027
10251
|
printHuman(
|
|
10028
10252
|
c.yellow(`
|
|
@@ -10089,18 +10313,18 @@ async function reviseHosted(where, ref, opts) {
|
|
|
10089
10313
|
json: { brain: where.name, ...result },
|
|
10090
10314
|
human: () => {
|
|
10091
10315
|
if (result.dry_run) {
|
|
10092
|
-
printHuman(`## revise (dry run) \u2014 ${
|
|
10316
|
+
printHuman(`## revise (dry run) \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
10093
10317
|
`);
|
|
10094
|
-
printHuman(`\`${
|
|
10318
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10095
10319
|
`);
|
|
10096
|
-
|
|
10320
|
+
printBlock(renderDiff(result.before, result.after));
|
|
10097
10321
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
10098
10322
|
return;
|
|
10099
10323
|
}
|
|
10100
10324
|
if (opts.action === "conflict") {
|
|
10101
|
-
printHuman(`Recorded a disagreement in **${
|
|
10325
|
+
printHuman(`Recorded a disagreement in **${oneLine(where.name, WIDTH.title)}**.
|
|
10102
10326
|
`);
|
|
10103
|
-
printHuman(`\`${
|
|
10327
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10104
10328
|
`);
|
|
10105
10329
|
printHuman(
|
|
10106
10330
|
c.gray(
|
|
@@ -10108,16 +10332,16 @@ async function reviseHosted(where, ref, opts) {
|
|
|
10108
10332
|
)
|
|
10109
10333
|
);
|
|
10110
10334
|
} else {
|
|
10111
|
-
printHuman(`Revised in **${
|
|
10335
|
+
printHuman(`Revised in **${oneLine(where.name, WIDTH.title)}**.
|
|
10112
10336
|
`);
|
|
10113
|
-
printHuman(`\`${
|
|
10337
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
10114
10338
|
`);
|
|
10115
10339
|
}
|
|
10116
|
-
|
|
10340
|
+
printBlock(renderDiff(result.before, result.after));
|
|
10117
10341
|
if (result.body_drift) {
|
|
10118
10342
|
printHuman(
|
|
10119
10343
|
c.yellow(`
|
|
10120
|
-
\u26A0 the body still reads "${
|
|
10344
|
+
\u26A0 the body still reads "${oneLine(result.body_drift, WIDTH.text)}"`) + c.gray(
|
|
10121
10345
|
"\n \u2014 the frontmatter now concludes differently. The prose is yours and was not\n touched; edit it yourself if it should agree."
|
|
10122
10346
|
)
|
|
10123
10347
|
);
|
|
@@ -10141,7 +10365,7 @@ To put it back: \`memgineering revise ${ref} --claim ${JSON.stringify(
|
|
|
10141
10365
|
);
|
|
10142
10366
|
} else if (result.reverse?.hint) {
|
|
10143
10367
|
printHuman(c.gray(`
|
|
10144
|
-
${
|
|
10368
|
+
${oneLine(result.reverse.hint, WIDTH.text)}`));
|
|
10145
10369
|
}
|
|
10146
10370
|
}
|
|
10147
10371
|
});
|
|
@@ -10185,7 +10409,7 @@ function nothingToChange(path, ref, verb = "revise") {
|
|
|
10185
10409
|
memgineering revise ${ref} --action reinforce --claim "<the conclusion>" --reason "<what confirms it>"` : `It is already ${verb === "retire" ? "retired" : "current"}. \`memgineering ${verb === "retire" ? "unretire" : "retire"} ${ref}\` is what changes that.`;
|
|
10186
10410
|
return memgError(
|
|
10187
10411
|
"nothing_to_change",
|
|
10188
|
-
`${
|
|
10412
|
+
`${oneLine(path, WIDTH.path)} already says exactly this`,
|
|
10189
10413
|
`Nothing was written. ${way}
|
|
10190
10414
|
A \`--reason\` does not make it a change either \u2014 it would record an operation that changed nothing, and one whose \`undo\` would reverse nothing while saying it had.
|
|
10191
10415
|
To see what the note says now: \`memgineering open ${ref}\``
|
|
@@ -10293,20 +10517,22 @@ function renderSummary(p) {
|
|
|
10293
10517
|
const claim = p.claim === void 0 || p.claim === null ? null : oneLine(p.claim, CLAIM_WIDTH);
|
|
10294
10518
|
const unavailable = p.claim === void 0 && !claimless;
|
|
10295
10519
|
const lines2 = [
|
|
10296
|
-
`- **${
|
|
10520
|
+
`- **${oneLine(p.action, WIDTH.title)}**${p.proposed_kind ? ` \u2192 ${p.proposed_kind}` : ""} \`${oneLine(p.target, WIDTH.path)}\``,
|
|
10297
10521
|
` ${p.id} ${p.state}`
|
|
10298
10522
|
];
|
|
10299
10523
|
if (claim !== null && claim !== "") lines2.push(` \u2192 ${claim}`);
|
|
10300
10524
|
else if (unavailable) {
|
|
10301
10525
|
lines2.push(
|
|
10302
10526
|
c.gray(
|
|
10303
|
-
` \u2192 (claim unavailable: this brain's server is older than the CLI. \`memgineering open ${
|
|
10527
|
+
` \u2192 (claim unavailable: this brain's server is older than the CLI. \`memgineering open ${oneLine(p.memory_id ?? p.target, WIDTH.handle)}\` shows what the note says NOW; upgrading the server restores the claim.)`
|
|
10304
10528
|
)
|
|
10305
10529
|
);
|
|
10306
10530
|
}
|
|
10307
10531
|
lines2.push(quoteBlock(p.reason, " > "));
|
|
10308
10532
|
if (p.supersedes.length > 0) {
|
|
10309
|
-
lines2.push(
|
|
10533
|
+
lines2.push(
|
|
10534
|
+
c.gray(` replaces: ${p.supersedes.map((id) => oneLine(id, WIDTH.handle)).join(", ")}`)
|
|
10535
|
+
);
|
|
10310
10536
|
}
|
|
10311
10537
|
return lines2.join("\n");
|
|
10312
10538
|
}
|
|
@@ -10422,10 +10648,10 @@ function renderApproval(applied, vault) {
|
|
|
10422
10648
|
},
|
|
10423
10649
|
human: () => {
|
|
10424
10650
|
printHuman(
|
|
10425
|
-
`Applied **${
|
|
10651
|
+
`Applied **${oneLine(applied.action, WIDTH.title)}** to \`${oneLine(applied.target, WIDTH.path)}\`.
|
|
10426
10652
|
`
|
|
10427
10653
|
);
|
|
10428
|
-
|
|
10654
|
+
printBlock(renderDiff(applied.before, applied.after));
|
|
10429
10655
|
printHuman(c.gray(`
|
|
10430
10656
|
Undo: \`${undoHint(applied.opId, vault)}\``));
|
|
10431
10657
|
}
|
|
@@ -10435,7 +10661,7 @@ function renderRejection(id, state, target) {
|
|
|
10435
10661
|
printDual({
|
|
10436
10662
|
json: { proposal_id: id, state, target },
|
|
10437
10663
|
human: () => {
|
|
10438
|
-
printHuman(`Declined \`${
|
|
10664
|
+
printHuman(`Declined \`${oneLine(target, WIDTH.path)}\`.
|
|
10439
10665
|
`);
|
|
10440
10666
|
printHuman(c.gray("The note was not touched."));
|
|
10441
10667
|
}
|
|
@@ -10493,9 +10719,9 @@ function renderRebase(id, state, target, before, after) {
|
|
|
10493
10719
|
hint: `This is the change as it applies to the note TODAY, and it has not been approved. Read the diff, then \`memgineering proposals approve ${id}\` \u2014 or reject it if the note moving on made it wrong.`
|
|
10494
10720
|
},
|
|
10495
10721
|
human: () => {
|
|
10496
|
-
printHuman(`Recomputed against \`${
|
|
10722
|
+
printHuman(`Recomputed against \`${oneLine(target, WIDTH.path)}\`.
|
|
10497
10723
|
`);
|
|
10498
|
-
|
|
10724
|
+
printBlock(renderDiff(before, after));
|
|
10499
10725
|
printHuman(
|
|
10500
10726
|
c.gray(
|
|
10501
10727
|
`
|
|
@@ -10656,16 +10882,16 @@ var TITLE_WIDTH = 72;
|
|
|
10656
10882
|
function noteLines(id, title, path) {
|
|
10657
10883
|
const shown = oneLine(title, TITLE_WIDTH);
|
|
10658
10884
|
return [
|
|
10659
|
-
shown === "" ? ` ${
|
|
10885
|
+
shown === "" ? ` ${oneLine(id, WIDTH.handle)}` : ` ${oneLine(id, WIDTH.handle)} ${shown}`,
|
|
10660
10886
|
// Backticked, as it was before the title moved onto the row above it. This
|
|
10661
10887
|
// output is read by agents as markdown, and a path outside code formatting
|
|
10662
10888
|
// is a path they have to guess the extent of.
|
|
10663
|
-
c.gray(` \`${
|
|
10889
|
+
c.gray(` \`${oneLine(path, WIDTH.path)}\``)
|
|
10664
10890
|
];
|
|
10665
10891
|
}
|
|
10666
10892
|
function renderCandidate(candidate) {
|
|
10667
10893
|
const lines2 = [
|
|
10668
|
-
`- **${
|
|
10894
|
+
`- **${oneLine(candidate.rule, WIDTH.title)}** \u2014 ${oneLine(candidate.evidence, WIDTH.text)}`,
|
|
10669
10895
|
...noteLines(candidate.memory_id, candidate.title, candidate.path)
|
|
10670
10896
|
];
|
|
10671
10897
|
if (candidate.other_id !== null) {
|
|
@@ -10707,7 +10933,7 @@ function proposeCommand() {
|
|
|
10707
10933
|
},
|
|
10708
10934
|
human: () => {
|
|
10709
10935
|
printHuman(
|
|
10710
|
-
`Proposed **${
|
|
10936
|
+
`Proposed **${oneLine(filed.action, WIDTH.title)}** on \`${oneLine(filed.target, WIDTH.path)}\`.
|
|
10711
10937
|
`
|
|
10712
10938
|
);
|
|
10713
10939
|
printHuman(c.gray(` ${filed.id}`));
|
|
@@ -11142,14 +11368,14 @@ function lifecycleCommand(action) {
|
|
|
11142
11368
|
json: { action, memory_id: entry.memory.id, target: entry.path, op_id: record.op_id },
|
|
11143
11369
|
human: () => {
|
|
11144
11370
|
printHuman(
|
|
11145
|
-
action === "retire" ? `Retired **${
|
|
11146
|
-
` : `**${
|
|
11371
|
+
action === "retire" ? `Retired **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
11372
|
+
` : `**${oneLine(entry.memory.title, WIDTH.title)}** is current again.
|
|
11147
11373
|
`
|
|
11148
11374
|
);
|
|
11149
11375
|
if (action === "retire") {
|
|
11150
11376
|
printHuman(
|
|
11151
11377
|
c.gray(
|
|
11152
|
-
"It still appears in recall \u2014 ranked last and labelled. Nothing was removed.\nTo stop reading the note entirely: `memgineering exclude " +
|
|
11378
|
+
"It still appears in recall \u2014 ranked last and labelled. Nothing was removed.\nTo stop reading the note entirely: `memgineering exclude " + oneLine(entry.path, WIDTH.path) + "`"
|
|
11153
11379
|
)
|
|
11154
11380
|
);
|
|
11155
11381
|
}
|
|
@@ -11207,8 +11433,10 @@ function ignoreCommand(mode) {
|
|
|
11207
11433
|
mode === "exclude" ? (
|
|
11208
11434
|
// The path is argv here, but argv came from a listing of the
|
|
11209
11435
|
// user's own notes — and a filename is as authored as a title.
|
|
11210
|
-
c.gray(
|
|
11211
|
-
|
|
11436
|
+
c.gray(
|
|
11437
|
+
`\`${oneLine(target, WIDTH.path)}\` was already excluded \u2014 nothing changed.`
|
|
11438
|
+
)
|
|
11439
|
+
) : c.gray(`\`${oneLine(target, WIDTH.path)}\` was not excluded \u2014 nothing changed.`)
|
|
11212
11440
|
);
|
|
11213
11441
|
return;
|
|
11214
11442
|
}
|
|
@@ -11238,13 +11466,13 @@ async function lifecycleHosted(where, action, ref, reason) {
|
|
|
11238
11466
|
json: { brain: where.name, ...result },
|
|
11239
11467
|
human: () => {
|
|
11240
11468
|
printHuman(
|
|
11241
|
-
action === "retire" ? `Retired in **${
|
|
11242
|
-
` : `Current again in **${
|
|
11469
|
+
action === "retire" ? `Retired in **${oneLine(where.name, WIDTH.title)}**.
|
|
11470
|
+
` : `Current again in **${oneLine(where.name, WIDTH.title)}**.
|
|
11243
11471
|
`
|
|
11244
11472
|
);
|
|
11245
|
-
printHuman(`\`${
|
|
11473
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
11246
11474
|
`);
|
|
11247
|
-
printHuman(c.gray(
|
|
11475
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11248
11476
|
printHuman(
|
|
11249
11477
|
c.gray(
|
|
11250
11478
|
`
|
|
@@ -11264,15 +11492,15 @@ async function ignoreHosted(where, mode, path, reason) {
|
|
|
11264
11492
|
json: { brain: where.name, ...result },
|
|
11265
11493
|
human: () => {
|
|
11266
11494
|
if (!result.changed) {
|
|
11267
|
-
printHuman(c.gray(
|
|
11495
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11268
11496
|
return;
|
|
11269
11497
|
}
|
|
11270
11498
|
printHuman(
|
|
11271
|
-
mode === "exclude" ? `No longer reading \`${
|
|
11272
|
-
` : `Reading \`${
|
|
11499
|
+
mode === "exclude" ? `No longer reading \`${oneLine(result.path, WIDTH.path)}\` in **${oneLine(where.name, WIDTH.title)}**.
|
|
11500
|
+
` : `Reading \`${oneLine(result.path, WIDTH.path)}\` again in **${oneLine(where.name, WIDTH.title)}**.
|
|
11273
11501
|
`
|
|
11274
11502
|
);
|
|
11275
|
-
printHuman(c.gray(
|
|
11503
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11276
11504
|
if (result.op_id) printHuman(c.gray(`
|
|
11277
11505
|
Undo: \`memgineering ${opposite(mode)} ${path}\``));
|
|
11278
11506
|
}
|
|
@@ -11336,10 +11564,12 @@ function evidenceCommand() {
|
|
|
11336
11564
|
});
|
|
11337
11565
|
}
|
|
11338
11566
|
function renderEvidence(report, opts) {
|
|
11339
|
-
printHuman(`## evidence: ${
|
|
11340
|
-
`);
|
|
11341
|
-
printHuman(`\`id: ${stripControl(report.id)}\` \xB7 \`${stripControl(report.path)}\`
|
|
11567
|
+
printHuman(`## evidence: ${oneLine(report.title, WIDTH.title)}
|
|
11342
11568
|
`);
|
|
11569
|
+
printHuman(
|
|
11570
|
+
`\`id: ${oneLine(report.id, WIDTH.handle)}\` \xB7 \`${oneLine(report.path, WIDTH.path)}\`
|
|
11571
|
+
`
|
|
11572
|
+
);
|
|
11343
11573
|
if (report.critical) {
|
|
11344
11574
|
printHuman(c.yellow("\u26A0 critical \u2014 this note defines how the agent behaves.") + "\n");
|
|
11345
11575
|
}
|
|
@@ -11389,11 +11619,12 @@ function renderEvidence(report, opts) {
|
|
|
11389
11619
|
]);
|
|
11390
11620
|
}
|
|
11391
11621
|
const pad = rows.reduce((w, [label]) => Math.max(w, label.length), 0) + 2;
|
|
11392
|
-
for (const [label, value] of rows)
|
|
11622
|
+
for (const [label, value] of rows)
|
|
11623
|
+
printHuman(` ${label.padEnd(pad)}${oneLine(value, WIDTH.text)}`);
|
|
11393
11624
|
if (report.standing) {
|
|
11394
11625
|
const means = STANDING_MEANS[report.standing];
|
|
11395
11626
|
printHuman(`
|
|
11396
|
-
**${
|
|
11627
|
+
**${oneLine(report.standing, WIDTH.title)}**${means ? ` \u2014 ${means}` : ""}`);
|
|
11397
11628
|
}
|
|
11398
11629
|
if (report.contradicts > 0) {
|
|
11399
11630
|
printHuman(
|
|
@@ -11414,7 +11645,7 @@ function renderEvidence(report, opts) {
|
|
|
11414
11645
|
report.last_reason.verb,
|
|
11415
11646
|
report.last_reason.ts === null ? null : report.last_reason.ts.slice(0, 10)
|
|
11416
11647
|
].filter((part) => part !== null && part !== "").join(", ");
|
|
11417
|
-
if (provenance !== "") printHuman(c.gray(` ${
|
|
11648
|
+
if (provenance !== "") printHuman(c.gray(` ${oneLine(provenance, WIDTH.text)}`));
|
|
11418
11649
|
} else if (report.changed > 0) {
|
|
11419
11650
|
printHuman(
|
|
11420
11651
|
c.gray(
|
|
@@ -11550,6 +11781,7 @@ import { Command as Command11 } from "commander";
|
|
|
11550
11781
|
// src/lib/rules.ts
|
|
11551
11782
|
init_src();
|
|
11552
11783
|
init_index_build();
|
|
11784
|
+
init_ui();
|
|
11553
11785
|
init_vault();
|
|
11554
11786
|
var RULE_LIMIT = 8;
|
|
11555
11787
|
async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date()) {
|
|
@@ -11578,11 +11810,12 @@ async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date())
|
|
|
11578
11810
|
};
|
|
11579
11811
|
}
|
|
11580
11812
|
function renderRuleLines(rules) {
|
|
11581
|
-
return rules.map(({ entry }) => `- ${entry.memory.summary ?? entry.memory.title}`);
|
|
11813
|
+
return rules.map(({ entry }) => `- ${oneLine(entry.memory.summary ?? entry.memory.title, 200)}`);
|
|
11582
11814
|
}
|
|
11583
11815
|
|
|
11584
11816
|
// src/commands/guard.ts
|
|
11585
11817
|
init_deadline();
|
|
11818
|
+
init_ui();
|
|
11586
11819
|
function guardCommand() {
|
|
11587
11820
|
return new Command11("guard").description("hook entry \u2014 show the user\u2019s rules before the first edit of a session").option("--context-dir <path>", "where the work is happening, when stdin does not say").option("--at <event>", "pre-tool-use | stop", "pre-tool-use").action(async (opts) => {
|
|
11588
11821
|
try {
|
|
@@ -11622,7 +11855,7 @@ async function runGuard(payload, contextDirFlag) {
|
|
|
11622
11855
|
return JSON.stringify({
|
|
11623
11856
|
hookSpecificOutput: {
|
|
11624
11857
|
hookEventName: "PreToolUse",
|
|
11625
|
-
additionalContext: contextFor(claimed.
|
|
11858
|
+
additionalContext: contextFor(claimed.lines, claimed.total)
|
|
11626
11859
|
}
|
|
11627
11860
|
});
|
|
11628
11861
|
}
|
|
@@ -11636,16 +11869,22 @@ async function runStopGuard(payload, contextDirFlag) {
|
|
|
11636
11869
|
return JSON.stringify({
|
|
11637
11870
|
hookSpecificOutput: {
|
|
11638
11871
|
hookEventName: "Stop",
|
|
11639
|
-
additionalContext: stopContextFor(claimed.
|
|
11872
|
+
additionalContext: stopContextFor(claimed.lines, claimed.total)
|
|
11640
11873
|
}
|
|
11641
11874
|
});
|
|
11642
11875
|
}
|
|
11643
11876
|
async function claimRules(sessionId, contextDir) {
|
|
11877
|
+
const hosted = await hostedRuleLines().catch(() => null);
|
|
11878
|
+
if (hosted !== null) {
|
|
11879
|
+
if (hosted.lines.length === 0) return null;
|
|
11880
|
+
if (!await claimSession(sessionId, hosted.brainId)) return null;
|
|
11881
|
+
return { lines: hosted.lines, total: hosted.total };
|
|
11882
|
+
}
|
|
11644
11883
|
const collected = await collectRules(void 0, contextDir).catch(() => null);
|
|
11645
11884
|
if (collected === null) return null;
|
|
11646
11885
|
const { brainRoot, rules, total } = collected;
|
|
11647
11886
|
if (rules.length === 0) return null;
|
|
11648
|
-
if (!await claimSession(sessionId)) return null;
|
|
11887
|
+
if (!await claimSession(sessionId, brainRoot)) return null;
|
|
11649
11888
|
await recordEvent(brainRoot, {
|
|
11650
11889
|
verb: "guard",
|
|
11651
11890
|
contextDir,
|
|
@@ -11653,22 +11892,20 @@ async function claimRules(sessionId, contextDir) {
|
|
|
11653
11892
|
limit: RULE_LIMIT,
|
|
11654
11893
|
returnedIds: rules.map((r) => r.entry.memory.id)
|
|
11655
11894
|
});
|
|
11656
|
-
return { rules, total };
|
|
11895
|
+
return { lines: renderRuleLines(rules), total };
|
|
11657
11896
|
}
|
|
11658
|
-
function contextFor(
|
|
11659
|
-
const
|
|
11660
|
-
|
|
11661
|
-
(${total - rules.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11897
|
+
function contextFor(lines2, total) {
|
|
11898
|
+
const more = total > lines2.length ? `
|
|
11899
|
+
(${total - lines2.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11662
11900
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11663
11901
|
|
|
11664
11902
|
${lines2.join("\n")}${more}
|
|
11665
11903
|
|
|
11666
11904
|
Follow them. If what you are about to do goes against one, say so in a sentence instead of doing it quietly.`;
|
|
11667
11905
|
}
|
|
11668
|
-
function stopContextFor(
|
|
11669
|
-
const
|
|
11670
|
-
|
|
11671
|
-
(${total - rules.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11906
|
+
function stopContextFor(lines2, total) {
|
|
11907
|
+
const more = total > lines2.length ? `
|
|
11908
|
+
(${total - lines2.length} more \u2014 \`memgineering rules\` for all of them.)` : "";
|
|
11672
11909
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11673
11910
|
|
|
11674
11911
|
${lines2.join("\n")}${more}
|
|
@@ -11699,10 +11936,10 @@ async function readPayload() {
|
|
|
11699
11936
|
}
|
|
11700
11937
|
}
|
|
11701
11938
|
var guardDir = () => join16(brandHome(), "guard");
|
|
11702
|
-
async function claimSession(sessionId) {
|
|
11939
|
+
async function claimSession(sessionId, brain) {
|
|
11703
11940
|
const marker = join16(
|
|
11704
11941
|
guardDir(),
|
|
11705
|
-
`${createHash7("sha256").update(sessionId).digest("hex").slice(0, 32)}`
|
|
11942
|
+
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
11706
11943
|
);
|
|
11707
11944
|
try {
|
|
11708
11945
|
await mkdir11(guardDir(), { recursive: true, mode: 448 });
|
|
@@ -11727,6 +11964,17 @@ async function pruneOldMarkers() {
|
|
|
11727
11964
|
} catch {
|
|
11728
11965
|
}
|
|
11729
11966
|
}
|
|
11967
|
+
var RULE_WIDTH = 200;
|
|
11968
|
+
async function hostedRuleLines() {
|
|
11969
|
+
const where = await resolveTarget({});
|
|
11970
|
+
if (where.kind !== "cloud") return null;
|
|
11971
|
+
const { rules, total } = await cloudRules(await openCloudBrain(where));
|
|
11972
|
+
return {
|
|
11973
|
+
lines: rules.map((r) => `- ${oneLine(r.summary ?? r.title, RULE_WIDTH)}`),
|
|
11974
|
+
total,
|
|
11975
|
+
brainId: where.brainId
|
|
11976
|
+
};
|
|
11977
|
+
}
|
|
11730
11978
|
|
|
11731
11979
|
// src/program.ts
|
|
11732
11980
|
init_init();
|
|
@@ -11769,7 +12017,7 @@ function logCommand() {
|
|
|
11769
12017
|
human: () => {
|
|
11770
12018
|
const diverged2 = Array.isArray(history["divergence"]) ? history["divergence"] : [];
|
|
11771
12019
|
const total = typeof history["total"] === "number" ? history["total"] : rows.length;
|
|
11772
|
-
printHuman(`## log \u2014 ${
|
|
12020
|
+
printHuman(`## log \u2014 ${oneLine(target.name, WIDTH.title)} (${total} operations)
|
|
11773
12021
|
`);
|
|
11774
12022
|
if (rows.length === 0) {
|
|
11775
12023
|
printHuman("_This brain has no recorded operations._\n");
|
|
@@ -11783,7 +12031,8 @@ function logCommand() {
|
|
|
11783
12031
|
);
|
|
11784
12032
|
}
|
|
11785
12033
|
const hint = history["hint"];
|
|
11786
|
-
if (typeof hint === "string" && hint !== "")
|
|
12034
|
+
if (typeof hint === "string" && hint !== "")
|
|
12035
|
+
printHuman(c.gray(oneLine(hint, WIDTH.text)));
|
|
11787
12036
|
}
|
|
11788
12037
|
});
|
|
11789
12038
|
return;
|
|
@@ -11905,15 +12154,19 @@ function renderOps(ops, opts) {
|
|
|
11905
12154
|
op.diverged && !op.undone ? "note has changed since" : null
|
|
11906
12155
|
].filter(Boolean);
|
|
11907
12156
|
printHuman(
|
|
11908
|
-
`- ${
|
|
12157
|
+
`- ${oneLine(op.verb, WIDTH.title)} \`${oneLine(op.target, WIDTH.path)}\`${marks.length ? c.yellow(` (${marks.join(", ")})`) : ""}`
|
|
11909
12158
|
);
|
|
11910
12159
|
printHuman(
|
|
11911
|
-
c.gray(
|
|
12160
|
+
c.gray(
|
|
12161
|
+
` ${oneLine(op.ts, WIDTH.handle)}${op.actor ? ` \xB7 ${oneLine(op.actor, WIDTH.title)}` : ""}`
|
|
12162
|
+
) + ` ${c.gray(undoable(op, opts.hosted) ? "undo:" : "op:")} ${oneLine(op.op_id, WIDTH.handle)}`
|
|
11912
12163
|
);
|
|
11913
12164
|
if (op.rationale) printHuman(c.gray(quoteBlock(op.rationale, " > ")));
|
|
11914
12165
|
if (opts.verbose) {
|
|
11915
12166
|
printHuman(
|
|
11916
|
-
c.gray(
|
|
12167
|
+
c.gray(
|
|
12168
|
+
` ${oneLine(op.hash_before ?? "\u2205", WIDTH.handle)} \u2192 ${oneLine(op.hash_after ?? "\u2205", WIDTH.handle)}`
|
|
12169
|
+
)
|
|
11917
12170
|
);
|
|
11918
12171
|
}
|
|
11919
12172
|
}
|
|
@@ -11969,7 +12222,7 @@ init_pending_login();
|
|
|
11969
12222
|
init_ui();
|
|
11970
12223
|
import { Command as Command14 } from "commander";
|
|
11971
12224
|
function logoutCommand() {
|
|
11972
|
-
return new Command14("logout").description("sign out of
|
|
12225
|
+
return new Command14("logout").description("sign out of your account \u2014 the pointer and your notes stay").option("--all", "revoke every token on the account, not just this machine\u2019s").option(
|
|
11973
12226
|
"--local",
|
|
11974
12227
|
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
11975
12228
|
).action(async (opts) => {
|
|
@@ -12052,6 +12305,11 @@ function logoutCommand() {
|
|
|
12052
12305
|
opts.all ? " Every token on the account is dead. `memgineering login` starts a new one." : " This machine\u2019s token is revoked. Other machines are unaffected."
|
|
12053
12306
|
)
|
|
12054
12307
|
);
|
|
12308
|
+
printHuman(
|
|
12309
|
+
c.gray(
|
|
12310
|
+
" Your notes are untouched: signing out takes a token, not a brain.\n This machine still remembers which brain it was reading, so `memgineering\n login` picks up where you left off. `memgineering unlink --brain` forgets it."
|
|
12311
|
+
)
|
|
12312
|
+
);
|
|
12055
12313
|
}
|
|
12056
12314
|
});
|
|
12057
12315
|
});
|
|
@@ -12135,12 +12393,12 @@ init_ui();
|
|
|
12135
12393
|
function pullCommand() {
|
|
12136
12394
|
return new Command16("pull").description("download your hosted brain to a folder on this machine").argument("[destination]", "folder to write into (created if missing)").option("--brain <name>", "which hosted brain to download").option("--dry-run", "say what would be written and write nothing").action(async (destination, opts) => {
|
|
12137
12395
|
const { brain: source, others } = await resolveSource(opts.brain);
|
|
12138
|
-
const target = resolve8(destination ?? `./${
|
|
12396
|
+
const target = resolve8(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
|
|
12139
12397
|
const first = await cloudListNotes(source, { limit: 1 });
|
|
12140
12398
|
if (first.total === 0) {
|
|
12141
12399
|
throw memgError(
|
|
12142
12400
|
"invalid_input",
|
|
12143
|
-
`\u201C${
|
|
12401
|
+
`\u201C${oneLine(source.name, WIDTH.title)}\u201D has no notes to download`,
|
|
12144
12402
|
"Nothing was written. `memgineering push` carries a brain on this disk up to the hosted one, and `remember` writes a first note into it."
|
|
12145
12403
|
);
|
|
12146
12404
|
}
|
|
@@ -12161,13 +12419,13 @@ function pullCommand() {
|
|
|
12161
12419
|
printHuman(`## pull (dry run)
|
|
12162
12420
|
`);
|
|
12163
12421
|
printHuman(
|
|
12164
|
-
`${first.total} note(s) from \u201C${
|
|
12422
|
+
`${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D into \`${target}\`
|
|
12165
12423
|
`
|
|
12166
12424
|
);
|
|
12167
12425
|
for (const note of preview.slice(0, 20)) {
|
|
12168
12426
|
printHuman(
|
|
12169
12427
|
c.gray(
|
|
12170
|
-
` ${
|
|
12428
|
+
` ${oneLine(note.path, WIDTH.path)}${note.excluded === true ? " (excluded)" : ""}`
|
|
12171
12429
|
)
|
|
12172
12430
|
);
|
|
12173
12431
|
}
|
|
@@ -12182,7 +12440,7 @@ function pullCommand() {
|
|
|
12182
12440
|
});
|
|
12183
12441
|
return;
|
|
12184
12442
|
}
|
|
12185
|
-
progress(`downloading ${first.total} note(s) from \u201C${
|
|
12443
|
+
progress(`downloading ${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D\u2026`);
|
|
12186
12444
|
const written = [];
|
|
12187
12445
|
const already = [];
|
|
12188
12446
|
const excluded = [];
|
|
@@ -12224,7 +12482,7 @@ function pullCommand() {
|
|
|
12224
12482
|
human: () => {
|
|
12225
12483
|
printHuman(
|
|
12226
12484
|
c.green(
|
|
12227
|
-
`\u2713 ${written.length} note(s) from \u201C${
|
|
12485
|
+
`\u2713 ${written.length} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D written to \`${target}\``
|
|
12228
12486
|
)
|
|
12229
12487
|
);
|
|
12230
12488
|
if (already.length > 0) {
|
|
@@ -12276,8 +12534,8 @@ async function resolveSource(named) {
|
|
|
12276
12534
|
if (!found) {
|
|
12277
12535
|
throw memgError(
|
|
12278
12536
|
"invalid_input",
|
|
12279
|
-
named ? `no hosted brain called \u201C${
|
|
12280
|
-
`Pass one of: ${brains.map((b) =>
|
|
12537
|
+
named ? `no hosted brain called \u201C${oneLine(named, WIDTH.title)}\u201D` : "more than one hosted brain \u2014 say which",
|
|
12538
|
+
`Pass one of: ${brains.map((b) => oneLine(b.name, WIDTH.title)).join(", ")} (e.g. \`memgineering pull --brain ${oneLine(brains[0]?.name ?? "", WIDTH.title)}\`)`
|
|
12281
12539
|
);
|
|
12282
12540
|
}
|
|
12283
12541
|
return {
|
|
@@ -12294,7 +12552,7 @@ async function resolveSource(named) {
|
|
|
12294
12552
|
}
|
|
12295
12553
|
function sayWhatIsElsewhere(others, into) {
|
|
12296
12554
|
if (others.length === 0) return;
|
|
12297
|
-
const names = others.map(
|
|
12555
|
+
const names = others.map((name) => oneLine(name, WIDTH.title));
|
|
12298
12556
|
printHuman(
|
|
12299
12557
|
c.yellow(
|
|
12300
12558
|
`
|
|
@@ -12392,7 +12650,7 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12392
12650
|
return;
|
|
12393
12651
|
}
|
|
12394
12652
|
const destination = await resolveDestination(opts.brain, local.root);
|
|
12395
|
-
progress(`uploading ${paths.length} note(s) to \u201C${
|
|
12653
|
+
progress(`uploading ${paths.length} note(s) to \u201C${oneLine(destination.name, WIDTH.title)}\u201D\u2026`);
|
|
12396
12654
|
const uploaded = [];
|
|
12397
12655
|
const already = [];
|
|
12398
12656
|
const refused = [];
|
|
@@ -12462,7 +12720,9 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12462
12720
|
},
|
|
12463
12721
|
human: () => {
|
|
12464
12722
|
printHuman(
|
|
12465
|
-
c.green(
|
|
12723
|
+
c.green(
|
|
12724
|
+
`\u2713 ${uploaded.length} note(s) uploaded to \u201C${oneLine(destination.name, WIDTH.title)}\u201D`
|
|
12725
|
+
)
|
|
12466
12726
|
);
|
|
12467
12727
|
if (already.length > 0) {
|
|
12468
12728
|
printHuman(c.gray(` ${already.length} were already there \u2014 left as they are`));
|
|
@@ -12480,7 +12740,9 @@ If your notes are in one of those, that rule is why \u2014 nothing here can see
|
|
|
12480
12740
|
\u26A0 ${refused.length} note(s) were NOT uploaded:`) + c.gray("\n they are untouched on this machine and can be fixed and pushed again")
|
|
12481
12741
|
);
|
|
12482
12742
|
for (const r of refused.slice(0, 10)) {
|
|
12483
|
-
printHuman(
|
|
12743
|
+
printHuman(
|
|
12744
|
+
c.gray(` ${oneLine(r.path, WIDTH.path)} (${oneLine(r.code, WIDTH.title)})`)
|
|
12745
|
+
);
|
|
12484
12746
|
}
|
|
12485
12747
|
if (refused.length > 10) {
|
|
12486
12748
|
printHuman(c.gray(` \u2026 and ${refused.length - 10} more`));
|
|
@@ -12502,14 +12764,14 @@ Nothing was deleted from \`${local.root}\` \u2014
|
|
|
12502
12764
|
function printRepoint(repoint, destination) {
|
|
12503
12765
|
if (repoint === null) {
|
|
12504
12766
|
printHuman(c.gray(`
|
|
12505
|
-
This machine still reads \u201C${
|
|
12767
|
+
This machine still reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D.`));
|
|
12506
12768
|
return;
|
|
12507
12769
|
}
|
|
12508
12770
|
if (repoint.from === null) {
|
|
12509
12771
|
printHuman(
|
|
12510
12772
|
c.gray(
|
|
12511
12773
|
`
|
|
12512
|
-
This machine now reads \u201C${
|
|
12774
|
+
This machine now reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D \u2014 it was not reading a hosted
|
|
12513
12775
|
brain before. \`memgineering recall --local\` still reads the folder.`
|
|
12514
12776
|
)
|
|
12515
12777
|
);
|
|
@@ -12518,9 +12780,9 @@ brain before. \`memgineering recall --local\` still reads the folder.`
|
|
|
12518
12780
|
printHuman(
|
|
12519
12781
|
c.yellow(
|
|
12520
12782
|
`
|
|
12521
|
-
\u26A0 THIS MACHINE NOW READS \u201C${
|
|
12783
|
+
\u26A0 THIS MACHINE NOW READS \u201C${oneLine(destination.name, WIDTH.title)}\u201D \u2014 it was reading \u201C${oneLine(repoint.from.name, WIDTH.title)}\u201D.`
|
|
12522
12784
|
) + c.gray(
|
|
12523
|
-
"\n That is a side effect of `--brain`, and it outlives this command: `recall`,\n `remember` and the memories an agent session starts with all come from the\n brain named here. Nothing in \u201C" +
|
|
12785
|
+
"\n That is a side effect of `--brain`, and it outlives this command: `recall`,\n `remember` and the memories an agent session starts with all come from the\n brain named here. Nothing in \u201C" + oneLine(repoint.from.name, WIDTH.title) + "\u201D was changed or deleted.\n To point back without uploading: `memgineering use --brain " + oneLine(repoint.from.name, 72) + "`."
|
|
12524
12786
|
)
|
|
12525
12787
|
);
|
|
12526
12788
|
}
|
|
@@ -12727,7 +12989,7 @@ function recallCommand() {
|
|
|
12727
12989
|
if (semantic.state !== "skipped" || semantic.hint === void 0) return;
|
|
12728
12990
|
printHuman(
|
|
12729
12991
|
c.yellow("\u26A0 Meaning was not searched \u2014 this answer matched words only.") + c.gray(`
|
|
12730
|
-
${
|
|
12992
|
+
${oneLine(semantic.hint, WIDTH.text)}
|
|
12731
12993
|
`)
|
|
12732
12994
|
);
|
|
12733
12995
|
};
|
|
@@ -12775,13 +13037,11 @@ function recallCommand() {
|
|
|
12775
13037
|
for (const id of conflict.between) {
|
|
12776
13038
|
const reason = reasons.get(id);
|
|
12777
13039
|
if (!reason) {
|
|
12778
|
-
printHuman(c.gray(` ${
|
|
13040
|
+
printHuman(c.gray(` ${oneLine(id, WIDTH.handle)} \u2014 no reason was recorded.`));
|
|
12779
13041
|
continue;
|
|
12780
13042
|
}
|
|
12781
13043
|
printHuman(
|
|
12782
|
-
|
|
12783
|
-
` ${id} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12784
|
-
)
|
|
13044
|
+
` ${oneLine(id, WIDTH.handle)} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12785
13045
|
);
|
|
12786
13046
|
printHuman(quoteBlock(reason.text, " > "));
|
|
12787
13047
|
}
|
|
@@ -12797,8 +13057,10 @@ function recallCommand() {
|
|
|
12797
13057
|
);
|
|
12798
13058
|
}
|
|
12799
13059
|
if (result.related.length > 0) {
|
|
12800
|
-
printHuman(
|
|
12801
|
-
`)
|
|
13060
|
+
printHuman(
|
|
13061
|
+
`_related:_ ${result.related.map((id) => oneLine(id, WIDTH.handle)).join(", ")}
|
|
13062
|
+
`
|
|
13063
|
+
);
|
|
12802
13064
|
}
|
|
12803
13065
|
if (result.truncated) {
|
|
12804
13066
|
const { withheld, estimated_tokens, budget_tokens } = result.truncated;
|
|
@@ -12852,9 +13114,8 @@ function asksInSentences(query) {
|
|
|
12852
13114
|
return query.trim().split(/\s+/).filter(Boolean).length >= 4;
|
|
12853
13115
|
}
|
|
12854
13116
|
function side(id, status, validFrom2) {
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
);
|
|
13117
|
+
const date = validFrom2 === null ? "" : ` \xB7 ${oneLine(validFrom2.slice(0, 10), WIDTH.handle)}`;
|
|
13118
|
+
return `${oneLine(id, WIDTH.handle)} (${oneLine(status, WIDTH.title)}${date})`;
|
|
12858
13119
|
}
|
|
12859
13120
|
async function conflictReasons(vaultRoot, conflicts, byId) {
|
|
12860
13121
|
const { ops } = await readOps(vaultRoot);
|
|
@@ -12873,7 +13134,7 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
12873
13134
|
const out = [];
|
|
12874
13135
|
const standing = card.freshness === "current" ? "" : ` \xB7 ${card.freshness}`;
|
|
12875
13136
|
const age = Number.isFinite(card.age_days) ? ` \xB7 ${ago2(Math.floor(card.age_days))}` : "";
|
|
12876
|
-
out.push(`### ${n}. ${
|
|
13137
|
+
out.push(`### ${n}. ${oneLine(card.title, WIDTH.title)}`);
|
|
12877
13138
|
if (scaffolding) {
|
|
12878
13139
|
out.push(c.yellow("not filled in yet") + c.gray(" \u2014 still the template `init` wrote"));
|
|
12879
13140
|
} else {
|
|
@@ -12881,14 +13142,14 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
12881
13142
|
card.binding ? c.bold("a standing decision") : null,
|
|
12882
13143
|
`${card.confidence}${standing}${age}`
|
|
12883
13144
|
].filter(Boolean);
|
|
12884
|
-
out.push(
|
|
13145
|
+
out.push(oneLine(marks.join(c.gray(" \xB7 ")), WIDTH.text));
|
|
12885
13146
|
}
|
|
12886
13147
|
if (glossHere && !scaffolding) {
|
|
12887
13148
|
out.push(c.gray("stated = these fields came from the note, not from inference"));
|
|
12888
13149
|
}
|
|
12889
13150
|
if (detail !== "title") {
|
|
12890
13151
|
if (card.summary) out.push("", quoteBlock(card.summary));
|
|
12891
|
-
out.push("", `\`open: ${
|
|
13152
|
+
out.push("", `\`open: ${oneLine(handle2, WIDTH.handle)}\``);
|
|
12892
13153
|
}
|
|
12893
13154
|
if (stage.outline && stage.outline.length > 0) {
|
|
12894
13155
|
out.push("", ...stage.outline.map((h) => `- ${h}`));
|
|
@@ -13019,10 +13280,10 @@ function rememberCommand() {
|
|
|
13019
13280
|
...kindLanded ? {} : { kind_dropped: kind }
|
|
13020
13281
|
},
|
|
13021
13282
|
human: () => {
|
|
13022
|
-
printHuman(`Remembered in ${
|
|
13283
|
+
printHuman(`Remembered in ${oneLine(target.name, WIDTH.title)}.
|
|
13023
13284
|
`);
|
|
13024
13285
|
printHuman(
|
|
13025
|
-
`note: \`${
|
|
13286
|
+
`note: \`${oneLine(written.id, WIDTH.handle)}\` \xB7 \`${oneLine(written.path, WIDTH.path)}\``
|
|
13026
13287
|
);
|
|
13027
13288
|
if (opts.rule === true) {
|
|
13028
13289
|
printHuman("rule: shown before you write a file here, once a session");
|
|
@@ -13034,7 +13295,9 @@ function rememberCommand() {
|
|
|
13034
13295
|
)
|
|
13035
13296
|
);
|
|
13036
13297
|
}
|
|
13037
|
-
printHuman(
|
|
13298
|
+
printHuman(
|
|
13299
|
+
c.gray(`undo: \`memgineering undo ${oneLine(written.op_id, WIDTH.handle)}\``)
|
|
13300
|
+
);
|
|
13038
13301
|
}
|
|
13039
13302
|
});
|
|
13040
13303
|
return;
|
|
@@ -13235,9 +13498,7 @@ function resurfaceCommand() {
|
|
|
13235
13498
|
`);
|
|
13236
13499
|
if (unreachableDefault) {
|
|
13237
13500
|
printHuman(
|
|
13238
|
-
c.yellow(
|
|
13239
|
-
`Your default brain is not on disk right now: ${unreachableDefault}`
|
|
13240
|
-
)
|
|
13501
|
+
c.yellow(`Your default brain is not on disk right now: ${unreachableDefault}`)
|
|
13241
13502
|
);
|
|
13242
13503
|
printHuman(
|
|
13243
13504
|
c.gray(
|
|
@@ -13251,7 +13512,7 @@ function resurfaceCommand() {
|
|
|
13251
13512
|
);
|
|
13252
13513
|
for (const [i, { entry, why }] of rankedShown.entries()) {
|
|
13253
13514
|
const blank = untouched.has(entry.path);
|
|
13254
|
-
printHuman(`### ${i + 1}. ${
|
|
13515
|
+
printHuman(`### ${i + 1}. ${oneLine(entry.memory.title, WIDTH.title)}`);
|
|
13255
13516
|
printHuman(blank ? c.yellow("not filled in yet") : why.join(" \xB7 "));
|
|
13256
13517
|
if (blank) {
|
|
13257
13518
|
const hasWritten = rankedShown.some((r) => !untouched.has(r.entry.path));
|
|
@@ -13293,31 +13554,31 @@ async function resurfaceHosted(where, limit) {
|
|
|
13293
13554
|
},
|
|
13294
13555
|
human: () => {
|
|
13295
13556
|
if (result.cards.length === 0) {
|
|
13296
|
-
printHuman(`## resurface \u2014 ${
|
|
13557
|
+
printHuman(`## resurface \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
13297
13558
|
`);
|
|
13298
13559
|
printHuman("_Nothing to bring up yet._\n");
|
|
13299
|
-
if (result.hint) printHuman(c.gray(
|
|
13560
|
+
if (result.hint) printHuman(c.gray(oneLine(result.hint, WIDTH.text)));
|
|
13300
13561
|
return;
|
|
13301
13562
|
}
|
|
13302
|
-
printHuman(
|
|
13303
|
-
|
|
13563
|
+
printHuman(
|
|
13564
|
+
`## resurface \u2014 ${oneLine(where.name, WIDTH.title)} (${result.cards.length} cards)
|
|
13565
|
+
`
|
|
13566
|
+
);
|
|
13304
13567
|
for (const [i, card] of result.cards.entries()) {
|
|
13305
|
-
printHuman(`### ${i + 1}. ${
|
|
13568
|
+
printHuman(`### ${i + 1}. ${oneLine(card.title, WIDTH.title)}`);
|
|
13306
13569
|
if (card.untouched === true) printHuman(c.yellow("not filled in yet"));
|
|
13307
|
-
else if (card.why.length > 0) printHuman(
|
|
13570
|
+
else if (card.why.length > 0) printHuman(oneLine(card.why.join(" \xB7 "), WIDTH.text));
|
|
13308
13571
|
if (card.summary) printHuman(`
|
|
13309
13572
|
${quoteBlock(card.summary)}`);
|
|
13310
13573
|
printHuman(`
|
|
13311
|
-
\`open: ${
|
|
13574
|
+
\`open: ${oneLine(card.handle, WIDTH.handle)}\`
|
|
13312
13575
|
`);
|
|
13313
13576
|
}
|
|
13314
|
-
|
|
13577
|
+
printBlock(
|
|
13315
13578
|
c.gray(
|
|
13316
|
-
|
|
13317
|
-
`Ranked by ${result.ranked_by.join(", ")}. A hosted brain does not record where a
|
|
13579
|
+
`Ranked by ${oneLine(result.ranked_by.join(", "), WIDTH.text)}. A hosted brain does not record where a
|
|
13318
13580
|
question was asked from, so "in this folder" is not one of them \u2014 that signal
|
|
13319
13581
|
exists only for a brain on this machine.`
|
|
13320
|
-
)
|
|
13321
13582
|
)
|
|
13322
13583
|
);
|
|
13323
13584
|
}
|
|
@@ -13338,7 +13599,7 @@ function rulesCommand() {
|
|
|
13338
13599
|
printDual({
|
|
13339
13600
|
json: { brain: where.name, hosted: true, rules: rules2, total: total2 },
|
|
13340
13601
|
human: () => {
|
|
13341
|
-
printHuman(`## rules \u2014 ${
|
|
13602
|
+
printHuman(`## rules \u2014 ${oneLine(where.name, WIDTH.title)} (${total2})
|
|
13342
13603
|
`);
|
|
13343
13604
|
if (rules2.length === 0) {
|
|
13344
13605
|
printHuman("_Nothing is marked as a rule in this brain._\n");
|
|
@@ -13350,8 +13611,8 @@ function rulesCommand() {
|
|
|
13350
13611
|
return;
|
|
13351
13612
|
}
|
|
13352
13613
|
for (const rule of rules2) {
|
|
13353
|
-
printHuman(`- ${
|
|
13354
|
-
printHuman(c.gray(` \`open: ${
|
|
13614
|
+
printHuman(`- ${oneLine(rule.summary ?? rule.title, WIDTH.text)}`);
|
|
13615
|
+
printHuman(c.gray(` \`open: ${oneLine(rule.handle, WIDTH.handle)}\``));
|
|
13355
13616
|
}
|
|
13356
13617
|
if (total2 > rules2.length) {
|
|
13357
13618
|
printHuman(c.gray(`
|
|
@@ -13403,10 +13664,10 @@ This answered from \`${brainRoot}\` instead, so rules you wrote in the missing o
|
|
|
13403
13664
|
printHuman(`## rules (${rules.length}${total > rules.length ? ` of ${total}` : ""})
|
|
13404
13665
|
`);
|
|
13405
13666
|
for (const { entry } of rules) {
|
|
13406
|
-
printHuman(`- ${
|
|
13667
|
+
printHuman(`- ${oneLine(entry.memory.summary ?? entry.memory.title, WIDTH.text)}`);
|
|
13407
13668
|
printHuman(
|
|
13408
13669
|
c.gray(
|
|
13409
|
-
` \`open: ${displayHandle(entry.memory.id, allIds)}\`${entry.memory.scope ? ` \xB7 ${
|
|
13670
|
+
` \`open: ${displayHandle(entry.memory.id, allIds)}\`${entry.memory.scope ? ` \xB7 ${oneLine(entry.memory.scope, WIDTH.title)}` : ""}`
|
|
13410
13671
|
)
|
|
13411
13672
|
);
|
|
13412
13673
|
}
|
|
@@ -13768,7 +14029,7 @@ async function reportPreviousAttempt() {
|
|
|
13768
14029
|
function reportNotice(text) {
|
|
13769
14030
|
if (!text) return;
|
|
13770
14031
|
printHuman(c.gray(`
|
|
13771
|
-
note from memgineering.com: "${
|
|
14032
|
+
note from memgineering.com: "${oneLine(text, WIDTH.text)}"`));
|
|
13772
14033
|
}
|
|
13773
14034
|
function reportBelowMinimum(below) {
|
|
13774
14035
|
if (!below) return;
|
|
@@ -13806,11 +14067,11 @@ function undoCommand() {
|
|
|
13806
14067
|
json: { ...undone, brain: { hosted: true, name: where.name } },
|
|
13807
14068
|
human: () => {
|
|
13808
14069
|
printHuman(
|
|
13809
|
-
`Took back \`${
|
|
14070
|
+
`Took back \`${oneLine(undone.undone, WIDTH.handle)}\` \u2014 a ${oneLine(undone.verb, WIDTH.title)}.
|
|
13810
14071
|
`
|
|
13811
14072
|
);
|
|
13812
|
-
printHuman(c.gray(` ${
|
|
13813
|
-
printHuman(c.gray(` recorded as \`${
|
|
14073
|
+
printHuman(c.gray(` ${oneLine(undone.target, WIDTH.path)}`));
|
|
14074
|
+
printHuman(c.gray(` recorded as \`${oneLine(undone.op_id, WIDTH.handle)}\``));
|
|
13814
14075
|
}
|
|
13815
14076
|
});
|
|
13816
14077
|
return;
|
|
@@ -13896,7 +14157,7 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
13896
14157
|
},
|
|
13897
14158
|
human: () => {
|
|
13898
14159
|
printHuman(
|
|
13899
|
-
`Undone: ${
|
|
14160
|
+
`Undone: ${oneLine(target.verb, WIDTH.title)} on \`${oneLine(target.target, WIDTH.path)}\`
|
|
13900
14161
|
`
|
|
13901
14162
|
);
|
|
13902
14163
|
if (opId === void 0) {
|
|
@@ -13987,13 +14248,91 @@ Read what it says now, then decide what should stay.`;
|
|
|
13987
14248
|
The text from before that change is still on this machine: ${join23(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
13988
14249
|
}
|
|
13989
14250
|
|
|
14251
|
+
// src/commands/brains.ts
|
|
14252
|
+
init_errors();
|
|
14253
|
+
init_api_client();
|
|
14254
|
+
import { Command as Command25 } from "commander";
|
|
14255
|
+
init_config();
|
|
14256
|
+
init_ui();
|
|
14257
|
+
var NAME_WIDTH = 48;
|
|
14258
|
+
function brainsCommand() {
|
|
14259
|
+
return new Command25("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
|
|
14260
|
+
"after",
|
|
14261
|
+
"\nReads only. `memgineering use --brain <name>` points this machine at one of\nthem; `memgineering push --brain <name>` is what creates a new one."
|
|
14262
|
+
).action(async () => {
|
|
14263
|
+
const base = apiUrl();
|
|
14264
|
+
const credentials = await requireCredentials(base);
|
|
14265
|
+
const brains = await listCloudBrains(credentials.token, base);
|
|
14266
|
+
const pointer = (await loadConfig()).brain.cloud;
|
|
14267
|
+
const currentId = pointer?.api_url === base ? pointer.id : null;
|
|
14268
|
+
if (brains.length === 0) {
|
|
14269
|
+
throw memgError(
|
|
14270
|
+
"invalid_input",
|
|
14271
|
+
"this account has no hosted brains",
|
|
14272
|
+
"Nothing is wrong \u2014 none has been made yet. `memgineering push --brain <name>` creates one and fills it from a folder on this machine."
|
|
14273
|
+
);
|
|
14274
|
+
}
|
|
14275
|
+
const rows = brains.map((b) => ({
|
|
14276
|
+
id: b.id,
|
|
14277
|
+
name: b.name,
|
|
14278
|
+
// Passed through rather than defaulted to 0. A server that does not send
|
|
14279
|
+
// a count and a brain that holds no notes are different facts, and the
|
|
14280
|
+
// second is alarming.
|
|
14281
|
+
note_count: b.note_count ?? null,
|
|
14282
|
+
created_at: b.created_at ?? null,
|
|
14283
|
+
current: b.id === currentId
|
|
14284
|
+
}));
|
|
14285
|
+
printDual({
|
|
14286
|
+
json: {
|
|
14287
|
+
// WHOSE ACCOUNT, on the command that lists what the account holds.
|
|
14288
|
+
//
|
|
14289
|
+
// "What is this computer connected to?" took three commands to answer:
|
|
14290
|
+
// `whoami` for the identity, this for the brains, `use` for the folder
|
|
14291
|
+
// — and `whoami`, the one named after the account, was the only one
|
|
14292
|
+
// that answered none of the rest. Two round-test agents ran all three.
|
|
14293
|
+
// The name is free here; it came back with the token.
|
|
14294
|
+
account: { id: credentials.account_id, name: credentials.display_name },
|
|
14295
|
+
server: base,
|
|
14296
|
+
brains: rows,
|
|
14297
|
+
count: rows.length,
|
|
14298
|
+
// Named at the top level too, so a caller does not have to scan the
|
|
14299
|
+
// array to answer the question it most often has.
|
|
14300
|
+
current: currentId,
|
|
14301
|
+
hint: currentId === null ? "This machine is not pointed at any of them. `memgineering use --brain <name>` picks one \u2014 it moves the pointer and uploads nothing." : "Switch with `memgineering use --brain <name>`, which moves the pointer and uploads nothing. `push --brain <name>` is what creates a new brain."
|
|
14302
|
+
},
|
|
14303
|
+
human: () => {
|
|
14304
|
+
printHuman(
|
|
14305
|
+
`## ${rows.length} brain${rows.length === 1 ? "" : "s"} on ${credentials.display_name === null ? "this account" : `${oneLine(credentials.display_name, NAME_WIDTH)}\u2019s account`}
|
|
14306
|
+
`
|
|
14307
|
+
);
|
|
14308
|
+
for (const row of rows) {
|
|
14309
|
+
const name = oneLine(row.name, NAME_WIDTH).padEnd(NAME_WIDTH);
|
|
14310
|
+
const notes = row.note_count === null ? "" : `${String(row.note_count).padStart(6)} note(s)`;
|
|
14311
|
+
const made = row.created_at === null ? "" : ` made ${row.created_at.slice(0, 10)}`;
|
|
14312
|
+
printHuman(
|
|
14313
|
+
` ${name}${c.gray(notes)}${c.gray(made)}` + (row.current ? c.yellow(" \u2190 this machine reads this one") : "")
|
|
14314
|
+
);
|
|
14315
|
+
}
|
|
14316
|
+
printHuman("");
|
|
14317
|
+
printHuman(
|
|
14318
|
+
c.gray(
|
|
14319
|
+
currentId === null ? " This machine is not pointed at any of them.\n `memgineering use --brain <name>` picks one \u2014 nothing is uploaded." : " `memgineering use --brain <name>` switches \u2014 nothing is uploaded.\n `memgineering push --brain <name>` is what creates a new one."
|
|
14320
|
+
)
|
|
14321
|
+
);
|
|
14322
|
+
}
|
|
14323
|
+
});
|
|
14324
|
+
});
|
|
14325
|
+
}
|
|
14326
|
+
|
|
13990
14327
|
// src/commands/use.ts
|
|
13991
14328
|
init_atomic();
|
|
13992
14329
|
init_brand();
|
|
13993
14330
|
init_errors();
|
|
13994
|
-
|
|
14331
|
+
init_api_client();
|
|
14332
|
+
init_credentials();
|
|
13995
14333
|
import { isAbsolute as isAbsolute5, relative as relative3, resolve as resolve11 } from "path";
|
|
13996
|
-
import { Command as
|
|
14334
|
+
import { Command as Command26 } from "commander";
|
|
14335
|
+
init_config();
|
|
13997
14336
|
init_vault();
|
|
13998
14337
|
init_ui();
|
|
13999
14338
|
function lostDefaultLines(missing) {
|
|
@@ -14012,16 +14351,24 @@ var VIA_MEANS = {
|
|
|
14012
14351
|
default: "because it is your default brain (`memgineering use --default <path>` changes it)"
|
|
14013
14352
|
};
|
|
14014
14353
|
function useCommand() {
|
|
14015
|
-
return new
|
|
14354
|
+
return new Command26("use").description(
|
|
14355
|
+
"say which brain answers here \u2014 `--brain <name>` for the whole machine, or a path to bind this folder"
|
|
14356
|
+
).argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--brain <name>", "point this machine at a hosted brain, by its name or id").option("--here <dir>", "write the pointer in this directory instead of the cwd").option(
|
|
14016
14357
|
"--default",
|
|
14017
|
-
"
|
|
14358
|
+
"for a FOLDER on this machine: make it the one used wherever no pointer applies. Not for hosted brains \u2014 `--brain <name>` is that"
|
|
14018
14359
|
).action(async (brainPath, opts) => {
|
|
14360
|
+
if (opts.brain !== void 0) {
|
|
14361
|
+
await pointAtHosted(opts.brain, opts, brainPath);
|
|
14362
|
+
return;
|
|
14363
|
+
}
|
|
14019
14364
|
const where = await canonicalize(resolve11(opts.here ?? process.cwd()));
|
|
14020
14365
|
if (!brainPath) {
|
|
14021
14366
|
const pointer = await readPointer(where);
|
|
14022
14367
|
const resolved = await resolveBrain(void 0, where).catch((err) => err);
|
|
14023
14368
|
const stuck = resolved instanceof Error;
|
|
14024
14369
|
const hosted = await hostedPointer();
|
|
14370
|
+
const stored = await readCredentials().catch(() => null);
|
|
14371
|
+
const signedIn = stored !== null && stored.api_url === apiUrl();
|
|
14025
14372
|
printDual({
|
|
14026
14373
|
json: {
|
|
14027
14374
|
// The brain that answers `recall` and takes `remember` — the same
|
|
@@ -14056,14 +14403,24 @@ function useCommand() {
|
|
|
14056
14403
|
brain: stuck ? null : resolved.brain.root,
|
|
14057
14404
|
via: stuck ? null : resolved.via,
|
|
14058
14405
|
pointer: pointer ? pointer.from : null,
|
|
14059
|
-
|
|
14406
|
+
// ONLY WHEN THE LOCAL FOLDER IS THE ANSWER. `stuck` describes the
|
|
14407
|
+
// FOLDER resolver, and on a machine reading a hosted brain that
|
|
14408
|
+
// resolver having nothing is the expected state, not a problem —
|
|
14409
|
+
// but it was reported unqualified beside `destination: "hosted"`,
|
|
14410
|
+
// so a successful switch produced `problem: "no_vault_linked"` and
|
|
14411
|
+
// `message: "no brain is linked yet"` in the same document that
|
|
14412
|
+
// named the brain it had just moved to. Two agents read `problem`
|
|
14413
|
+
// first and would have told the user the switch failed.
|
|
14414
|
+
//
|
|
14415
|
+
// `--local` is how somebody asks what the folder side would do.
|
|
14416
|
+
...stuck && hosted?.signedIn !== true ? {
|
|
14060
14417
|
problem: resolved.code ?? "unresolved",
|
|
14061
14418
|
message: resolved.message
|
|
14062
14419
|
} : {}
|
|
14063
14420
|
},
|
|
14064
14421
|
human: () => {
|
|
14065
14422
|
if (hosted?.signedIn) {
|
|
14066
|
-
printHuman(`Using the hosted brain \`${
|
|
14423
|
+
printHuman(`Using the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\`
|
|
14067
14424
|
`);
|
|
14068
14425
|
printHuman(
|
|
14069
14426
|
c.gray(
|
|
@@ -14084,7 +14441,7 @@ function useCommand() {
|
|
|
14084
14441
|
);
|
|
14085
14442
|
printHuman(
|
|
14086
14443
|
c.yellow(
|
|
14087
|
-
` A sign-in to \`${
|
|
14444
|
+
` A sign-in to \`${oneLine(hosted.name, NAME_WIDTH2)}\` is waiting to be approved.`
|
|
14088
14445
|
)
|
|
14089
14446
|
);
|
|
14090
14447
|
printHuman(
|
|
@@ -14101,7 +14458,9 @@ function useCommand() {
|
|
|
14101
14458
|
printHuman(`No brain resolves here.
|
|
14102
14459
|
`);
|
|
14103
14460
|
printHuman(
|
|
14104
|
-
c.yellow(
|
|
14461
|
+
c.yellow(
|
|
14462
|
+
` Pointed at the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\`,`
|
|
14463
|
+
) + c.gray(" with no sign-in on this machine that matches it,\n") + c.gray(" and no brain linked here either. `memgineering login` connects it.")
|
|
14105
14464
|
);
|
|
14106
14465
|
return;
|
|
14107
14466
|
}
|
|
@@ -14110,7 +14469,7 @@ function useCommand() {
|
|
|
14110
14469
|
printHuman(c.gray(` ${VIA_MEANS[resolved.via]}`));
|
|
14111
14470
|
printHuman(
|
|
14112
14471
|
c.yellow(
|
|
14113
|
-
` This machine is pointed at the hosted brain \`${
|
|
14472
|
+
` This machine is pointed at the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\` and is not signed in to it,`
|
|
14114
14473
|
) + c.gray("\n so reads and writes are landing here instead.")
|
|
14115
14474
|
);
|
|
14116
14475
|
printHuman(
|
|
@@ -14121,6 +14480,14 @@ function useCommand() {
|
|
|
14121
14480
|
if (stuck) {
|
|
14122
14481
|
printHuman(`No brain resolves here.
|
|
14123
14482
|
`);
|
|
14483
|
+
if (signedIn) {
|
|
14484
|
+
printHuman(
|
|
14485
|
+
c.yellow(" This machine is signed in, and pointed at no brain.") + c.gray(
|
|
14486
|
+
"\n Your notes may be on the account rather than missing.\n `memgineering brains` lists them; `memgineering use --brain <name>` points here.\n `init` and `link` make a NEW empty brain on this machine \u2014 not what you want\n if the notes are already on the account."
|
|
14487
|
+
)
|
|
14488
|
+
);
|
|
14489
|
+
return;
|
|
14490
|
+
}
|
|
14124
14491
|
printHuman(c.gray(` ${resolved.message}`));
|
|
14125
14492
|
const hint = resolved.hint;
|
|
14126
14493
|
if (hint) printHuman(c.gray(`
|
|
@@ -14187,13 +14554,114 @@ A pointer to an unlinked folder would fail on every command instead of at this o
|
|
|
14187
14554
|
});
|
|
14188
14555
|
});
|
|
14189
14556
|
}
|
|
14557
|
+
var NAME_WIDTH2 = 72;
|
|
14558
|
+
function describeSize(count) {
|
|
14559
|
+
if (count === void 0) return "";
|
|
14560
|
+
if (count === 0)
|
|
14561
|
+
return " \u2014 which is EMPTY. Nothing here has been lost; that brain has no notes yet";
|
|
14562
|
+
return ` \u2014 ${count} note(s)`;
|
|
14563
|
+
}
|
|
14564
|
+
async function pointAtHosted(named, opts, path) {
|
|
14565
|
+
if (named.startsWith("--")) {
|
|
14566
|
+
throw memgError(
|
|
14567
|
+
"invalid_input",
|
|
14568
|
+
`\`--brain\` was given the flag \`${oneLine(named, 32)}\` as its name`,
|
|
14569
|
+
"Nothing was changed. `--brain` takes the name or id of a hosted brain, and the\nflag written after it was read as that name. Put the name first \u2014 `memgineering\nuse --brain <name>` \u2014 and run the other flag as its own command."
|
|
14570
|
+
);
|
|
14571
|
+
}
|
|
14572
|
+
const clash = path !== void 0 ? "a folder path" : opts.default === true ? "`--default`" : opts.here !== void 0 ? "`--here`" : null;
|
|
14573
|
+
if (clash !== null) {
|
|
14574
|
+
throw memgError(
|
|
14575
|
+
"invalid_input",
|
|
14576
|
+
`\`--brain\` cannot be combined with ${clash}`,
|
|
14577
|
+
"They point different things. `--brain <name>` moves the one hosted brain this machine reads, which is a single setting for the whole machine; a path, `--default` and `--here` all bind a FOLDER on this disk. Run them separately."
|
|
14578
|
+
);
|
|
14579
|
+
}
|
|
14580
|
+
const base = apiUrl();
|
|
14581
|
+
const credentials = await requireCredentials(base).catch((err) => {
|
|
14582
|
+
if (err instanceof MemgError && err.code === "not_signed_in") {
|
|
14583
|
+
throw memgError(
|
|
14584
|
+
"not_signed_in",
|
|
14585
|
+
"this machine is not signed in to any account",
|
|
14586
|
+
"Nothing was changed. `--brain` names a brain on an ACCOUNT, so there has to be one:\n\n `memgineering login --emit-only` then show the user the code, and re-run this\n\nA brain in a folder on this machine is a different thing and `use <path>` binds one."
|
|
14587
|
+
);
|
|
14588
|
+
}
|
|
14589
|
+
throw err;
|
|
14590
|
+
});
|
|
14591
|
+
const brains = await listCloudBrains(credentials.token, base);
|
|
14592
|
+
if (brains.length === 0) {
|
|
14593
|
+
throw memgError(
|
|
14594
|
+
"invalid_input",
|
|
14595
|
+
"this account has no hosted brains",
|
|
14596
|
+
"Nothing was changed. `memgineering push --brain <name>` creates one and fills it from a folder on this machine."
|
|
14597
|
+
);
|
|
14598
|
+
}
|
|
14599
|
+
const found = brains.find((b) => b.id === named) ?? brains.find((b) => b.name === named);
|
|
14600
|
+
if (!found) {
|
|
14601
|
+
throw memgError(
|
|
14602
|
+
"invalid_input",
|
|
14603
|
+
`no hosted brain called "${oneLine(named, NAME_WIDTH2)}"`,
|
|
14604
|
+
"Nothing was changed. This account has:\n" + brains.map((b) => ` ${oneLine(b.name, NAME_WIDTH2)} (${oneLine(b.id, NAME_WIDTH2)})`).join("\n") + "\nIf one of those is the one they meant, use it. If they genuinely want a NEW\nnotebook, ASK THEM FIRST and say what it costs: `memgineering push --brain\n<name>` creates one by UPLOADING a folder into it, so a new brain arrives\nholding whatever that folder held \u2014 which on a fresh one is five empty\ntemplate files, not their notes. Creating one is not a way to find\nnotes that already exist."
|
|
14605
|
+
);
|
|
14606
|
+
}
|
|
14607
|
+
const config = await loadConfig();
|
|
14608
|
+
const previous = config.brain.cloud ?? null;
|
|
14609
|
+
const unchanged = previous?.id === found.id && previous.api_url === base && previous.name === found.name;
|
|
14610
|
+
if (!unchanged) {
|
|
14611
|
+
await saveConfig({
|
|
14612
|
+
...config,
|
|
14613
|
+
brain: { ...config.brain, cloud: { id: found.id, name: found.name, api_url: base } }
|
|
14614
|
+
});
|
|
14615
|
+
}
|
|
14616
|
+
printDual({
|
|
14617
|
+
json: {
|
|
14618
|
+
brain: {
|
|
14619
|
+
id: found.id,
|
|
14620
|
+
name: found.name,
|
|
14621
|
+
api_url: base,
|
|
14622
|
+
// Carried because the human line says it. A caller that has to ask a
|
|
14623
|
+
// second time to learn the destination is empty is a caller that will
|
|
14624
|
+
// not ask.
|
|
14625
|
+
...found.note_count === void 0 ? {} : { note_count: found.note_count }
|
|
14626
|
+
},
|
|
14627
|
+
previous: previous ? { id: previous.id, name: previous.name } : null,
|
|
14628
|
+
// Present even when nothing moved, so a caller can tell "already there"
|
|
14629
|
+
// from "switched" without comparing two names itself.
|
|
14630
|
+
changed: !unchanged
|
|
14631
|
+
},
|
|
14632
|
+
human: () => {
|
|
14633
|
+
if (unchanged) {
|
|
14634
|
+
printHuman(`Already using \`${oneLine(found.name, NAME_WIDTH2)}\`.
|
|
14635
|
+
`);
|
|
14636
|
+
printHuman(c.gray(" Nothing changed."));
|
|
14637
|
+
return;
|
|
14638
|
+
}
|
|
14639
|
+
printHuman(
|
|
14640
|
+
`Now using the hosted brain \`${oneLine(found.name, NAME_WIDTH2)}\`${describeSize(found.note_count)}.
|
|
14641
|
+
`
|
|
14642
|
+
);
|
|
14643
|
+
if (previous) {
|
|
14644
|
+
printHuman(
|
|
14645
|
+
c.yellow(
|
|
14646
|
+
` This machine was reading \`${oneLine(previous.name, NAME_WIDTH2)}\` and now is not.`
|
|
14647
|
+
)
|
|
14648
|
+
);
|
|
14649
|
+
}
|
|
14650
|
+
printHuman(
|
|
14651
|
+
c.gray(
|
|
14652
|
+
" Reads and writes go there from now on. Nothing was uploaded and nothing\n was downloaded \u2014 `push` and `pull` are what move notes."
|
|
14653
|
+
)
|
|
14654
|
+
);
|
|
14655
|
+
}
|
|
14656
|
+
});
|
|
14657
|
+
}
|
|
14190
14658
|
|
|
14191
14659
|
// src/commands/whoami.ts
|
|
14192
14660
|
init_api_client();
|
|
14193
14661
|
init_ui();
|
|
14194
|
-
import { Command as
|
|
14662
|
+
import { Command as Command27 } from "commander";
|
|
14195
14663
|
function whoamiCommand() {
|
|
14196
|
-
return new
|
|
14664
|
+
return new Command27("whoami").description("show which account this machine is signed in as").action(async () => {
|
|
14197
14665
|
const base = apiUrl();
|
|
14198
14666
|
const credentials = await requireCredentials(base);
|
|
14199
14667
|
const res = await apiRequest({
|
|
@@ -14219,9 +14687,11 @@ function whoamiCommand() {
|
|
|
14219
14687
|
},
|
|
14220
14688
|
human: () => {
|
|
14221
14689
|
printHuman(
|
|
14222
|
-
c.bold(
|
|
14690
|
+
c.bold(
|
|
14691
|
+
oneLine(account.display_name ?? account.id ?? credentials.account_id, WIDTH.title)
|
|
14692
|
+
)
|
|
14223
14693
|
);
|
|
14224
|
-
if (account.email) printHuman(c.gray(` ${
|
|
14694
|
+
if (account.email) printHuman(c.gray(` ${oneLine(account.email, WIDTH.title)}`));
|
|
14225
14695
|
printHuman(c.gray(` on ${base}`));
|
|
14226
14696
|
if (live !== null) {
|
|
14227
14697
|
printHuman(
|
|
@@ -14236,6 +14706,8 @@ function whoamiCommand() {
|
|
|
14236
14706
|
}
|
|
14237
14707
|
|
|
14238
14708
|
// src/lib/next-step.ts
|
|
14709
|
+
init_api_client();
|
|
14710
|
+
init_credentials();
|
|
14239
14711
|
init_config();
|
|
14240
14712
|
function isSetUp(cfg) {
|
|
14241
14713
|
return Object.keys(cfg.system.installed).length > 0;
|
|
@@ -14289,7 +14761,16 @@ Then run \`memgineering onboard\`: it prints what to ask so the brain knows whos
|
|
|
14289
14761
|
it is. A brain whose \`01_BASE/\` is still template text answers almost nothing.
|
|
14290
14762
|
|
|
14291
14763
|
Read \`memgineering-setup\` for accounts and which of their tools this reaches.`;
|
|
14292
|
-
|
|
14764
|
+
var POINT_AT_A_BRAIN = `## Signed in, and this machine reads no brain yet.
|
|
14765
|
+
|
|
14766
|
+
\`memgineering brains\` lists what is on the account.
|
|
14767
|
+
\`memgineering use --brain <name>\` points this machine at one.
|
|
14768
|
+
`;
|
|
14769
|
+
function nextStepFor(cfg, signedIn = false) {
|
|
14770
|
+
if (signedIn && cfg.brain.cloud === void 0) {
|
|
14771
|
+
return { id: "point_at_a_brain", markdown: POINT_AT_A_BRAIN };
|
|
14772
|
+
}
|
|
14773
|
+
if (cfg.brain.cloud !== void 0 && cfg.brain.cloud.api_url === apiUrl()) return null;
|
|
14293
14774
|
if (!isSetUp(cfg)) return { id: "finish_setup", markdown: FINISH_SETUP };
|
|
14294
14775
|
if (cfg.brain.brains.length === 0 && cfg.brain.cloud === void 0) {
|
|
14295
14776
|
return { id: "connect_a_brain", markdown: CONNECT_A_BRAIN };
|
|
@@ -14298,7 +14779,8 @@ function nextStepFor(cfg) {
|
|
|
14298
14779
|
}
|
|
14299
14780
|
async function currentNextStep() {
|
|
14300
14781
|
try {
|
|
14301
|
-
|
|
14782
|
+
const stored = await readCredentials().catch(() => null);
|
|
14783
|
+
return nextStepFor(await loadConfig(), stored !== null && stored.api_url === apiUrl());
|
|
14302
14784
|
} catch {
|
|
14303
14785
|
return null;
|
|
14304
14786
|
}
|
|
@@ -14308,7 +14790,7 @@ async function currentNextStep() {
|
|
|
14308
14790
|
init_ui();
|
|
14309
14791
|
init_brand();
|
|
14310
14792
|
function buildProgram() {
|
|
14311
|
-
const program = new
|
|
14793
|
+
const program = new Command28("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", async (thisCommand, actionCommand) => {
|
|
14312
14794
|
const opts = thisCommand.optsWithGlobals();
|
|
14313
14795
|
if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
|
|
14314
14796
|
await announceNextStep(actionCommand.name());
|
|
@@ -14329,6 +14811,7 @@ function buildProgram() {
|
|
|
14329
14811
|
program.addCommand(initCommand());
|
|
14330
14812
|
program.addCommand(linkCommand());
|
|
14331
14813
|
program.addCommand(useCommand());
|
|
14814
|
+
program.addCommand(brainsCommand());
|
|
14332
14815
|
program.addCommand(unlinkCommand());
|
|
14333
14816
|
program.addCommand(reindexCommand());
|
|
14334
14817
|
program.addCommand(syncRulesCommand());
|
|
@@ -14368,8 +14851,15 @@ function makeParseErrorsActionable(program) {
|
|
|
14368
14851
|
outputError: (str, write) => {
|
|
14369
14852
|
write(str);
|
|
14370
14853
|
if (!/unknown option|unknown command|too many arguments/i.test(str)) return;
|
|
14371
|
-
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
14372
14854
|
const path = cmd.parent === null ? cmd.name() : `${cmd.parent.name()} ${cmd.name()}`;
|
|
14855
|
+
if (/unknown command/i.test(str) && cmd.commands.length > 0) {
|
|
14856
|
+
write(
|
|
14857
|
+
`hint: ${path} has ${cmd.commands.map((c2) => c2.name()).sort().join(" ")}
|
|
14858
|
+
`
|
|
14859
|
+
);
|
|
14860
|
+
return;
|
|
14861
|
+
}
|
|
14862
|
+
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
14373
14863
|
write(
|
|
14374
14864
|
flags.length > 0 ? `hint: ${path} takes ${flags.join(" ")}
|
|
14375
14865
|
` : `hint: \`${path} --help\` lists what this takes.
|
|
@@ -14397,7 +14887,7 @@ async function handleError(err) {
|
|
|
14397
14887
|
if (isJsonMode()) {
|
|
14398
14888
|
printJson({ error: { code: err.code, message: err.message, hint: err.hint } });
|
|
14399
14889
|
} else {
|
|
14400
|
-
printHuman(c.red(`\u2717 ${
|
|
14890
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14401
14891
|
for (const line of stripControl(err.hint).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14402
14892
|
}
|
|
14403
14893
|
process.exit(1);
|
|
@@ -14414,7 +14904,7 @@ async function handleError(err) {
|
|
|
14414
14904
|
}
|
|
14415
14905
|
});
|
|
14416
14906
|
} else {
|
|
14417
|
-
printHuman(c.red(`\u2717 ${
|
|
14907
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14418
14908
|
if (hintStr) {
|
|
14419
14909
|
for (const line of stripControl(hintStr).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14420
14910
|
}
|
|
@@ -14422,7 +14912,7 @@ async function handleError(err) {
|
|
|
14422
14912
|
process.exit(1);
|
|
14423
14913
|
}
|
|
14424
14914
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|
|
14425
|
-
else printHuman(c.red(`\u2717 ${
|
|
14915
|
+
else printHuman(c.red(`\u2717 ${oneLine(String(err), WIDTH.text)}`));
|
|
14426
14916
|
process.exit(1);
|
|
14427
14917
|
}
|
|
14428
14918
|
|