memgineering 0.7.4 → 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 +226 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-setup/SKILL.md +38 -1
- package/assets/memgineering-writing/SKILL.md +13 -2
- package/dist/index.js +934 -318
- package/package.json +2 -2
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
|
/**
|
|
@@ -120,32 +133,46 @@ function stripControl(input) {
|
|
|
120
133
|
let out = "";
|
|
121
134
|
for (let i = 0; i < input.length; i++) {
|
|
122
135
|
const code = input.charCodeAt(i);
|
|
123
|
-
if (code === 27) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else if (input.charCodeAt(j) === 27) j += 2;
|
|
132
|
-
i = j - 1;
|
|
136
|
+
if (code === 27 && input[i + 1] === "]" || code === 157) {
|
|
137
|
+
const from = code === 157 ? i + 1 : i + 2;
|
|
138
|
+
let j = from;
|
|
139
|
+
while (j < input.length && input.charCodeAt(j) !== 7 && input.charCodeAt(j) !== 156 && !(input.charCodeAt(j) === 27 && input[j + 1] === "\\")) {
|
|
140
|
+
j++;
|
|
141
|
+
}
|
|
142
|
+
if (j >= input.length) {
|
|
143
|
+
i = from - 1;
|
|
133
144
|
continue;
|
|
134
145
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
j += input.charCodeAt(j) === 27 ? 2 : 1;
|
|
147
|
+
i = j - 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (code === 27 && input[i + 1] === "[" || code === 155) {
|
|
151
|
+
const from = code === 155 ? i + 1 : i + 2;
|
|
152
|
+
let j = from;
|
|
153
|
+
while (j < input.length && input.charCodeAt(j) >= 48 && input.charCodeAt(j) <= 63) j++;
|
|
154
|
+
while (j < input.length && input.charCodeAt(j) >= 32 && input.charCodeAt(j) <= 47) j++;
|
|
155
|
+
const final = j < input.length ? input.charCodeAt(j) : -1;
|
|
156
|
+
if (final >= 64 && final <= 126) {
|
|
140
157
|
i = j;
|
|
141
158
|
continue;
|
|
142
159
|
}
|
|
160
|
+
i = from - 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (code === 27) {
|
|
143
164
|
i += 1;
|
|
144
165
|
continue;
|
|
145
166
|
}
|
|
146
|
-
if (code
|
|
167
|
+
if (code === 133 || code === 8232 || code === 8233) {
|
|
168
|
+
out += "\n";
|
|
147
169
|
continue;
|
|
148
170
|
}
|
|
171
|
+
if (code <= 31 && code !== 10 && code !== 9 || code === 127) continue;
|
|
172
|
+
if (code >= 128 && code <= 159) continue;
|
|
173
|
+
if (code === 1564 || code === 8206 || code === 8207) continue;
|
|
174
|
+
if (code >= 8234 && code <= 8238) continue;
|
|
175
|
+
if (code >= 8294 && code <= 8297) continue;
|
|
149
176
|
out += input[i];
|
|
150
177
|
}
|
|
151
178
|
return out;
|
|
@@ -153,6 +180,15 @@ function stripControl(input) {
|
|
|
153
180
|
function quoteBlock(text, prefix = "> ") {
|
|
154
181
|
return stripControl(text).split("\n").map((line) => prefix + line).join("\n");
|
|
155
182
|
}
|
|
183
|
+
function oneLine(text, max) {
|
|
184
|
+
const flat = stripControl(text).replace(/\s+/g, " ").trim();
|
|
185
|
+
const chars = [...flat];
|
|
186
|
+
if (chars.length <= max) return flat;
|
|
187
|
+
return `${chars.slice(0, Math.max(1, max - 1)).join("").trimEnd()}\u2026`;
|
|
188
|
+
}
|
|
189
|
+
function printBlock(text) {
|
|
190
|
+
printHuman(stripControl(text));
|
|
191
|
+
}
|
|
156
192
|
function progress(text) {
|
|
157
193
|
if (isInteractive()) process.stderr.write(kleur.gray(` ${text}
|
|
158
194
|
`));
|
|
@@ -167,12 +203,23 @@ function undoHint(opId, vault) {
|
|
|
167
203
|
function quoteArg(value) {
|
|
168
204
|
return /^[A-Za-z0-9._\/-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
169
205
|
}
|
|
170
|
-
var jsonMode, c;
|
|
206
|
+
var jsonMode, CONTROL_INTRODUCER, WIDTH, c;
|
|
171
207
|
var init_ui = __esm({
|
|
172
208
|
"src/ui.ts"() {
|
|
173
209
|
"use strict";
|
|
174
210
|
init_brand();
|
|
175
211
|
jsonMode = false;
|
|
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
|
+
};
|
|
176
223
|
c = kleur;
|
|
177
224
|
}
|
|
178
225
|
});
|
|
@@ -969,10 +1016,12 @@ function secretContentHits(text) {
|
|
|
969
1016
|
if (pattern.test(text)) hits.push({ code });
|
|
970
1017
|
}
|
|
971
1018
|
const plain = stripMarkdownEmphasis(text);
|
|
972
|
-
|
|
1019
|
+
const bare = withoutIntroducers(text);
|
|
1020
|
+
const bothWays = (check) => check(text) || check(plain) || bare !== text && (check(bare) || check(stripMarkdownEmphasis(bare)));
|
|
1021
|
+
if (bothWays(hasLabelledCredential)) {
|
|
973
1022
|
hits.push({ code: "labelled_credential" });
|
|
974
1023
|
}
|
|
975
|
-
if (
|
|
1024
|
+
if (bothWays(hasSpacedCredential)) {
|
|
976
1025
|
hits.push({ code: "spaced_credential" });
|
|
977
1026
|
}
|
|
978
1027
|
if (hasCredentialNearPrivateIp(text)) hits.push({ code: "credential_near_private_ip" });
|
|
@@ -1003,7 +1052,7 @@ function isExplicitlyDenied(relPath, deny = EMPTY_DENY_LIST) {
|
|
|
1003
1052
|
function isNameAllowlisted(relPath, allow = EMPTY_DENY_LIST) {
|
|
1004
1053
|
return isExplicitlyDenied(relPath, allow);
|
|
1005
1054
|
}
|
|
1006
|
-
var SENSITIVE_NAME_TERMS, foldForMatch, FOLDED_TERMS, SECRET_CONTENT_PATTERNS, CREDENTIAL_LABEL, LABEL_TAIL, LABEL_QUALIFIER, SEPARATOR, HEADER_CELL_IS_LABEL, stripMarkdownEmphasis, LABELLED_CREDENTIAL_RE, SPACED_CREDENTIAL_RE, LOOKS_LIKE_A_CREDENTIAL, FINANCIAL_NUMBER_RE, SHORT_SECRET_RE, PRIVATE_IP_RE, ACCESS_WORD_RE, VALUE_IS_NOT_A_SECRET, DeniedReadError, WEAK_CODES, isWeakSecretCode, EMPTY_DENY_LIST;
|
|
1055
|
+
var SENSITIVE_NAME_TERMS, foldForMatch, FOLDED_TERMS, SECRET_CONTENT_PATTERNS, CREDENTIAL_LABEL, LABEL_TAIL, LABEL_QUALIFIER, SEPARATOR, HEADER_CELL_IS_LABEL, stripMarkdownEmphasis, LABELLED_CREDENTIAL_RE, SPACED_CREDENTIAL_RE, LOOKS_LIKE_A_CREDENTIAL, FINANCIAL_NUMBER_RE, SHORT_SECRET_RE, PRIVATE_IP_RE, ACCESS_WORD_RE, VALUE_IS_NOT_A_SECRET, DeniedReadError, WEAK_CODES, isWeakSecretCode, withoutIntroducers, EMPTY_DENY_LIST;
|
|
1007
1056
|
var init_deny = __esm({
|
|
1008
1057
|
"../../packages/storage-adapter/src/local-file/deny.ts"() {
|
|
1009
1058
|
"use strict";
|
|
@@ -1123,6 +1172,12 @@ var init_deny = __esm({
|
|
|
1123
1172
|
"credential_table"
|
|
1124
1173
|
]);
|
|
1125
1174
|
isWeakSecretCode = (code) => WEAK_CODES.has(code);
|
|
1175
|
+
withoutIntroducers = (text) => (
|
|
1176
|
+
// `ESC [` and `ESC ]` are two characters, so removing the ESC alone would
|
|
1177
|
+
// leave a bracket where the hidden byte was and the label would still not
|
|
1178
|
+
// read as itself.
|
|
1179
|
+
text.replace(/\u001b[[\]]?|[\u009b\u009d]/g, "")
|
|
1180
|
+
);
|
|
1126
1181
|
EMPTY_DENY_LIST = { entries: [] };
|
|
1127
1182
|
}
|
|
1128
1183
|
});
|
|
@@ -3129,6 +3184,7 @@ async function walkVault(root, opts = {}) {
|
|
|
3129
3184
|
const files2 = [];
|
|
3130
3185
|
const skippedSymlinks = [];
|
|
3131
3186
|
const excluded = [];
|
|
3187
|
+
const excludedDirs = [];
|
|
3132
3188
|
const visit = async (dirAbs, relDir, depth) => {
|
|
3133
3189
|
if (depth > maxDepth) return;
|
|
3134
3190
|
let entries;
|
|
@@ -3146,6 +3202,7 @@ async function walkVault(root, opts = {}) {
|
|
|
3146
3202
|
const isDir = entry.isDirectory();
|
|
3147
3203
|
if (isExcluded(rel, isDir, opts.ignore)) {
|
|
3148
3204
|
excluded.push(rel);
|
|
3205
|
+
if (isDir) excludedDirs.push(rel);
|
|
3149
3206
|
continue;
|
|
3150
3207
|
}
|
|
3151
3208
|
if (isDir) {
|
|
@@ -3157,7 +3214,7 @@ async function walkVault(root, opts = {}) {
|
|
|
3157
3214
|
};
|
|
3158
3215
|
await visit(rootAbs, "", 0);
|
|
3159
3216
|
files2.sort();
|
|
3160
|
-
return { files: files2, skippedSymlinks, excluded };
|
|
3217
|
+
return { files: files2, skippedSymlinks, excluded, excludedDirs };
|
|
3161
3218
|
}
|
|
3162
3219
|
function resolveInside(root, relPath) {
|
|
3163
3220
|
if (relPath === "" || relPath.startsWith("/") || relPath.includes("\0")) {
|
|
@@ -4399,9 +4456,11 @@ function linkCommand() {
|
|
|
4399
4456
|
Nothing was indexed. This is what WOULD be stored:
|
|
4400
4457
|
`));
|
|
4401
4458
|
for (const r of rows) {
|
|
4402
|
-
printHuman(` ${c.bold(
|
|
4403
|
-
printHuman(
|
|
4404
|
-
|
|
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)}`));
|
|
4405
4464
|
}
|
|
4406
4465
|
printHuman(
|
|
4407
4466
|
c.gray(`
|
|
@@ -4674,8 +4733,8 @@ Brain: ${root}`));
|
|
|
4674
4733
|
if (samples.length > 0) {
|
|
4675
4734
|
printHuman(c.gray("\n For example, these exact lines would be stored:"));
|
|
4676
4735
|
for (const r of samples) {
|
|
4677
|
-
printHuman(c.gray(` ${
|
|
4678
|
-
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)}`));
|
|
4679
4738
|
}
|
|
4680
4739
|
if (notes.length > samples.length) {
|
|
4681
4740
|
printHuman(
|
|
@@ -4795,7 +4854,7 @@ function loginCommand() {
|
|
|
4795
4854
|
},
|
|
4796
4855
|
human: () => {
|
|
4797
4856
|
printHuman(
|
|
4798
|
-
`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))}` : ""}.`
|
|
4799
4858
|
);
|
|
4800
4859
|
printHuman(c.gray(" `memgineering logout` first if you want a different account."));
|
|
4801
4860
|
}
|
|
@@ -4868,13 +4927,13 @@ function emit(pending, ctx) {
|
|
|
4868
4927
|
},
|
|
4869
4928
|
human: () => {
|
|
4870
4929
|
printHuman("Show this code to whoever is signing in:\n");
|
|
4871
|
-
printHuman(` ${c.bold().yellow(
|
|
4930
|
+
printHuman(` ${c.bold().yellow(oneLine(pending.user_code, WIDTH.handle))}
|
|
4872
4931
|
`);
|
|
4873
4932
|
if (ctx.opened) {
|
|
4874
4933
|
printHuman("Opened the approval page in your browser.");
|
|
4875
4934
|
} else {
|
|
4876
4935
|
printHuman("Open this to approve:\n");
|
|
4877
|
-
printHuman(` ${c.cyan(
|
|
4936
|
+
printHuman(` ${c.cyan(oneLine(pending.verification_url, WIDTH.path))}`);
|
|
4878
4937
|
}
|
|
4879
4938
|
printHuman(
|
|
4880
4939
|
c.gray(
|
|
@@ -4882,7 +4941,7 @@ function emit(pending, ctx) {
|
|
|
4882
4941
|
)
|
|
4883
4942
|
);
|
|
4884
4943
|
if (ctx.opened && ctx.showUrl) {
|
|
4885
|
-
printHuman(c.gray(` ${
|
|
4944
|
+
printHuman(c.gray(` ${oneLine(pending.verification_url, WIDTH.path)}`));
|
|
4886
4945
|
}
|
|
4887
4946
|
printHuman(
|
|
4888
4947
|
c.gray(
|
|
@@ -4924,7 +4983,7 @@ async function waitForApproval(pending, base) {
|
|
|
4924
4983
|
human: () => {
|
|
4925
4984
|
printHuman(
|
|
4926
4985
|
`
|
|
4927
|
-
Signed in${claimed.display_name ? ` as ${c.bold(
|
|
4986
|
+
Signed in${claimed.display_name ? ` as ${c.bold(oneLine(claimed.display_name, WIDTH.title))}` : ""}.`
|
|
4928
4987
|
);
|
|
4929
4988
|
printHuman(c.gray(" The token is stored on this machine only, readable by you alone."));
|
|
4930
4989
|
}
|
|
@@ -5025,17 +5084,19 @@ function printSignInStep(step) {
|
|
|
5025
5084
|
switch (step.status) {
|
|
5026
5085
|
case "signed-in":
|
|
5027
5086
|
printHuman(
|
|
5028
|
-
c.gray(
|
|
5029
|
-
`)
|
|
5087
|
+
c.gray(
|
|
5088
|
+
` signed in${step.displayName ? ` as ${oneLine(step.displayName, WIDTH.title)}` : ""}
|
|
5089
|
+
`
|
|
5090
|
+
)
|
|
5030
5091
|
);
|
|
5031
5092
|
return;
|
|
5032
5093
|
case "started":
|
|
5033
5094
|
printHuman("Show this code to whoever is signing in:\n");
|
|
5034
|
-
printHuman(` ${c.bold().yellow(
|
|
5095
|
+
printHuman(` ${c.bold().yellow(oneLine(step.userCode, WIDTH.handle))}
|
|
5035
5096
|
`);
|
|
5036
5097
|
printHuman(
|
|
5037
5098
|
step.opened ? c.gray(" The approval page is open in the browser.") : `Open this to approve:
|
|
5038
|
-
${c.cyan(
|
|
5099
|
+
${c.cyan(oneLine(step.verificationUrl, WIDTH.path))}`
|
|
5039
5100
|
);
|
|
5040
5101
|
printHuman(
|
|
5041
5102
|
c.gray(" Not waiting \u2014 setup carries on, and the next command collects the token.\n")
|
|
@@ -5043,12 +5104,14 @@ function printSignInStep(step) {
|
|
|
5043
5104
|
return;
|
|
5044
5105
|
case "pending":
|
|
5045
5106
|
printHuman(
|
|
5046
|
-
c.gray(
|
|
5047
|
-
`)
|
|
5107
|
+
c.gray(
|
|
5108
|
+
` a sign-in is waiting for approval \u2014 code ${oneLine(step.userCode, WIDTH.handle)}
|
|
5109
|
+
`
|
|
5110
|
+
)
|
|
5048
5111
|
);
|
|
5049
5112
|
return;
|
|
5050
5113
|
case "unavailable":
|
|
5051
|
-
printHuman(c.yellow(` Could not start a sign-in: ${
|
|
5114
|
+
printHuman(c.yellow(` Could not start a sign-in: ${oneLine(step.because, WIDTH.text)}`));
|
|
5052
5115
|
printHuman(c.gray(" Setup continues \u2014 an account is not needed for any of it.\n"));
|
|
5053
5116
|
return;
|
|
5054
5117
|
case "would-start":
|
|
@@ -7356,11 +7419,11 @@ var init_setup_web = __esm({
|
|
|
7356
7419
|
// src/commands/setup.ts
|
|
7357
7420
|
import { createHash as createHash8 } from "crypto";
|
|
7358
7421
|
import { access, mkdir as mkdir13, readFile as readFile19 } from "fs/promises";
|
|
7359
|
-
import { homedir as homedir4 } from "os";
|
|
7360
7422
|
import { dirname as dirname5, join as join19 } from "path";
|
|
7361
7423
|
import { fileURLToPath } from "url";
|
|
7362
7424
|
import { Command as Command22 } from "commander";
|
|
7363
7425
|
import prompts3 from "prompts";
|
|
7426
|
+
import { homedir as homedir4 } from "os";
|
|
7364
7427
|
function setupCommand() {
|
|
7365
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(
|
|
7366
7429
|
"--tools <list>",
|
|
@@ -7432,6 +7495,18 @@ function setupCommand() {
|
|
|
7432
7495
|
...signInStepJson(signIn),
|
|
7433
7496
|
defaults_used: defaultsTaken,
|
|
7434
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() } : {},
|
|
7435
7510
|
tools: choice.tools.map((t) => t.id),
|
|
7436
7511
|
auto_update: choice.autoUpdate,
|
|
7437
7512
|
hook: choice.hook,
|
|
@@ -7455,9 +7530,27 @@ function setupCommand() {
|
|
|
7455
7530
|
if (opts.silent && changed.length === 0) return;
|
|
7456
7531
|
if (opts.dryRun) {
|
|
7457
7532
|
printSignInStep(signIn);
|
|
7458
|
-
|
|
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):
|
|
7459
7546
|
`);
|
|
7460
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
|
+
);
|
|
7461
7554
|
return;
|
|
7462
7555
|
}
|
|
7463
7556
|
printSignInStep(signIn);
|
|
@@ -7558,7 +7651,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7558
7651
|
const hub = await loadAsset(BRAND.hubFileName);
|
|
7559
7652
|
const installed = { ...(await loadConfig()).system.installed };
|
|
7560
7653
|
const mark = (path, hash) => {
|
|
7561
|
-
const key2 = path.startsWith(
|
|
7654
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7562
7655
|
installed[key2] = hash;
|
|
7563
7656
|
};
|
|
7564
7657
|
const persist = async () => {
|
|
@@ -7570,11 +7663,11 @@ async function applySetup(choice, dryRun) {
|
|
|
7570
7663
|
});
|
|
7571
7664
|
};
|
|
7572
7665
|
const wasEdited = (path, onDisk) => {
|
|
7573
|
-
const key2 = path.startsWith(
|
|
7666
|
+
const key2 = path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7574
7667
|
const known = installed[key2];
|
|
7575
7668
|
return known !== void 0 && onDisk !== null && fingerprint(onDisk) !== known;
|
|
7576
7669
|
};
|
|
7577
|
-
const keyOf = (path) => path.startsWith(
|
|
7670
|
+
const keyOf = (path) => path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7578
7671
|
try {
|
|
7579
7672
|
return await write();
|
|
7580
7673
|
} finally {
|
|
@@ -7582,7 +7675,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7582
7675
|
}
|
|
7583
7676
|
async function write() {
|
|
7584
7677
|
for (const tool of choice.tools) {
|
|
7585
|
-
const hubPath = join19(
|
|
7678
|
+
const hubPath = join19(agentHome(), tool.hubFile);
|
|
7586
7679
|
const written = await injectBlock(
|
|
7587
7680
|
hubPath,
|
|
7588
7681
|
hub + tailFor(tool),
|
|
@@ -7598,7 +7691,7 @@ async function applySetup(choice, dryRun) {
|
|
|
7598
7691
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
7599
7692
|
for (const skill of BRAND.subSkills) {
|
|
7600
7693
|
const content = await loadAsset(join19(skill, "SKILL.md"));
|
|
7601
|
-
const path = join19(
|
|
7694
|
+
const path = join19(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
7602
7695
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
7603
7696
|
results.push({
|
|
7604
7697
|
tool: tool.label,
|
|
@@ -7668,7 +7761,7 @@ async function resolveMode(opts) {
|
|
|
7668
7761
|
return { kind: mode ?? "agent", chosen: mode !== void 0 };
|
|
7669
7762
|
}
|
|
7670
7763
|
async function detectTools() {
|
|
7671
|
-
const home =
|
|
7764
|
+
const home = agentHome();
|
|
7672
7765
|
const found = [];
|
|
7673
7766
|
for (const tool of TOOLS) {
|
|
7674
7767
|
if (await exists2(join19(home, tool.dir))) found.push(tool);
|
|
@@ -7801,7 +7894,7 @@ async function writeIfDifferent(filePath, content, dryRun) {
|
|
|
7801
7894
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
7802
7895
|
}
|
|
7803
7896
|
async function installHook(spec, tool, dryRun) {
|
|
7804
|
-
const settingsPath = join19(
|
|
7897
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7805
7898
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7806
7899
|
let settings = {};
|
|
7807
7900
|
if (raw !== null) {
|
|
@@ -7851,7 +7944,7 @@ async function installHook(spec, tool, dryRun) {
|
|
|
7851
7944
|
return raw === null ? "injected" : "updated";
|
|
7852
7945
|
}
|
|
7853
7946
|
async function removeHook(spec, tool, dryRun) {
|
|
7854
|
-
const settingsPath = join19(
|
|
7947
|
+
const settingsPath = join19(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
7855
7948
|
const raw = await readFile19(settingsPath, "utf8").catch(() => null);
|
|
7856
7949
|
if (raw === null) return "unchanged";
|
|
7857
7950
|
let parsed;
|
|
@@ -8062,7 +8155,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
8062
8155
|
});
|
|
8063
8156
|
|
|
8064
8157
|
// src/program.ts
|
|
8065
|
-
import { Command as
|
|
8158
|
+
import { Command as Command28 } from "commander";
|
|
8066
8159
|
|
|
8067
8160
|
// src/help.ts
|
|
8068
8161
|
init_ui();
|
|
@@ -8086,7 +8179,7 @@ var COMMAND_GROUPS = [
|
|
|
8086
8179
|
{
|
|
8087
8180
|
title: "ACCOUNT",
|
|
8088
8181
|
blurb: "optional \u2014 connect a hosted brain; everything above works without it",
|
|
8089
|
-
commands: ["login", "logout", "whoami", "push", "pull"]
|
|
8182
|
+
commands: ["login", "logout", "whoami", "brains", "push", "pull"]
|
|
8090
8183
|
},
|
|
8091
8184
|
{
|
|
8092
8185
|
title: "SYSTEM",
|
|
@@ -8099,7 +8192,12 @@ var ENV_ROWS = [
|
|
|
8099
8192
|
["MEMGINEERING_HOME=<dir>", "state + derived index (default: ~/.memgineering)"],
|
|
8100
8193
|
["MEMGINEERING_JSON=1", "force --json framing without the flag"],
|
|
8101
8194
|
["MEMGINEERING_NO_UPDATE=1", "skip the update check for this run"],
|
|
8102
|
-
["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)"]
|
|
8103
8201
|
];
|
|
8104
8202
|
function commandLabel(sub) {
|
|
8105
8203
|
const args = sub.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
|
|
@@ -8252,12 +8350,13 @@ var cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
|
8252
8350
|
var cloudReindex = (brain, args = {}) => call(brain, "POST", "/reindex", {
|
|
8253
8351
|
...args.rebuild === true ? { rebuild: true } : {}
|
|
8254
8352
|
});
|
|
8255
|
-
async function listCloudBrains(token, baseUrl) {
|
|
8353
|
+
async function listCloudBrains(token, baseUrl, opts = {}) {
|
|
8256
8354
|
const res = await apiRequest({
|
|
8257
8355
|
method: "GET",
|
|
8258
8356
|
path: "/v1/brains",
|
|
8259
8357
|
token,
|
|
8260
|
-
baseUrl
|
|
8358
|
+
baseUrl,
|
|
8359
|
+
...opts.retries === void 0 ? {} : { retries: opts.retries }
|
|
8261
8360
|
});
|
|
8262
8361
|
return res.body?.brains ?? [];
|
|
8263
8362
|
}
|
|
@@ -8275,6 +8374,125 @@ async function createCloudBrain(token, baseUrl, name) {
|
|
|
8275
8374
|
// src/commands/brain-admin.ts
|
|
8276
8375
|
init_config();
|
|
8277
8376
|
|
|
8377
|
+
// src/lib/held-back.ts
|
|
8378
|
+
init_ui();
|
|
8379
|
+
var isNote = (p) => /\.(md|markdown)$/i.test(p);
|
|
8380
|
+
function heldBack(scan) {
|
|
8381
|
+
const denied = scan.denied ?? [];
|
|
8382
|
+
const byName = denied.filter((d) => d.kind === "name" && isNote(d.path)).map((d) => ({ path: d.path, term: d.term }));
|
|
8383
|
+
const explicitly = denied.filter((d) => d.kind === "explicit" && isNote(d.path)).map((d) => d.path);
|
|
8384
|
+
const byRule = scan.excluded.filter(isNote);
|
|
8385
|
+
const symlinked = scan.skippedSymlinks.filter(isNote);
|
|
8386
|
+
return {
|
|
8387
|
+
total: byName.length + explicitly.length + byRule.length + symlinked.length,
|
|
8388
|
+
folders: scan.excludedDirs ?? [],
|
|
8389
|
+
byName,
|
|
8390
|
+
explicitly,
|
|
8391
|
+
byRule,
|
|
8392
|
+
symlinked
|
|
8393
|
+
};
|
|
8394
|
+
}
|
|
8395
|
+
function heldBackJson(held, totalKey = "not_sent") {
|
|
8396
|
+
return {
|
|
8397
|
+
// The count's key is the caller's, because `not_sent` is a claim about
|
|
8398
|
+
// sending and `reindex` sends nothing. Naming its total `not_sent` would
|
|
8399
|
+
// have an agent reporting a failed upload from a command that never
|
|
8400
|
+
// contacts a server. `push` keeps the key it has always emitted.
|
|
8401
|
+
[totalKey]: held.total,
|
|
8402
|
+
denied_by_name: held.byName,
|
|
8403
|
+
denied_explicitly: held.explicitly,
|
|
8404
|
+
excluded_by_rule: held.byRule,
|
|
8405
|
+
skipped_symlinks: held.symlinked,
|
|
8406
|
+
// Present so a zero total can be read correctly. A non-empty list here means
|
|
8407
|
+
// the count above is a floor, not the answer — nothing inside these was
|
|
8408
|
+
// walked, so nobody knows how many notes they hold.
|
|
8409
|
+
excluded_folders: held.folders
|
|
8410
|
+
};
|
|
8411
|
+
}
|
|
8412
|
+
var NAME_GUESS_LABEL = "name looks private";
|
|
8413
|
+
function heldBackGroups(held) {
|
|
8414
|
+
return [
|
|
8415
|
+
{
|
|
8416
|
+
label: NAME_GUESS_LABEL,
|
|
8417
|
+
rows: held.byName.map((d) => ({ path: d.path, detail: `matched "${d.term ?? "?"}"` }))
|
|
8418
|
+
},
|
|
8419
|
+
{ label: "listed in .memgdeny", rows: held.explicitly.map((p) => ({ path: p, detail: null })) },
|
|
8420
|
+
{ label: "excluded by a rule", rows: held.byRule.map((p) => ({ path: p, detail: null })) },
|
|
8421
|
+
{
|
|
8422
|
+
label: "a symlink, which is never followed",
|
|
8423
|
+
rows: held.symlinked.map((p) => ({ path: p, detail: null }))
|
|
8424
|
+
}
|
|
8425
|
+
];
|
|
8426
|
+
}
|
|
8427
|
+
function describeHeldBack(held) {
|
|
8428
|
+
return [
|
|
8429
|
+
...heldBackGroups(held).flatMap(
|
|
8430
|
+
({ label, rows }) => rows.map(
|
|
8431
|
+
(r) => `${oneLine(r.path, WIDTH.path)} \u2014 ${label}${r.detail === null ? "" : ` (${oneLine(r.detail, WIDTH.text)})`}`
|
|
8432
|
+
)
|
|
8433
|
+
),
|
|
8434
|
+
...held.folders.map(
|
|
8435
|
+
(dir) => `${oneLine(dir, WIDTH.path)}/ \u2014 an excluded folder, whose contents were never walked`
|
|
8436
|
+
)
|
|
8437
|
+
];
|
|
8438
|
+
}
|
|
8439
|
+
function printHeldBack(held, wording) {
|
|
8440
|
+
if (held.total === 0 && held.folders.length === 0) return;
|
|
8441
|
+
const guessed = held.byName;
|
|
8442
|
+
const chosen = heldBackGroups(held).filter(({ label }) => label !== NAME_GUESS_LABEL);
|
|
8443
|
+
const chosenCount = chosen.reduce((n, g) => n + g.rows.length, 0);
|
|
8444
|
+
if (chosenCount > 0 || held.folders.length > 0) {
|
|
8445
|
+
printHuman(
|
|
8446
|
+
c.gray(
|
|
8447
|
+
chosenCount === 0 ? `
|
|
8448
|
+
Some notes are not in ${wording.where}, because your rules say so:` : `
|
|
8449
|
+
${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
8450
|
+
)
|
|
8451
|
+
);
|
|
8452
|
+
for (const { label, rows } of chosen) {
|
|
8453
|
+
if (rows.length === 0) continue;
|
|
8454
|
+
printHuman(c.gray(` ${rows.length} \u2014 ${label}:`));
|
|
8455
|
+
for (const r of rows.slice(0, 10)) printHuman(c.gray(` ${oneLine(r.path, WIDTH.path)}`));
|
|
8456
|
+
if (rows.length > 10) printHuman(c.gray(` \u2026 and ${rows.length - 10} more`));
|
|
8457
|
+
}
|
|
8458
|
+
if (held.folders.length > 0) {
|
|
8459
|
+
printHuman(
|
|
8460
|
+
c.gray(
|
|
8461
|
+
` ${held.folders.length} excluded folder(s) \u2014 their contents were not walked, so notes
|
|
8462
|
+
inside them are not counted above:`
|
|
8463
|
+
)
|
|
8464
|
+
);
|
|
8465
|
+
for (const dir of held.folders.slice(0, 10))
|
|
8466
|
+
printHuman(c.gray(` ${oneLine(dir, WIDTH.path)}/`));
|
|
8467
|
+
if (held.folders.length > 10) {
|
|
8468
|
+
printHuman(c.gray(` \u2026 and ${held.folders.length - 10} more`));
|
|
8469
|
+
}
|
|
8470
|
+
}
|
|
8471
|
+
}
|
|
8472
|
+
if (guessed.length > 0) {
|
|
8473
|
+
printHuman(
|
|
8474
|
+
c.yellow(
|
|
8475
|
+
`
|
|
8476
|
+
\u26A0 ${guessed.length} note(s) are not in ${wording.where}, held back on this machine by a GUESS about the name:`
|
|
8477
|
+
)
|
|
8478
|
+
);
|
|
8479
|
+
for (const d of guessed.slice(0, 10)) {
|
|
8480
|
+
printHuman(
|
|
8481
|
+
c.gray(
|
|
8482
|
+
` ${oneLine(d.path, WIDTH.path)} (matched "${oneLine(d.term ?? "?", WIDTH.title)}")`
|
|
8483
|
+
)
|
|
8484
|
+
);
|
|
8485
|
+
}
|
|
8486
|
+
if (guessed.length > 10) printHuman(c.gray(` \u2026 and ${guessed.length - 10} more`));
|
|
8487
|
+
printHuman(
|
|
8488
|
+
c.gray(
|
|
8489
|
+
` This may be a false alarm \u2014 rename the note, or list it in .memgallow,
|
|
8490
|
+
and ${wording.retry}.`
|
|
8491
|
+
)
|
|
8492
|
+
);
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
|
|
8278
8496
|
// src/lib/ledger.ts
|
|
8279
8497
|
init_deny();
|
|
8280
8498
|
init_brand();
|
|
@@ -8311,13 +8529,13 @@ function isOpaqueId(id) {
|
|
|
8311
8529
|
return id.length === OPAQUE_ID_LENGTH && /^[0-9ABCDEFGHJKMNPQRSTVWXYZ]+$/.test(id);
|
|
8312
8530
|
}
|
|
8313
8531
|
function displayHandle(id, allIds) {
|
|
8314
|
-
if (!isOpaqueId(id)) return
|
|
8532
|
+
if (!isOpaqueId(id)) return oneLine(id, WIDTH.handle);
|
|
8315
8533
|
for (let len = MIN_HANDLE_LENGTH; len < id.length; len++) {
|
|
8316
8534
|
const prefix = id.slice(0, len);
|
|
8317
8535
|
const matches = allIds.filter((other) => other.startsWith(prefix));
|
|
8318
8536
|
if (matches.length === 1) return prefix;
|
|
8319
8537
|
}
|
|
8320
|
-
return
|
|
8538
|
+
return oneLine(id, WIDTH.handle);
|
|
8321
8539
|
}
|
|
8322
8540
|
function matchIds(ref, allIds) {
|
|
8323
8541
|
const exact = allIds.filter((id) => id === ref);
|
|
@@ -8413,6 +8631,13 @@ var RULE_VERSION = 2;
|
|
|
8413
8631
|
var RATIONALE_MAX = 500;
|
|
8414
8632
|
function sanitizeRationale(raw) {
|
|
8415
8633
|
if (raw == null) return null;
|
|
8634
|
+
if (CONTROL_INTRODUCER.test(raw)) {
|
|
8635
|
+
throw memgError(
|
|
8636
|
+
"invalid_input",
|
|
8637
|
+
"the reason contains a terminal control sequence",
|
|
8638
|
+
"Nothing was saved. The text carries invisible formatting \u2014 usually from pasting out of a terminal, a chat window, or a file that was decoded wrongly \u2014 and stored prose that hides characters can read as something other than what it says. Type the same words in again as plain text and run it once more. Keep --reason: the reason is the part of a memory that still means anything later."
|
|
8639
|
+
);
|
|
8640
|
+
}
|
|
8416
8641
|
const clean = stripControl(raw).trim();
|
|
8417
8642
|
if (clean === "") return null;
|
|
8418
8643
|
const hits = secretContentHits(clean);
|
|
@@ -8537,14 +8762,83 @@ init_api_client();
|
|
|
8537
8762
|
init_credentials();
|
|
8538
8763
|
init_pending_login();
|
|
8539
8764
|
init_config();
|
|
8765
|
+
init_ui();
|
|
8540
8766
|
init_vault();
|
|
8541
8767
|
async function resolveTarget(opts = {}) {
|
|
8542
8768
|
if (opts.local !== true) {
|
|
8543
8769
|
const cloud = await cloudTarget();
|
|
8544
|
-
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;
|
|
8545
8841
|
}
|
|
8546
|
-
const { brain, via } = await resolveBrain(opts.vault);
|
|
8547
|
-
return { kind: "local", brain, via };
|
|
8548
8842
|
}
|
|
8549
8843
|
async function hostedPointer() {
|
|
8550
8844
|
const base = apiUrl();
|
|
@@ -8603,14 +8897,17 @@ function reindexCommand() {
|
|
|
8603
8897
|
protectedVault: true
|
|
8604
8898
|
});
|
|
8605
8899
|
const scan = await adapter.scan();
|
|
8606
|
-
const
|
|
8900
|
+
const held = heldBack(scan);
|
|
8607
8901
|
printDual({
|
|
8608
8902
|
json: {
|
|
8609
8903
|
brain: brain.root,
|
|
8610
8904
|
indexed: built.entries.length,
|
|
8611
8905
|
cache_dir: built.cacheDir,
|
|
8612
8906
|
issues: built.issues,
|
|
8613
|
-
denied_by_name
|
|
8907
|
+
// Carries `denied_by_name` under the key it always had, plus the three
|
|
8908
|
+
// this command used to drop. The total is `not_indexed` rather than
|
|
8909
|
+
// push's `not_sent`: nothing is sent from here.
|
|
8910
|
+
...heldBackJson(held, "not_indexed"),
|
|
8614
8911
|
denied_by_content: built.denied.filter((d) => d.reason === "content")
|
|
8615
8912
|
},
|
|
8616
8913
|
human: () => {
|
|
@@ -8623,14 +8920,23 @@ function reindexCommand() {
|
|
|
8623
8920
|
)
|
|
8624
8921
|
);
|
|
8625
8922
|
}
|
|
8923
|
+
printHeldBack(held, { where: "the index", retry: "reindex" });
|
|
8626
8924
|
const byContent = built.denied.filter((d) => d.reason === "content");
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8925
|
+
if (byContent.length > 0) {
|
|
8926
|
+
printHuman(
|
|
8927
|
+
c.yellow(
|
|
8928
|
+
`
|
|
8929
|
+
\u26A0 ${byContent.length} note(s) refused \u2014 content looks like a credential:`
|
|
8930
|
+
)
|
|
8931
|
+
);
|
|
8932
|
+
for (const d of byContent.slice(0, 10)) {
|
|
8933
|
+
printHuman(
|
|
8934
|
+
c.gray(` ${oneLine(d.path, WIDTH.path)} (${oneLine(d.detail, WIDTH.text)})`)
|
|
8935
|
+
);
|
|
8936
|
+
}
|
|
8937
|
+
if (byContent.length > 10) {
|
|
8938
|
+
printHuman(c.gray(` \u2026 and ${byContent.length - 10} more`));
|
|
8939
|
+
}
|
|
8634
8940
|
}
|
|
8635
8941
|
}
|
|
8636
8942
|
});
|
|
@@ -8647,7 +8953,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8647
8953
|
// route documents, spelled the way the route spells it.
|
|
8648
8954
|
json: { brain: { hosted: true, name: target.name }, ...answered },
|
|
8649
8955
|
human: () => {
|
|
8650
|
-
printHuman(`Reindexed ${
|
|
8956
|
+
printHuman(`Reindexed ${oneLine(target.name, WIDTH.title)}.
|
|
8651
8957
|
`);
|
|
8652
8958
|
if (lexical === void 0) {
|
|
8653
8959
|
printHuman(c.yellow("\u26A0 this brain\u2019s server did not report the card index"));
|
|
@@ -8685,7 +8991,7 @@ async function reindexHosted(target, rebuild) {
|
|
|
8685
8991
|
if (answered.usable === false) {
|
|
8686
8992
|
printHuman(
|
|
8687
8993
|
c.yellow(
|
|
8688
|
-
` \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)})`
|
|
8689
8995
|
)
|
|
8690
8996
|
);
|
|
8691
8997
|
}
|
|
@@ -8704,7 +9010,7 @@ function printFailures(failed, failures) {
|
|
|
8704
9010
|
for (const failure of (failures ?? []).slice(0, 3)) {
|
|
8705
9011
|
printHuman(
|
|
8706
9012
|
c.gray(
|
|
8707
|
-
` ${
|
|
9013
|
+
` ${oneLine(failure.path, WIDTH.path)} (${oneLine(failure.kind, WIDTH.title)}: ${oneLine(failure.detail, WIDTH.text)})`
|
|
8708
9014
|
)
|
|
8709
9015
|
);
|
|
8710
9016
|
}
|
|
@@ -8721,18 +9027,39 @@ function nextStep(stopReason) {
|
|
|
8721
9027
|
case "unusable_index":
|
|
8722
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.";
|
|
8723
9029
|
default:
|
|
8724
|
-
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.`;
|
|
8725
9031
|
}
|
|
8726
9032
|
}
|
|
8727
9033
|
function unlinkCommand() {
|
|
8728
|
-
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) => {
|
|
8729
|
-
|
|
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
|
+
});
|
|
8730
9052
|
const cfg = await loadConfig();
|
|
8731
9053
|
const remaining = cfg.brain.brains.filter((b) => b.root !== brain.root);
|
|
8732
9054
|
const nextDefault = cfg.brain.default === brain.root ? remaining[0]?.root : cfg.brain.default;
|
|
9055
|
+
const { default: _dropped, ...restOfBrain } = cfg.brain;
|
|
8733
9056
|
await saveConfig({
|
|
8734
9057
|
...cfg,
|
|
8735
|
-
brain: {
|
|
9058
|
+
brain: {
|
|
9059
|
+
...restOfBrain,
|
|
9060
|
+
brains: remaining,
|
|
9061
|
+
...nextDefault === void 0 ? {} : { default: nextDefault }
|
|
9062
|
+
}
|
|
8736
9063
|
});
|
|
8737
9064
|
if (opts.purge) {
|
|
8738
9065
|
await clearCache(cacheDirFor(brain.root, brandHome()));
|
|
@@ -8820,6 +9147,54 @@ async function readLines(root, name) {
|
|
|
8820
9147
|
const raw = await readFile10(join10(root, name), "utf8").catch(() => "");
|
|
8821
9148
|
return raw.split("\n").map((l) => l.replace(/\r$/, "").trim()).filter((l) => l !== "" && !l.startsWith("#"));
|
|
8822
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
|
+
}
|
|
8823
9198
|
|
|
8824
9199
|
// src/commands/consolidate.ts
|
|
8825
9200
|
init_src();
|
|
@@ -9117,17 +9492,23 @@ function openCommand() {
|
|
|
9117
9492
|
brain: { hosted: true, name: target.name }
|
|
9118
9493
|
},
|
|
9119
9494
|
human: () => {
|
|
9120
|
-
printHuman(
|
|
9121
|
-
|
|
9495
|
+
printHuman(
|
|
9496
|
+
`## ${oneLine(String(found.memory["title"] ?? found.path), WIDTH.title)}
|
|
9497
|
+
`
|
|
9498
|
+
);
|
|
9122
9499
|
const summary = found.memory["summary"];
|
|
9123
9500
|
if (typeof summary === "string" && summary !== "") {
|
|
9124
|
-
printHuman(`> ${
|
|
9501
|
+
printHuman(`> ${oneLine(summary, WIDTH.text)}
|
|
9125
9502
|
`);
|
|
9126
9503
|
}
|
|
9127
|
-
printHuman(
|
|
9504
|
+
printHuman(
|
|
9505
|
+
c.gray(
|
|
9506
|
+
`${oneLine(found.path, WIDTH.path)} \xB7 ${oneLine(target.name, WIDTH.title)}`
|
|
9507
|
+
)
|
|
9508
|
+
);
|
|
9128
9509
|
if (detail !== "card" && found.body !== "") {
|
|
9129
|
-
printHuman(
|
|
9130
|
-
|
|
9510
|
+
printHuman("\n");
|
|
9511
|
+
printBlock(found.body);
|
|
9131
9512
|
}
|
|
9132
9513
|
}
|
|
9133
9514
|
});
|
|
@@ -9164,7 +9545,7 @@ ${stripControl(found.body)}`);
|
|
|
9164
9545
|
...stage
|
|
9165
9546
|
},
|
|
9166
9547
|
human: () => {
|
|
9167
|
-
printHuman(`## ${
|
|
9548
|
+
printHuman(`## ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9168
9549
|
`);
|
|
9169
9550
|
if (scaffolding) {
|
|
9170
9551
|
printHuman(
|
|
@@ -9177,13 +9558,17 @@ ${stripControl(found.body)}`);
|
|
|
9177
9558
|
}
|
|
9178
9559
|
printHuman(
|
|
9179
9560
|
`
|
|
9180
|
-
\`id: ${
|
|
9561
|
+
\`id: ${oneLine(entry.memory.id, WIDTH.handle)}\` \xB7 \`${oneLine(entry.path, WIDTH.path)}\`
|
|
9181
9562
|
`
|
|
9182
9563
|
);
|
|
9183
9564
|
if (entry.memory.summary) printHuman(quoteBlock(entry.memory.summary) + "\n");
|
|
9184
9565
|
for (const claim of claims) {
|
|
9185
|
-
printHuman(`- ${
|
|
9186
|
-
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
|
+
);
|
|
9187
9572
|
}
|
|
9188
9573
|
if (claims.length === 0 && scaffolding) {
|
|
9189
9574
|
printHuman(
|
|
@@ -9214,13 +9599,14 @@ makes the two surface together as a conflict.`
|
|
|
9214
9599
|
}
|
|
9215
9600
|
if (stage.outline && stage.outline.length > 0) {
|
|
9216
9601
|
printHuman("\n### sections\n");
|
|
9217
|
-
for (const heading of stage.outline)
|
|
9602
|
+
for (const heading of stage.outline)
|
|
9603
|
+
printHuman(`- ${oneLine(heading, WIDTH.title)}`);
|
|
9218
9604
|
printHuman("");
|
|
9219
9605
|
}
|
|
9220
9606
|
if (stage.sections) {
|
|
9221
9607
|
for (const section of stage.sections) {
|
|
9222
9608
|
printHuman(`
|
|
9223
|
-
### ${
|
|
9609
|
+
### ${oneLine(section.label, WIDTH.title)}
|
|
9224
9610
|
`);
|
|
9225
9611
|
printHuman(quoteBlock(section.body));
|
|
9226
9612
|
}
|
|
@@ -9780,11 +10166,11 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
9780
10166
|
after: preview.after
|
|
9781
10167
|
},
|
|
9782
10168
|
human: () => {
|
|
9783
|
-
printHuman(`## revise (dry run): ${
|
|
10169
|
+
printHuman(`## revise (dry run): ${oneLine(entry.memory.title, WIDTH.title)}
|
|
9784
10170
|
`);
|
|
9785
|
-
printHuman(`\`${
|
|
10171
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
9786
10172
|
`);
|
|
9787
|
-
|
|
10173
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
9788
10174
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
9789
10175
|
}
|
|
9790
10176
|
});
|
|
@@ -9844,10 +10230,10 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
9844
10230
|
human: () => {
|
|
9845
10231
|
if (proposal.action === "conflict") {
|
|
9846
10232
|
printHuman(
|
|
9847
|
-
`Recorded a disagreement about **${
|
|
10233
|
+
`Recorded a disagreement about **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
9848
10234
|
`
|
|
9849
10235
|
);
|
|
9850
|
-
printHuman(`\`${
|
|
10236
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
9851
10237
|
`);
|
|
9852
10238
|
printHuman(
|
|
9853
10239
|
c.gray(
|
|
@@ -9855,12 +10241,12 @@ To see what is there now: \`memgineering open ` + ref + "`"
|
|
|
9855
10241
|
)
|
|
9856
10242
|
);
|
|
9857
10243
|
} else {
|
|
9858
|
-
printHuman(`Revised **${
|
|
10244
|
+
printHuman(`Revised **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
9859
10245
|
`);
|
|
9860
|
-
printHuman(`\`${
|
|
10246
|
+
printHuman(`\`${oneLine(entry.path, WIDTH.path)}\`
|
|
9861
10247
|
`);
|
|
9862
10248
|
}
|
|
9863
|
-
|
|
10249
|
+
printBlock(renderDiff(preview.before, preview.after));
|
|
9864
10250
|
if (drift) {
|
|
9865
10251
|
printHuman(
|
|
9866
10252
|
c.yellow(`
|
|
@@ -9927,18 +10313,18 @@ async function reviseHosted(where, ref, opts) {
|
|
|
9927
10313
|
json: { brain: where.name, ...result },
|
|
9928
10314
|
human: () => {
|
|
9929
10315
|
if (result.dry_run) {
|
|
9930
|
-
printHuman(`## revise (dry run) \u2014 ${
|
|
10316
|
+
printHuman(`## revise (dry run) \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
9931
10317
|
`);
|
|
9932
|
-
printHuman(`\`${
|
|
10318
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
9933
10319
|
`);
|
|
9934
|
-
|
|
10320
|
+
printBlock(renderDiff(result.before, result.after));
|
|
9935
10321
|
printHuman(c.gray("\nNothing was written. Drop --dry-run to apply."));
|
|
9936
10322
|
return;
|
|
9937
10323
|
}
|
|
9938
10324
|
if (opts.action === "conflict") {
|
|
9939
|
-
printHuman(`Recorded a disagreement in **${
|
|
10325
|
+
printHuman(`Recorded a disagreement in **${oneLine(where.name, WIDTH.title)}**.
|
|
9940
10326
|
`);
|
|
9941
|
-
printHuman(`\`${
|
|
10327
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
9942
10328
|
`);
|
|
9943
10329
|
printHuman(
|
|
9944
10330
|
c.gray(
|
|
@@ -9946,16 +10332,16 @@ async function reviseHosted(where, ref, opts) {
|
|
|
9946
10332
|
)
|
|
9947
10333
|
);
|
|
9948
10334
|
} else {
|
|
9949
|
-
printHuman(`Revised in **${
|
|
10335
|
+
printHuman(`Revised in **${oneLine(where.name, WIDTH.title)}**.
|
|
9950
10336
|
`);
|
|
9951
|
-
printHuman(`\`${
|
|
10337
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
9952
10338
|
`);
|
|
9953
10339
|
}
|
|
9954
|
-
|
|
10340
|
+
printBlock(renderDiff(result.before, result.after));
|
|
9955
10341
|
if (result.body_drift) {
|
|
9956
10342
|
printHuman(
|
|
9957
10343
|
c.yellow(`
|
|
9958
|
-
\u26A0 the body still reads "${
|
|
10344
|
+
\u26A0 the body still reads "${oneLine(result.body_drift, WIDTH.text)}"`) + c.gray(
|
|
9959
10345
|
"\n \u2014 the frontmatter now concludes differently. The prose is yours and was not\n touched; edit it yourself if it should agree."
|
|
9960
10346
|
)
|
|
9961
10347
|
);
|
|
@@ -9979,7 +10365,7 @@ To put it back: \`memgineering revise ${ref} --claim ${JSON.stringify(
|
|
|
9979
10365
|
);
|
|
9980
10366
|
} else if (result.reverse?.hint) {
|
|
9981
10367
|
printHuman(c.gray(`
|
|
9982
|
-
${
|
|
10368
|
+
${oneLine(result.reverse.hint, WIDTH.text)}`));
|
|
9983
10369
|
}
|
|
9984
10370
|
}
|
|
9985
10371
|
});
|
|
@@ -10023,7 +10409,7 @@ function nothingToChange(path, ref, verb = "revise") {
|
|
|
10023
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.`;
|
|
10024
10410
|
return memgError(
|
|
10025
10411
|
"nothing_to_change",
|
|
10026
|
-
`${
|
|
10412
|
+
`${oneLine(path, WIDTH.path)} already says exactly this`,
|
|
10027
10413
|
`Nothing was written. ${way}
|
|
10028
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.
|
|
10029
10415
|
To see what the note says now: \`memgineering open ${ref}\``
|
|
@@ -10066,11 +10452,17 @@ function proposalsCommand() {
|
|
|
10066
10452
|
opts.all === true ? { all: true } : {}
|
|
10067
10453
|
) : await listLocally(where.brain.root, opts.all === true);
|
|
10068
10454
|
const shown = listed.proposals;
|
|
10455
|
+
const unavailable = shown.filter(
|
|
10456
|
+
(p) => p.claim === void 0 && !carriesNoClaim(p.action)
|
|
10457
|
+
);
|
|
10458
|
+
const rows = shown.map(
|
|
10459
|
+
(p) => p.claim === void 0 && !carriesNoClaim(p.action) ? { ...p, claim: null, claim_unavailable: true } : p
|
|
10460
|
+
);
|
|
10069
10461
|
printDual({
|
|
10070
10462
|
json: {
|
|
10071
|
-
proposals:
|
|
10072
|
-
count:
|
|
10073
|
-
hint: hintForList(shown.length, listed.total, opts.all === true)
|
|
10463
|
+
proposals: rows,
|
|
10464
|
+
count: rows.length,
|
|
10465
|
+
hint: unavailable.length > 0 ? `${unavailable.length} of these came back without a claim because this brain's server is older than the CLI \u2014 they are marked \`claim_unavailable\`, and \`memgineering open <id>\` shows what each note says now. ${hintForList(shown.length, listed.total, opts.all === true)}` : hintForList(shown.length, listed.total, opts.all === true)
|
|
10074
10466
|
},
|
|
10075
10467
|
human: () => {
|
|
10076
10468
|
if (shown.length === 0) {
|
|
@@ -10111,6 +10503,7 @@ function summarize(p) {
|
|
|
10111
10503
|
action: p.action,
|
|
10112
10504
|
memory_id: p.memory_id,
|
|
10113
10505
|
target: p.target.path,
|
|
10506
|
+
claim: p.claim?.text ?? null,
|
|
10114
10507
|
reason: p.reason,
|
|
10115
10508
|
proposed_kind: p.proposed_kind,
|
|
10116
10509
|
supersedes: p.relations.supersedes,
|
|
@@ -10118,14 +10511,28 @@ function summarize(p) {
|
|
|
10118
10511
|
expires_at: p.expiresAt
|
|
10119
10512
|
};
|
|
10120
10513
|
}
|
|
10514
|
+
var CLAIM_WIDTH = 160;
|
|
10121
10515
|
function renderSummary(p) {
|
|
10516
|
+
const claimless = carriesNoClaim(p.action);
|
|
10517
|
+
const claim = p.claim === void 0 || p.claim === null ? null : oneLine(p.claim, CLAIM_WIDTH);
|
|
10518
|
+
const unavailable = p.claim === void 0 && !claimless;
|
|
10122
10519
|
const lines2 = [
|
|
10123
|
-
`- **${
|
|
10124
|
-
` ${p.id} ${p.state}
|
|
10125
|
-
quoteBlock(p.reason, " > ")
|
|
10520
|
+
`- **${oneLine(p.action, WIDTH.title)}**${p.proposed_kind ? ` \u2192 ${p.proposed_kind}` : ""} \`${oneLine(p.target, WIDTH.path)}\``,
|
|
10521
|
+
` ${p.id} ${p.state}`
|
|
10126
10522
|
];
|
|
10523
|
+
if (claim !== null && claim !== "") lines2.push(` \u2192 ${claim}`);
|
|
10524
|
+
else if (unavailable) {
|
|
10525
|
+
lines2.push(
|
|
10526
|
+
c.gray(
|
|
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.)`
|
|
10528
|
+
)
|
|
10529
|
+
);
|
|
10530
|
+
}
|
|
10531
|
+
lines2.push(quoteBlock(p.reason, " > "));
|
|
10127
10532
|
if (p.supersedes.length > 0) {
|
|
10128
|
-
lines2.push(
|
|
10533
|
+
lines2.push(
|
|
10534
|
+
c.gray(` replaces: ${p.supersedes.map((id) => oneLine(id, WIDTH.handle)).join(", ")}`)
|
|
10535
|
+
);
|
|
10129
10536
|
}
|
|
10130
10537
|
return lines2.join("\n");
|
|
10131
10538
|
}
|
|
@@ -10241,10 +10648,10 @@ function renderApproval(applied, vault) {
|
|
|
10241
10648
|
},
|
|
10242
10649
|
human: () => {
|
|
10243
10650
|
printHuman(
|
|
10244
|
-
`Applied **${
|
|
10651
|
+
`Applied **${oneLine(applied.action, WIDTH.title)}** to \`${oneLine(applied.target, WIDTH.path)}\`.
|
|
10245
10652
|
`
|
|
10246
10653
|
);
|
|
10247
|
-
|
|
10654
|
+
printBlock(renderDiff(applied.before, applied.after));
|
|
10248
10655
|
printHuman(c.gray(`
|
|
10249
10656
|
Undo: \`${undoHint(applied.opId, vault)}\``));
|
|
10250
10657
|
}
|
|
@@ -10254,7 +10661,7 @@ function renderRejection(id, state, target) {
|
|
|
10254
10661
|
printDual({
|
|
10255
10662
|
json: { proposal_id: id, state, target },
|
|
10256
10663
|
human: () => {
|
|
10257
|
-
printHuman(`Declined \`${
|
|
10664
|
+
printHuman(`Declined \`${oneLine(target, WIDTH.path)}\`.
|
|
10258
10665
|
`);
|
|
10259
10666
|
printHuman(c.gray("The note was not touched."));
|
|
10260
10667
|
}
|
|
@@ -10312,9 +10719,9 @@ function renderRebase(id, state, target, before, after) {
|
|
|
10312
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.`
|
|
10313
10720
|
},
|
|
10314
10721
|
human: () => {
|
|
10315
|
-
printHuman(`Recomputed against \`${
|
|
10722
|
+
printHuman(`Recomputed against \`${oneLine(target, WIDTH.path)}\`.
|
|
10316
10723
|
`);
|
|
10317
|
-
|
|
10724
|
+
printBlock(renderDiff(before, after));
|
|
10318
10725
|
printHuman(
|
|
10319
10726
|
c.gray(
|
|
10320
10727
|
`
|
|
@@ -10471,14 +10878,25 @@ function consolidateCommand() {
|
|
|
10471
10878
|
});
|
|
10472
10879
|
});
|
|
10473
10880
|
}
|
|
10881
|
+
var TITLE_WIDTH = 72;
|
|
10882
|
+
function noteLines(id, title, path) {
|
|
10883
|
+
const shown = oneLine(title, TITLE_WIDTH);
|
|
10884
|
+
return [
|
|
10885
|
+
shown === "" ? ` ${oneLine(id, WIDTH.handle)}` : ` ${oneLine(id, WIDTH.handle)} ${shown}`,
|
|
10886
|
+
// Backticked, as it was before the title moved onto the row above it. This
|
|
10887
|
+
// output is read by agents as markdown, and a path outside code formatting
|
|
10888
|
+
// is a path they have to guess the extent of.
|
|
10889
|
+
c.gray(` \`${oneLine(path, WIDTH.path)}\``)
|
|
10890
|
+
];
|
|
10891
|
+
}
|
|
10474
10892
|
function renderCandidate(candidate) {
|
|
10475
10893
|
const lines2 = [
|
|
10476
|
-
`- **${
|
|
10477
|
-
|
|
10894
|
+
`- **${oneLine(candidate.rule, WIDTH.title)}** \u2014 ${oneLine(candidate.evidence, WIDTH.text)}`,
|
|
10895
|
+
...noteLines(candidate.memory_id, candidate.title, candidate.path)
|
|
10478
10896
|
];
|
|
10479
10897
|
if (candidate.other_id !== null) {
|
|
10480
10898
|
lines2.push(
|
|
10481
|
-
|
|
10899
|
+
...noteLines(candidate.other_id, candidate.other_title ?? "", candidate.other_path ?? "")
|
|
10482
10900
|
);
|
|
10483
10901
|
}
|
|
10484
10902
|
lines2.push(
|
|
@@ -10515,7 +10933,7 @@ function proposeCommand() {
|
|
|
10515
10933
|
},
|
|
10516
10934
|
human: () => {
|
|
10517
10935
|
printHuman(
|
|
10518
|
-
`Proposed **${
|
|
10936
|
+
`Proposed **${oneLine(filed.action, WIDTH.title)}** on \`${oneLine(filed.target, WIDTH.path)}\`.
|
|
10519
10937
|
`
|
|
10520
10938
|
);
|
|
10521
10939
|
printHuman(c.gray(` ${filed.id}`));
|
|
@@ -10950,14 +11368,14 @@ function lifecycleCommand(action) {
|
|
|
10950
11368
|
json: { action, memory_id: entry.memory.id, target: entry.path, op_id: record.op_id },
|
|
10951
11369
|
human: () => {
|
|
10952
11370
|
printHuman(
|
|
10953
|
-
action === "retire" ? `Retired **${
|
|
10954
|
-
` : `**${
|
|
11371
|
+
action === "retire" ? `Retired **${oneLine(entry.memory.title, WIDTH.title)}**.
|
|
11372
|
+
` : `**${oneLine(entry.memory.title, WIDTH.title)}** is current again.
|
|
10955
11373
|
`
|
|
10956
11374
|
);
|
|
10957
11375
|
if (action === "retire") {
|
|
10958
11376
|
printHuman(
|
|
10959
11377
|
c.gray(
|
|
10960
|
-
"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) + "`"
|
|
10961
11379
|
)
|
|
10962
11380
|
);
|
|
10963
11381
|
}
|
|
@@ -11015,8 +11433,10 @@ function ignoreCommand(mode) {
|
|
|
11015
11433
|
mode === "exclude" ? (
|
|
11016
11434
|
// The path is argv here, but argv came from a listing of the
|
|
11017
11435
|
// user's own notes — and a filename is as authored as a title.
|
|
11018
|
-
c.gray(
|
|
11019
|
-
|
|
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.`)
|
|
11020
11440
|
);
|
|
11021
11441
|
return;
|
|
11022
11442
|
}
|
|
@@ -11046,13 +11466,13 @@ async function lifecycleHosted(where, action, ref, reason) {
|
|
|
11046
11466
|
json: { brain: where.name, ...result },
|
|
11047
11467
|
human: () => {
|
|
11048
11468
|
printHuman(
|
|
11049
|
-
action === "retire" ? `Retired in **${
|
|
11050
|
-
` : `Current again in **${
|
|
11469
|
+
action === "retire" ? `Retired in **${oneLine(where.name, WIDTH.title)}**.
|
|
11470
|
+
` : `Current again in **${oneLine(where.name, WIDTH.title)}**.
|
|
11051
11471
|
`
|
|
11052
11472
|
);
|
|
11053
|
-
printHuman(`\`${
|
|
11473
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
11054
11474
|
`);
|
|
11055
|
-
printHuman(c.gray(
|
|
11475
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11056
11476
|
printHuman(
|
|
11057
11477
|
c.gray(
|
|
11058
11478
|
`
|
|
@@ -11072,15 +11492,15 @@ async function ignoreHosted(where, mode, path, reason) {
|
|
|
11072
11492
|
json: { brain: where.name, ...result },
|
|
11073
11493
|
human: () => {
|
|
11074
11494
|
if (!result.changed) {
|
|
11075
|
-
printHuman(c.gray(
|
|
11495
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11076
11496
|
return;
|
|
11077
11497
|
}
|
|
11078
11498
|
printHuman(
|
|
11079
|
-
mode === "exclude" ? `No longer reading \`${
|
|
11080
|
-
` : `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)}**.
|
|
11081
11501
|
`
|
|
11082
11502
|
);
|
|
11083
|
-
printHuman(c.gray(
|
|
11503
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
11084
11504
|
if (result.op_id) printHuman(c.gray(`
|
|
11085
11505
|
Undo: \`memgineering ${opposite(mode)} ${path}\``));
|
|
11086
11506
|
}
|
|
@@ -11144,10 +11564,12 @@ function evidenceCommand() {
|
|
|
11144
11564
|
});
|
|
11145
11565
|
}
|
|
11146
11566
|
function renderEvidence(report, opts) {
|
|
11147
|
-
printHuman(`## evidence: ${
|
|
11148
|
-
`);
|
|
11149
|
-
printHuman(`\`id: ${stripControl(report.id)}\` \xB7 \`${stripControl(report.path)}\`
|
|
11567
|
+
printHuman(`## evidence: ${oneLine(report.title, WIDTH.title)}
|
|
11150
11568
|
`);
|
|
11569
|
+
printHuman(
|
|
11570
|
+
`\`id: ${oneLine(report.id, WIDTH.handle)}\` \xB7 \`${oneLine(report.path, WIDTH.path)}\`
|
|
11571
|
+
`
|
|
11572
|
+
);
|
|
11151
11573
|
if (report.critical) {
|
|
11152
11574
|
printHuman(c.yellow("\u26A0 critical \u2014 this note defines how the agent behaves.") + "\n");
|
|
11153
11575
|
}
|
|
@@ -11197,11 +11619,12 @@ function renderEvidence(report, opts) {
|
|
|
11197
11619
|
]);
|
|
11198
11620
|
}
|
|
11199
11621
|
const pad = rows.reduce((w, [label]) => Math.max(w, label.length), 0) + 2;
|
|
11200
|
-
for (const [label, value] of rows)
|
|
11622
|
+
for (const [label, value] of rows)
|
|
11623
|
+
printHuman(` ${label.padEnd(pad)}${oneLine(value, WIDTH.text)}`);
|
|
11201
11624
|
if (report.standing) {
|
|
11202
11625
|
const means = STANDING_MEANS[report.standing];
|
|
11203
11626
|
printHuman(`
|
|
11204
|
-
**${
|
|
11627
|
+
**${oneLine(report.standing, WIDTH.title)}**${means ? ` \u2014 ${means}` : ""}`);
|
|
11205
11628
|
}
|
|
11206
11629
|
if (report.contradicts > 0) {
|
|
11207
11630
|
printHuman(
|
|
@@ -11222,7 +11645,7 @@ function renderEvidence(report, opts) {
|
|
|
11222
11645
|
report.last_reason.verb,
|
|
11223
11646
|
report.last_reason.ts === null ? null : report.last_reason.ts.slice(0, 10)
|
|
11224
11647
|
].filter((part) => part !== null && part !== "").join(", ");
|
|
11225
|
-
if (provenance !== "") printHuman(c.gray(` ${
|
|
11648
|
+
if (provenance !== "") printHuman(c.gray(` ${oneLine(provenance, WIDTH.text)}`));
|
|
11226
11649
|
} else if (report.changed > 0) {
|
|
11227
11650
|
printHuman(
|
|
11228
11651
|
c.gray(
|
|
@@ -11358,6 +11781,7 @@ import { Command as Command11 } from "commander";
|
|
|
11358
11781
|
// src/lib/rules.ts
|
|
11359
11782
|
init_src();
|
|
11360
11783
|
init_index_build();
|
|
11784
|
+
init_ui();
|
|
11361
11785
|
init_vault();
|
|
11362
11786
|
var RULE_LIMIT = 8;
|
|
11363
11787
|
async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date()) {
|
|
@@ -11386,11 +11810,12 @@ async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date())
|
|
|
11386
11810
|
};
|
|
11387
11811
|
}
|
|
11388
11812
|
function renderRuleLines(rules) {
|
|
11389
|
-
return rules.map(({ entry }) => `- ${entry.memory.summary ?? entry.memory.title}`);
|
|
11813
|
+
return rules.map(({ entry }) => `- ${oneLine(entry.memory.summary ?? entry.memory.title, 200)}`);
|
|
11390
11814
|
}
|
|
11391
11815
|
|
|
11392
11816
|
// src/commands/guard.ts
|
|
11393
11817
|
init_deadline();
|
|
11818
|
+
init_ui();
|
|
11394
11819
|
function guardCommand() {
|
|
11395
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) => {
|
|
11396
11821
|
try {
|
|
@@ -11430,7 +11855,7 @@ async function runGuard(payload, contextDirFlag) {
|
|
|
11430
11855
|
return JSON.stringify({
|
|
11431
11856
|
hookSpecificOutput: {
|
|
11432
11857
|
hookEventName: "PreToolUse",
|
|
11433
|
-
additionalContext: contextFor(claimed.
|
|
11858
|
+
additionalContext: contextFor(claimed.lines, claimed.total)
|
|
11434
11859
|
}
|
|
11435
11860
|
});
|
|
11436
11861
|
}
|
|
@@ -11444,16 +11869,22 @@ async function runStopGuard(payload, contextDirFlag) {
|
|
|
11444
11869
|
return JSON.stringify({
|
|
11445
11870
|
hookSpecificOutput: {
|
|
11446
11871
|
hookEventName: "Stop",
|
|
11447
|
-
additionalContext: stopContextFor(claimed.
|
|
11872
|
+
additionalContext: stopContextFor(claimed.lines, claimed.total)
|
|
11448
11873
|
}
|
|
11449
11874
|
});
|
|
11450
11875
|
}
|
|
11451
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
|
+
}
|
|
11452
11883
|
const collected = await collectRules(void 0, contextDir).catch(() => null);
|
|
11453
11884
|
if (collected === null) return null;
|
|
11454
11885
|
const { brainRoot, rules, total } = collected;
|
|
11455
11886
|
if (rules.length === 0) return null;
|
|
11456
|
-
if (!await claimSession(sessionId)) return null;
|
|
11887
|
+
if (!await claimSession(sessionId, brainRoot)) return null;
|
|
11457
11888
|
await recordEvent(brainRoot, {
|
|
11458
11889
|
verb: "guard",
|
|
11459
11890
|
contextDir,
|
|
@@ -11461,22 +11892,20 @@ async function claimRules(sessionId, contextDir) {
|
|
|
11461
11892
|
limit: RULE_LIMIT,
|
|
11462
11893
|
returnedIds: rules.map((r) => r.entry.memory.id)
|
|
11463
11894
|
});
|
|
11464
|
-
return { rules, total };
|
|
11895
|
+
return { lines: renderRuleLines(rules), total };
|
|
11465
11896
|
}
|
|
11466
|
-
function contextFor(
|
|
11467
|
-
const
|
|
11468
|
-
|
|
11469
|
-
(${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.)` : "";
|
|
11470
11900
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11471
11901
|
|
|
11472
11902
|
${lines2.join("\n")}${more}
|
|
11473
11903
|
|
|
11474
11904
|
Follow them. If what you are about to do goes against one, say so in a sentence instead of doing it quietly.`;
|
|
11475
11905
|
}
|
|
11476
|
-
function stopContextFor(
|
|
11477
|
-
const
|
|
11478
|
-
|
|
11479
|
-
(${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.)` : "";
|
|
11480
11909
|
return `Decisions this user has already made. They are theirs, not suggestions, and they hold whether or not this task mentions them:
|
|
11481
11910
|
|
|
11482
11911
|
${lines2.join("\n")}${more}
|
|
@@ -11507,10 +11936,10 @@ async function readPayload() {
|
|
|
11507
11936
|
}
|
|
11508
11937
|
}
|
|
11509
11938
|
var guardDir = () => join16(brandHome(), "guard");
|
|
11510
|
-
async function claimSession(sessionId) {
|
|
11939
|
+
async function claimSession(sessionId, brain) {
|
|
11511
11940
|
const marker = join16(
|
|
11512
11941
|
guardDir(),
|
|
11513
|
-
`${createHash7("sha256").update(sessionId).digest("hex").slice(0, 32)}`
|
|
11942
|
+
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
11514
11943
|
);
|
|
11515
11944
|
try {
|
|
11516
11945
|
await mkdir11(guardDir(), { recursive: true, mode: 448 });
|
|
@@ -11535,6 +11964,17 @@ async function pruneOldMarkers() {
|
|
|
11535
11964
|
} catch {
|
|
11536
11965
|
}
|
|
11537
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
|
+
}
|
|
11538
11978
|
|
|
11539
11979
|
// src/program.ts
|
|
11540
11980
|
init_init();
|
|
@@ -11577,7 +12017,7 @@ function logCommand() {
|
|
|
11577
12017
|
human: () => {
|
|
11578
12018
|
const diverged2 = Array.isArray(history["divergence"]) ? history["divergence"] : [];
|
|
11579
12019
|
const total = typeof history["total"] === "number" ? history["total"] : rows.length;
|
|
11580
|
-
printHuman(`## log \u2014 ${
|
|
12020
|
+
printHuman(`## log \u2014 ${oneLine(target.name, WIDTH.title)} (${total} operations)
|
|
11581
12021
|
`);
|
|
11582
12022
|
if (rows.length === 0) {
|
|
11583
12023
|
printHuman("_This brain has no recorded operations._\n");
|
|
@@ -11591,7 +12031,8 @@ function logCommand() {
|
|
|
11591
12031
|
);
|
|
11592
12032
|
}
|
|
11593
12033
|
const hint = history["hint"];
|
|
11594
|
-
if (typeof hint === "string" && hint !== "")
|
|
12034
|
+
if (typeof hint === "string" && hint !== "")
|
|
12035
|
+
printHuman(c.gray(oneLine(hint, WIDTH.text)));
|
|
11595
12036
|
}
|
|
11596
12037
|
});
|
|
11597
12038
|
return;
|
|
@@ -11713,15 +12154,19 @@ function renderOps(ops, opts) {
|
|
|
11713
12154
|
op.diverged && !op.undone ? "note has changed since" : null
|
|
11714
12155
|
].filter(Boolean);
|
|
11715
12156
|
printHuman(
|
|
11716
|
-
`- ${
|
|
12157
|
+
`- ${oneLine(op.verb, WIDTH.title)} \`${oneLine(op.target, WIDTH.path)}\`${marks.length ? c.yellow(` (${marks.join(", ")})`) : ""}`
|
|
11717
12158
|
);
|
|
11718
12159
|
printHuman(
|
|
11719
|
-
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)}`
|
|
11720
12163
|
);
|
|
11721
12164
|
if (op.rationale) printHuman(c.gray(quoteBlock(op.rationale, " > ")));
|
|
11722
12165
|
if (opts.verbose) {
|
|
11723
12166
|
printHuman(
|
|
11724
|
-
c.gray(
|
|
12167
|
+
c.gray(
|
|
12168
|
+
` ${oneLine(op.hash_before ?? "\u2205", WIDTH.handle)} \u2192 ${oneLine(op.hash_after ?? "\u2205", WIDTH.handle)}`
|
|
12169
|
+
)
|
|
11725
12170
|
);
|
|
11726
12171
|
}
|
|
11727
12172
|
}
|
|
@@ -11777,7 +12222,7 @@ init_pending_login();
|
|
|
11777
12222
|
init_ui();
|
|
11778
12223
|
import { Command as Command14 } from "commander";
|
|
11779
12224
|
function logoutCommand() {
|
|
11780
|
-
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(
|
|
11781
12226
|
"--local",
|
|
11782
12227
|
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
11783
12228
|
).action(async (opts) => {
|
|
@@ -11860,6 +12305,11 @@ function logoutCommand() {
|
|
|
11860
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."
|
|
11861
12306
|
)
|
|
11862
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
|
+
);
|
|
11863
12313
|
}
|
|
11864
12314
|
});
|
|
11865
12315
|
});
|
|
@@ -11943,12 +12393,12 @@ init_ui();
|
|
|
11943
12393
|
function pullCommand() {
|
|
11944
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) => {
|
|
11945
12395
|
const { brain: source, others } = await resolveSource(opts.brain);
|
|
11946
|
-
const target = resolve8(destination ?? `./${
|
|
12396
|
+
const target = resolve8(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
|
|
11947
12397
|
const first = await cloudListNotes(source, { limit: 1 });
|
|
11948
12398
|
if (first.total === 0) {
|
|
11949
12399
|
throw memgError(
|
|
11950
12400
|
"invalid_input",
|
|
11951
|
-
`\u201C${
|
|
12401
|
+
`\u201C${oneLine(source.name, WIDTH.title)}\u201D has no notes to download`,
|
|
11952
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."
|
|
11953
12403
|
);
|
|
11954
12404
|
}
|
|
@@ -11969,13 +12419,13 @@ function pullCommand() {
|
|
|
11969
12419
|
printHuman(`## pull (dry run)
|
|
11970
12420
|
`);
|
|
11971
12421
|
printHuman(
|
|
11972
|
-
`${first.total} note(s) from \u201C${
|
|
12422
|
+
`${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D into \`${target}\`
|
|
11973
12423
|
`
|
|
11974
12424
|
);
|
|
11975
12425
|
for (const note of preview.slice(0, 20)) {
|
|
11976
12426
|
printHuman(
|
|
11977
12427
|
c.gray(
|
|
11978
|
-
` ${
|
|
12428
|
+
` ${oneLine(note.path, WIDTH.path)}${note.excluded === true ? " (excluded)" : ""}`
|
|
11979
12429
|
)
|
|
11980
12430
|
);
|
|
11981
12431
|
}
|
|
@@ -11990,7 +12440,7 @@ function pullCommand() {
|
|
|
11990
12440
|
});
|
|
11991
12441
|
return;
|
|
11992
12442
|
}
|
|
11993
|
-
progress(`downloading ${first.total} note(s) from \u201C${
|
|
12443
|
+
progress(`downloading ${first.total} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D\u2026`);
|
|
11994
12444
|
const written = [];
|
|
11995
12445
|
const already = [];
|
|
11996
12446
|
const excluded = [];
|
|
@@ -12032,7 +12482,7 @@ function pullCommand() {
|
|
|
12032
12482
|
human: () => {
|
|
12033
12483
|
printHuman(
|
|
12034
12484
|
c.green(
|
|
12035
|
-
`\u2713 ${written.length} note(s) from \u201C${
|
|
12485
|
+
`\u2713 ${written.length} note(s) from \u201C${oneLine(source.name, WIDTH.title)}\u201D written to \`${target}\``
|
|
12036
12486
|
)
|
|
12037
12487
|
);
|
|
12038
12488
|
if (already.length > 0) {
|
|
@@ -12084,8 +12534,8 @@ async function resolveSource(named) {
|
|
|
12084
12534
|
if (!found) {
|
|
12085
12535
|
throw memgError(
|
|
12086
12536
|
"invalid_input",
|
|
12087
|
-
named ? `no hosted brain called \u201C${
|
|
12088
|
-
`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)}\`)`
|
|
12089
12539
|
);
|
|
12090
12540
|
}
|
|
12091
12541
|
return {
|
|
@@ -12102,7 +12552,7 @@ async function resolveSource(named) {
|
|
|
12102
12552
|
}
|
|
12103
12553
|
function sayWhatIsElsewhere(others, into) {
|
|
12104
12554
|
if (others.length === 0) return;
|
|
12105
|
-
const names = others.map(
|
|
12555
|
+
const names = others.map((name) => oneLine(name, WIDTH.title));
|
|
12106
12556
|
printHuman(
|
|
12107
12557
|
c.yellow(
|
|
12108
12558
|
`
|
|
@@ -12147,6 +12597,11 @@ init_api_client();
|
|
|
12147
12597
|
import { Command as Command17 } from "commander";
|
|
12148
12598
|
init_vault();
|
|
12149
12599
|
init_ui();
|
|
12600
|
+
var HELD_BACK_WORDING = {
|
|
12601
|
+
where: "the hosted brain",
|
|
12602
|
+
retry: "push again"
|
|
12603
|
+
};
|
|
12604
|
+
var printHeldBack2 = (held) => printHeldBack(held, HELD_BACK_WORDING);
|
|
12150
12605
|
function pushCommand() {
|
|
12151
12606
|
return new Command17("push").description("upload the brain on this machine to your hosted one").option("--vault <path>", "which local brain to upload").option("--brain <name>", "the hosted brain to upload into (created if it does not exist)").option("--dry-run", "list what would be uploaded and send nothing").action(async (opts) => {
|
|
12152
12607
|
const { brain: local } = await resolveBrain(opts.vault);
|
|
@@ -12155,11 +12610,16 @@ function pushCommand() {
|
|
|
12155
12610
|
const paths = scan.files.filter(isNote);
|
|
12156
12611
|
const held = heldBack(scan);
|
|
12157
12612
|
if (paths.length === 0) {
|
|
12613
|
+
const sawFiles = scan.files.length > 0;
|
|
12614
|
+
const excluded = describeHeldBack(held).slice(0, 10).map((row) => ` ${row}`).join("\n");
|
|
12158
12615
|
throw memgError(
|
|
12159
12616
|
"invalid_input",
|
|
12160
12617
|
`there is nothing to upload in ${local.root}`,
|
|
12161
|
-
held.total
|
|
12162
|
-
|
|
12618
|
+
held.total > 0 ? `This brain has ${held.total} markdown note(s) and the rules on this machine hold back every one:
|
|
12619
|
+
${excluded}
|
|
12620
|
+
Rename one, or list it in .memgallow, if the name is a false alarm.` : held.folders.length > 0 ? `No markdown notes were found. ${held.folders.length} folder(s) were excluded by a rule and never looked inside:
|
|
12621
|
+
${excluded}
|
|
12622
|
+
If your notes are in one of those, that rule is why \u2014 nothing here can see them. If they are somewhere else, check the path: \`--vault\` takes the folder your notes live in.` : sawFiles ? "There are files here but no markdown notes. Check the path \u2014 `--vault` takes the folder your notes live in." : "This folder is empty. Check the path \u2014 `--vault` takes the folder your notes live in."
|
|
12163
12623
|
);
|
|
12164
12624
|
}
|
|
12165
12625
|
if (opts.dryRun) {
|
|
@@ -12179,7 +12639,7 @@ function pushCommand() {
|
|
|
12179
12639
|
`);
|
|
12180
12640
|
for (const path of paths.slice(0, 20)) printHuman(c.gray(` ${path}`));
|
|
12181
12641
|
if (paths.length > 20) printHuman(c.gray(` \u2026 and ${paths.length - 20} more`));
|
|
12182
|
-
|
|
12642
|
+
printHeldBack2(held);
|
|
12183
12643
|
printHuman(
|
|
12184
12644
|
c.gray(
|
|
12185
12645
|
"\nNothing was sent. What a dry run cannot know is the other screen: a note\nwhose CONTENT looks like a credential is held back \u2014 by this machine before\nsending, and by the server on arrival \u2014 and deciding that needs the bytes,\nwhich this does not read. Those appear in the real run\u2019s summary."
|
|
@@ -12190,7 +12650,7 @@ function pushCommand() {
|
|
|
12190
12650
|
return;
|
|
12191
12651
|
}
|
|
12192
12652
|
const destination = await resolveDestination(opts.brain, local.root);
|
|
12193
|
-
progress(`uploading ${paths.length} note(s) to \u201C${
|
|
12653
|
+
progress(`uploading ${paths.length} note(s) to \u201C${oneLine(destination.name, WIDTH.title)}\u201D\u2026`);
|
|
12194
12654
|
const uploaded = [];
|
|
12195
12655
|
const already = [];
|
|
12196
12656
|
const refused = [];
|
|
@@ -12260,7 +12720,9 @@ function pushCommand() {
|
|
|
12260
12720
|
},
|
|
12261
12721
|
human: () => {
|
|
12262
12722
|
printHuman(
|
|
12263
|
-
c.green(
|
|
12723
|
+
c.green(
|
|
12724
|
+
`\u2713 ${uploaded.length} note(s) uploaded to \u201C${oneLine(destination.name, WIDTH.title)}\u201D`
|
|
12725
|
+
)
|
|
12264
12726
|
);
|
|
12265
12727
|
if (already.length > 0) {
|
|
12266
12728
|
printHuman(c.gray(` ${already.length} were already there \u2014 left as they are`));
|
|
@@ -12278,13 +12740,15 @@ function pushCommand() {
|
|
|
12278
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")
|
|
12279
12741
|
);
|
|
12280
12742
|
for (const r of refused.slice(0, 10)) {
|
|
12281
|
-
printHuman(
|
|
12743
|
+
printHuman(
|
|
12744
|
+
c.gray(` ${oneLine(r.path, WIDTH.path)} (${oneLine(r.code, WIDTH.title)})`)
|
|
12745
|
+
);
|
|
12282
12746
|
}
|
|
12283
12747
|
if (refused.length > 10) {
|
|
12284
12748
|
printHuman(c.gray(` \u2026 and ${refused.length - 10} more`));
|
|
12285
12749
|
}
|
|
12286
12750
|
}
|
|
12287
|
-
|
|
12751
|
+
printHeldBack2(held);
|
|
12288
12752
|
printRepoint(repointed, destination);
|
|
12289
12753
|
printHuman(
|
|
12290
12754
|
c.gray(
|
|
@@ -12300,14 +12764,14 @@ Nothing was deleted from \`${local.root}\` \u2014
|
|
|
12300
12764
|
function printRepoint(repoint, destination) {
|
|
12301
12765
|
if (repoint === null) {
|
|
12302
12766
|
printHuman(c.gray(`
|
|
12303
|
-
This machine still reads \u201C${
|
|
12767
|
+
This machine still reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D.`));
|
|
12304
12768
|
return;
|
|
12305
12769
|
}
|
|
12306
12770
|
if (repoint.from === null) {
|
|
12307
12771
|
printHuman(
|
|
12308
12772
|
c.gray(
|
|
12309
12773
|
`
|
|
12310
|
-
This machine now reads \u201C${
|
|
12774
|
+
This machine now reads \u201C${oneLine(destination.name, WIDTH.title)}\u201D \u2014 it was not reading a hosted
|
|
12311
12775
|
brain before. \`memgineering recall --local\` still reads the folder.`
|
|
12312
12776
|
)
|
|
12313
12777
|
);
|
|
@@ -12316,85 +12780,9 @@ brain before. \`memgineering recall --local\` still reads the folder.`
|
|
|
12316
12780
|
printHuman(
|
|
12317
12781
|
c.yellow(
|
|
12318
12782
|
`
|
|
12319
|
-
\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.`
|
|
12320
12784
|
) + c.gray(
|
|
12321
|
-
"\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" +
|
|
12322
|
-
)
|
|
12323
|
-
);
|
|
12324
|
-
}
|
|
12325
|
-
var isNote = (p) => /\.(md|markdown)$/i.test(p);
|
|
12326
|
-
function heldBack(scan) {
|
|
12327
|
-
const denied = scan.denied ?? [];
|
|
12328
|
-
const byName = denied.filter((d) => d.kind === "name" && isNote(d.path)).map((d) => ({ path: d.path, term: d.term }));
|
|
12329
|
-
const explicitly = denied.filter((d) => d.kind === "explicit" && isNote(d.path)).map((d) => d.path);
|
|
12330
|
-
const byRule = scan.excluded.filter(isNote);
|
|
12331
|
-
const symlinked = scan.skippedSymlinks.filter(isNote);
|
|
12332
|
-
return {
|
|
12333
|
-
total: byName.length + explicitly.length + byRule.length + symlinked.length,
|
|
12334
|
-
byName,
|
|
12335
|
-
explicitly,
|
|
12336
|
-
byRule,
|
|
12337
|
-
symlinked
|
|
12338
|
-
};
|
|
12339
|
-
}
|
|
12340
|
-
function heldBackJson(held) {
|
|
12341
|
-
return {
|
|
12342
|
-
not_sent: held.total,
|
|
12343
|
-
denied_by_name: held.byName,
|
|
12344
|
-
denied_explicitly: held.explicitly,
|
|
12345
|
-
excluded_by_rule: held.byRule,
|
|
12346
|
-
skipped_symlinks: held.symlinked
|
|
12347
|
-
};
|
|
12348
|
-
}
|
|
12349
|
-
function heldBackGroups(held) {
|
|
12350
|
-
return [
|
|
12351
|
-
{
|
|
12352
|
-
label: "name looks private",
|
|
12353
|
-
rows: held.byName.map((d) => ({ path: d.path, detail: `matched "${d.term ?? "?"}"` }))
|
|
12354
|
-
},
|
|
12355
|
-
{ label: "listed in .memgdeny", rows: held.explicitly.map((p) => ({ path: p, detail: null })) },
|
|
12356
|
-
{ label: "excluded by a rule", rows: held.byRule.map((p) => ({ path: p, detail: null })) },
|
|
12357
|
-
{
|
|
12358
|
-
label: "a symlink, which is never followed",
|
|
12359
|
-
rows: held.symlinked.map((p) => ({ path: p, detail: null }))
|
|
12360
|
-
}
|
|
12361
|
-
];
|
|
12362
|
-
}
|
|
12363
|
-
function describeHeldBack(held) {
|
|
12364
|
-
return heldBackGroups(held).flatMap(
|
|
12365
|
-
({ label, rows }) => rows.map(
|
|
12366
|
-
(r) => `${stripControl(r.path)} \u2014 ${label}${r.detail === null ? "" : ` (${stripControl(r.detail)})`}`
|
|
12367
|
-
)
|
|
12368
|
-
);
|
|
12369
|
-
}
|
|
12370
|
-
function printHeldBack(held) {
|
|
12371
|
-
if (held.total === 0) return;
|
|
12372
|
-
printHuman(
|
|
12373
|
-
c.yellow(`
|
|
12374
|
-
\u26A0 ${held.total} note(s) held back on this machine \u2014 they never reach the brain:`)
|
|
12375
|
-
);
|
|
12376
|
-
for (const { label, rows } of heldBackGroups(held)) {
|
|
12377
|
-
if (rows.length === 0) continue;
|
|
12378
|
-
printHuman(c.gray(` ${rows.length} \u2014 ${label}:`));
|
|
12379
|
-
for (const r of rows.slice(0, 10)) {
|
|
12380
|
-
printHuman(
|
|
12381
|
-
c.gray(
|
|
12382
|
-
` ${stripControl(r.path)}${r.detail === null ? "" : ` (${stripControl(r.detail)})`}`
|
|
12383
|
-
)
|
|
12384
|
-
);
|
|
12385
|
-
}
|
|
12386
|
-
if (rows.length > 10) printHuman(c.gray(` \u2026 and ${rows.length - 10} more`));
|
|
12387
|
-
}
|
|
12388
|
-
if (held.byName.length > 0) {
|
|
12389
|
-
printHuman(
|
|
12390
|
-
c.gray(
|
|
12391
|
-
" The name list is a GUESS and this may be a false alarm \u2014 rename the note,\n or list it in .memgallow, and push again."
|
|
12392
|
-
)
|
|
12393
|
-
);
|
|
12394
|
-
}
|
|
12395
|
-
printHuman(
|
|
12396
|
-
c.gray(
|
|
12397
|
-
" Counted per file. A rule that excludes a whole FOLDER stops the walk there,\n so notes inside one are not listed above."
|
|
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) + "`."
|
|
12398
12786
|
)
|
|
12399
12787
|
);
|
|
12400
12788
|
}
|
|
@@ -12601,7 +12989,7 @@ function recallCommand() {
|
|
|
12601
12989
|
if (semantic.state !== "skipped" || semantic.hint === void 0) return;
|
|
12602
12990
|
printHuman(
|
|
12603
12991
|
c.yellow("\u26A0 Meaning was not searched \u2014 this answer matched words only.") + c.gray(`
|
|
12604
|
-
${
|
|
12992
|
+
${oneLine(semantic.hint, WIDTH.text)}
|
|
12605
12993
|
`)
|
|
12606
12994
|
);
|
|
12607
12995
|
};
|
|
@@ -12649,13 +13037,11 @@ function recallCommand() {
|
|
|
12649
13037
|
for (const id of conflict.between) {
|
|
12650
13038
|
const reason = reasons.get(id);
|
|
12651
13039
|
if (!reason) {
|
|
12652
|
-
printHuman(c.gray(` ${
|
|
13040
|
+
printHuman(c.gray(` ${oneLine(id, WIDTH.handle)} \u2014 no reason was recorded.`));
|
|
12653
13041
|
continue;
|
|
12654
13042
|
}
|
|
12655
13043
|
printHuman(
|
|
12656
|
-
|
|
12657
|
-
` ${id} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12658
|
-
)
|
|
13044
|
+
` ${oneLine(id, WIDTH.handle)} \u2014 ${reason.verb === "remember" ? "why it was recorded" : "why it last changed"} (${reason.ts.slice(0, 10)}):`
|
|
12659
13045
|
);
|
|
12660
13046
|
printHuman(quoteBlock(reason.text, " > "));
|
|
12661
13047
|
}
|
|
@@ -12671,8 +13057,10 @@ function recallCommand() {
|
|
|
12671
13057
|
);
|
|
12672
13058
|
}
|
|
12673
13059
|
if (result.related.length > 0) {
|
|
12674
|
-
printHuman(
|
|
12675
|
-
`)
|
|
13060
|
+
printHuman(
|
|
13061
|
+
`_related:_ ${result.related.map((id) => oneLine(id, WIDTH.handle)).join(", ")}
|
|
13062
|
+
`
|
|
13063
|
+
);
|
|
12676
13064
|
}
|
|
12677
13065
|
if (result.truncated) {
|
|
12678
13066
|
const { withheld, estimated_tokens, budget_tokens } = result.truncated;
|
|
@@ -12726,9 +13114,8 @@ function asksInSentences(query) {
|
|
|
12726
13114
|
return query.trim().split(/\s+/).filter(Boolean).length >= 4;
|
|
12727
13115
|
}
|
|
12728
13116
|
function side(id, status, validFrom2) {
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
);
|
|
13117
|
+
const date = validFrom2 === null ? "" : ` \xB7 ${oneLine(validFrom2.slice(0, 10), WIDTH.handle)}`;
|
|
13118
|
+
return `${oneLine(id, WIDTH.handle)} (${oneLine(status, WIDTH.title)}${date})`;
|
|
12732
13119
|
}
|
|
12733
13120
|
async function conflictReasons(vaultRoot, conflicts, byId) {
|
|
12734
13121
|
const { ops } = await readOps(vaultRoot);
|
|
@@ -12747,7 +13134,7 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
12747
13134
|
const out = [];
|
|
12748
13135
|
const standing = card.freshness === "current" ? "" : ` \xB7 ${card.freshness}`;
|
|
12749
13136
|
const age = Number.isFinite(card.age_days) ? ` \xB7 ${ago2(Math.floor(card.age_days))}` : "";
|
|
12750
|
-
out.push(`### ${n}. ${
|
|
13137
|
+
out.push(`### ${n}. ${oneLine(card.title, WIDTH.title)}`);
|
|
12751
13138
|
if (scaffolding) {
|
|
12752
13139
|
out.push(c.yellow("not filled in yet") + c.gray(" \u2014 still the template `init` wrote"));
|
|
12753
13140
|
} else {
|
|
@@ -12755,14 +13142,14 @@ function renderCard(n, card, handle2, detail, stage, glossHere = false, scaffold
|
|
|
12755
13142
|
card.binding ? c.bold("a standing decision") : null,
|
|
12756
13143
|
`${card.confidence}${standing}${age}`
|
|
12757
13144
|
].filter(Boolean);
|
|
12758
|
-
out.push(
|
|
13145
|
+
out.push(oneLine(marks.join(c.gray(" \xB7 ")), WIDTH.text));
|
|
12759
13146
|
}
|
|
12760
13147
|
if (glossHere && !scaffolding) {
|
|
12761
13148
|
out.push(c.gray("stated = these fields came from the note, not from inference"));
|
|
12762
13149
|
}
|
|
12763
13150
|
if (detail !== "title") {
|
|
12764
13151
|
if (card.summary) out.push("", quoteBlock(card.summary));
|
|
12765
|
-
out.push("", `\`open: ${
|
|
13152
|
+
out.push("", `\`open: ${oneLine(handle2, WIDTH.handle)}\``);
|
|
12766
13153
|
}
|
|
12767
13154
|
if (stage.outline && stage.outline.length > 0) {
|
|
12768
13155
|
out.push("", ...stage.outline.map((h) => `- ${h}`));
|
|
@@ -12893,10 +13280,10 @@ function rememberCommand() {
|
|
|
12893
13280
|
...kindLanded ? {} : { kind_dropped: kind }
|
|
12894
13281
|
},
|
|
12895
13282
|
human: () => {
|
|
12896
|
-
printHuman(`Remembered in ${
|
|
13283
|
+
printHuman(`Remembered in ${oneLine(target.name, WIDTH.title)}.
|
|
12897
13284
|
`);
|
|
12898
13285
|
printHuman(
|
|
12899
|
-
`note: \`${
|
|
13286
|
+
`note: \`${oneLine(written.id, WIDTH.handle)}\` \xB7 \`${oneLine(written.path, WIDTH.path)}\``
|
|
12900
13287
|
);
|
|
12901
13288
|
if (opts.rule === true) {
|
|
12902
13289
|
printHuman("rule: shown before you write a file here, once a session");
|
|
@@ -12908,7 +13295,9 @@ function rememberCommand() {
|
|
|
12908
13295
|
)
|
|
12909
13296
|
);
|
|
12910
13297
|
}
|
|
12911
|
-
printHuman(
|
|
13298
|
+
printHuman(
|
|
13299
|
+
c.gray(`undo: \`memgineering undo ${oneLine(written.op_id, WIDTH.handle)}\``)
|
|
13300
|
+
);
|
|
12912
13301
|
}
|
|
12913
13302
|
});
|
|
12914
13303
|
return;
|
|
@@ -13109,9 +13498,7 @@ function resurfaceCommand() {
|
|
|
13109
13498
|
`);
|
|
13110
13499
|
if (unreachableDefault) {
|
|
13111
13500
|
printHuman(
|
|
13112
|
-
c.yellow(
|
|
13113
|
-
`Your default brain is not on disk right now: ${unreachableDefault}`
|
|
13114
|
-
)
|
|
13501
|
+
c.yellow(`Your default brain is not on disk right now: ${unreachableDefault}`)
|
|
13115
13502
|
);
|
|
13116
13503
|
printHuman(
|
|
13117
13504
|
c.gray(
|
|
@@ -13125,7 +13512,7 @@ function resurfaceCommand() {
|
|
|
13125
13512
|
);
|
|
13126
13513
|
for (const [i, { entry, why }] of rankedShown.entries()) {
|
|
13127
13514
|
const blank = untouched.has(entry.path);
|
|
13128
|
-
printHuman(`### ${i + 1}. ${
|
|
13515
|
+
printHuman(`### ${i + 1}. ${oneLine(entry.memory.title, WIDTH.title)}`);
|
|
13129
13516
|
printHuman(blank ? c.yellow("not filled in yet") : why.join(" \xB7 "));
|
|
13130
13517
|
if (blank) {
|
|
13131
13518
|
const hasWritten = rankedShown.some((r) => !untouched.has(r.entry.path));
|
|
@@ -13167,31 +13554,31 @@ async function resurfaceHosted(where, limit) {
|
|
|
13167
13554
|
},
|
|
13168
13555
|
human: () => {
|
|
13169
13556
|
if (result.cards.length === 0) {
|
|
13170
|
-
printHuman(`## resurface \u2014 ${
|
|
13557
|
+
printHuman(`## resurface \u2014 ${oneLine(where.name, WIDTH.title)}
|
|
13171
13558
|
`);
|
|
13172
13559
|
printHuman("_Nothing to bring up yet._\n");
|
|
13173
|
-
if (result.hint) printHuman(c.gray(
|
|
13560
|
+
if (result.hint) printHuman(c.gray(oneLine(result.hint, WIDTH.text)));
|
|
13174
13561
|
return;
|
|
13175
13562
|
}
|
|
13176
|
-
printHuman(
|
|
13177
|
-
|
|
13563
|
+
printHuman(
|
|
13564
|
+
`## resurface \u2014 ${oneLine(where.name, WIDTH.title)} (${result.cards.length} cards)
|
|
13565
|
+
`
|
|
13566
|
+
);
|
|
13178
13567
|
for (const [i, card] of result.cards.entries()) {
|
|
13179
|
-
printHuman(`### ${i + 1}. ${
|
|
13568
|
+
printHuman(`### ${i + 1}. ${oneLine(card.title, WIDTH.title)}`);
|
|
13180
13569
|
if (card.untouched === true) printHuman(c.yellow("not filled in yet"));
|
|
13181
|
-
else if (card.why.length > 0) printHuman(
|
|
13570
|
+
else if (card.why.length > 0) printHuman(oneLine(card.why.join(" \xB7 "), WIDTH.text));
|
|
13182
13571
|
if (card.summary) printHuman(`
|
|
13183
13572
|
${quoteBlock(card.summary)}`);
|
|
13184
13573
|
printHuman(`
|
|
13185
|
-
\`open: ${
|
|
13574
|
+
\`open: ${oneLine(card.handle, WIDTH.handle)}\`
|
|
13186
13575
|
`);
|
|
13187
13576
|
}
|
|
13188
|
-
|
|
13577
|
+
printBlock(
|
|
13189
13578
|
c.gray(
|
|
13190
|
-
|
|
13191
|
-
`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
|
|
13192
13580
|
question was asked from, so "in this folder" is not one of them \u2014 that signal
|
|
13193
13581
|
exists only for a brain on this machine.`
|
|
13194
|
-
)
|
|
13195
13582
|
)
|
|
13196
13583
|
);
|
|
13197
13584
|
}
|
|
@@ -13212,7 +13599,7 @@ function rulesCommand() {
|
|
|
13212
13599
|
printDual({
|
|
13213
13600
|
json: { brain: where.name, hosted: true, rules: rules2, total: total2 },
|
|
13214
13601
|
human: () => {
|
|
13215
|
-
printHuman(`## rules \u2014 ${
|
|
13602
|
+
printHuman(`## rules \u2014 ${oneLine(where.name, WIDTH.title)} (${total2})
|
|
13216
13603
|
`);
|
|
13217
13604
|
if (rules2.length === 0) {
|
|
13218
13605
|
printHuman("_Nothing is marked as a rule in this brain._\n");
|
|
@@ -13224,8 +13611,8 @@ function rulesCommand() {
|
|
|
13224
13611
|
return;
|
|
13225
13612
|
}
|
|
13226
13613
|
for (const rule of rules2) {
|
|
13227
|
-
printHuman(`- ${
|
|
13228
|
-
printHuman(c.gray(` \`open: ${
|
|
13614
|
+
printHuman(`- ${oneLine(rule.summary ?? rule.title, WIDTH.text)}`);
|
|
13615
|
+
printHuman(c.gray(` \`open: ${oneLine(rule.handle, WIDTH.handle)}\``));
|
|
13229
13616
|
}
|
|
13230
13617
|
if (total2 > rules2.length) {
|
|
13231
13618
|
printHuman(c.gray(`
|
|
@@ -13277,10 +13664,10 @@ This answered from \`${brainRoot}\` instead, so rules you wrote in the missing o
|
|
|
13277
13664
|
printHuman(`## rules (${rules.length}${total > rules.length ? ` of ${total}` : ""})
|
|
13278
13665
|
`);
|
|
13279
13666
|
for (const { entry } of rules) {
|
|
13280
|
-
printHuman(`- ${
|
|
13667
|
+
printHuman(`- ${oneLine(entry.memory.summary ?? entry.memory.title, WIDTH.text)}`);
|
|
13281
13668
|
printHuman(
|
|
13282
13669
|
c.gray(
|
|
13283
|
-
` \`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)}` : ""}`
|
|
13284
13671
|
)
|
|
13285
13672
|
);
|
|
13286
13673
|
}
|
|
@@ -13642,7 +14029,7 @@ async function reportPreviousAttempt() {
|
|
|
13642
14029
|
function reportNotice(text) {
|
|
13643
14030
|
if (!text) return;
|
|
13644
14031
|
printHuman(c.gray(`
|
|
13645
|
-
note from memgineering.com: "${
|
|
14032
|
+
note from memgineering.com: "${oneLine(text, WIDTH.text)}"`));
|
|
13646
14033
|
}
|
|
13647
14034
|
function reportBelowMinimum(below) {
|
|
13648
14035
|
if (!below) return;
|
|
@@ -13680,11 +14067,11 @@ function undoCommand() {
|
|
|
13680
14067
|
json: { ...undone, brain: { hosted: true, name: where.name } },
|
|
13681
14068
|
human: () => {
|
|
13682
14069
|
printHuman(
|
|
13683
|
-
`Took back \`${
|
|
14070
|
+
`Took back \`${oneLine(undone.undone, WIDTH.handle)}\` \u2014 a ${oneLine(undone.verb, WIDTH.title)}.
|
|
13684
14071
|
`
|
|
13685
14072
|
);
|
|
13686
|
-
printHuman(c.gray(` ${
|
|
13687
|
-
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)}\``));
|
|
13688
14075
|
}
|
|
13689
14076
|
});
|
|
13690
14077
|
return;
|
|
@@ -13770,7 +14157,7 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
13770
14157
|
},
|
|
13771
14158
|
human: () => {
|
|
13772
14159
|
printHuman(
|
|
13773
|
-
`Undone: ${
|
|
14160
|
+
`Undone: ${oneLine(target.verb, WIDTH.title)} on \`${oneLine(target.target, WIDTH.path)}\`
|
|
13774
14161
|
`
|
|
13775
14162
|
);
|
|
13776
14163
|
if (opId === void 0) {
|
|
@@ -13861,13 +14248,91 @@ Read what it says now, then decide what should stay.`;
|
|
|
13861
14248
|
The text from before that change is still on this machine: ${join23(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
13862
14249
|
}
|
|
13863
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
|
+
|
|
13864
14327
|
// src/commands/use.ts
|
|
13865
14328
|
init_atomic();
|
|
13866
14329
|
init_brand();
|
|
13867
14330
|
init_errors();
|
|
13868
|
-
|
|
14331
|
+
init_api_client();
|
|
14332
|
+
init_credentials();
|
|
13869
14333
|
import { isAbsolute as isAbsolute5, relative as relative3, resolve as resolve11 } from "path";
|
|
13870
|
-
import { Command as
|
|
14334
|
+
import { Command as Command26 } from "commander";
|
|
14335
|
+
init_config();
|
|
13871
14336
|
init_vault();
|
|
13872
14337
|
init_ui();
|
|
13873
14338
|
function lostDefaultLines(missing) {
|
|
@@ -13886,16 +14351,24 @@ var VIA_MEANS = {
|
|
|
13886
14351
|
default: "because it is your default brain (`memgineering use --default <path>` changes it)"
|
|
13887
14352
|
};
|
|
13888
14353
|
function useCommand() {
|
|
13889
|
-
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(
|
|
13890
14357
|
"--default",
|
|
13891
|
-
"
|
|
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"
|
|
13892
14359
|
).action(async (brainPath, opts) => {
|
|
14360
|
+
if (opts.brain !== void 0) {
|
|
14361
|
+
await pointAtHosted(opts.brain, opts, brainPath);
|
|
14362
|
+
return;
|
|
14363
|
+
}
|
|
13893
14364
|
const where = await canonicalize(resolve11(opts.here ?? process.cwd()));
|
|
13894
14365
|
if (!brainPath) {
|
|
13895
14366
|
const pointer = await readPointer(where);
|
|
13896
14367
|
const resolved = await resolveBrain(void 0, where).catch((err) => err);
|
|
13897
14368
|
const stuck = resolved instanceof Error;
|
|
13898
14369
|
const hosted = await hostedPointer();
|
|
14370
|
+
const stored = await readCredentials().catch(() => null);
|
|
14371
|
+
const signedIn = stored !== null && stored.api_url === apiUrl();
|
|
13899
14372
|
printDual({
|
|
13900
14373
|
json: {
|
|
13901
14374
|
// The brain that answers `recall` and takes `remember` — the same
|
|
@@ -13930,14 +14403,24 @@ function useCommand() {
|
|
|
13930
14403
|
brain: stuck ? null : resolved.brain.root,
|
|
13931
14404
|
via: stuck ? null : resolved.via,
|
|
13932
14405
|
pointer: pointer ? pointer.from : null,
|
|
13933
|
-
|
|
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 ? {
|
|
13934
14417
|
problem: resolved.code ?? "unresolved",
|
|
13935
14418
|
message: resolved.message
|
|
13936
14419
|
} : {}
|
|
13937
14420
|
},
|
|
13938
14421
|
human: () => {
|
|
13939
14422
|
if (hosted?.signedIn) {
|
|
13940
|
-
printHuman(`Using the hosted brain \`${
|
|
14423
|
+
printHuman(`Using the hosted brain \`${oneLine(hosted.name, NAME_WIDTH2)}\`
|
|
13941
14424
|
`);
|
|
13942
14425
|
printHuman(
|
|
13943
14426
|
c.gray(
|
|
@@ -13958,7 +14441,7 @@ function useCommand() {
|
|
|
13958
14441
|
);
|
|
13959
14442
|
printHuman(
|
|
13960
14443
|
c.yellow(
|
|
13961
|
-
` A sign-in to \`${
|
|
14444
|
+
` A sign-in to \`${oneLine(hosted.name, NAME_WIDTH2)}\` is waiting to be approved.`
|
|
13962
14445
|
)
|
|
13963
14446
|
);
|
|
13964
14447
|
printHuman(
|
|
@@ -13975,7 +14458,9 @@ function useCommand() {
|
|
|
13975
14458
|
printHuman(`No brain resolves here.
|
|
13976
14459
|
`);
|
|
13977
14460
|
printHuman(
|
|
13978
|
-
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.")
|
|
13979
14464
|
);
|
|
13980
14465
|
return;
|
|
13981
14466
|
}
|
|
@@ -13984,7 +14469,7 @@ function useCommand() {
|
|
|
13984
14469
|
printHuman(c.gray(` ${VIA_MEANS[resolved.via]}`));
|
|
13985
14470
|
printHuman(
|
|
13986
14471
|
c.yellow(
|
|
13987
|
-
` 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,`
|
|
13988
14473
|
) + c.gray("\n so reads and writes are landing here instead.")
|
|
13989
14474
|
);
|
|
13990
14475
|
printHuman(
|
|
@@ -13995,6 +14480,14 @@ function useCommand() {
|
|
|
13995
14480
|
if (stuck) {
|
|
13996
14481
|
printHuman(`No brain resolves here.
|
|
13997
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
|
+
}
|
|
13998
14491
|
printHuman(c.gray(` ${resolved.message}`));
|
|
13999
14492
|
const hint = resolved.hint;
|
|
14000
14493
|
if (hint) printHuman(c.gray(`
|
|
@@ -14061,13 +14554,114 @@ A pointer to an unlinked folder would fail on every command instead of at this o
|
|
|
14061
14554
|
});
|
|
14062
14555
|
});
|
|
14063
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
|
+
}
|
|
14064
14658
|
|
|
14065
14659
|
// src/commands/whoami.ts
|
|
14066
14660
|
init_api_client();
|
|
14067
14661
|
init_ui();
|
|
14068
|
-
import { Command as
|
|
14662
|
+
import { Command as Command27 } from "commander";
|
|
14069
14663
|
function whoamiCommand() {
|
|
14070
|
-
return new
|
|
14664
|
+
return new Command27("whoami").description("show which account this machine is signed in as").action(async () => {
|
|
14071
14665
|
const base = apiUrl();
|
|
14072
14666
|
const credentials = await requireCredentials(base);
|
|
14073
14667
|
const res = await apiRequest({
|
|
@@ -14093,9 +14687,11 @@ function whoamiCommand() {
|
|
|
14093
14687
|
},
|
|
14094
14688
|
human: () => {
|
|
14095
14689
|
printHuman(
|
|
14096
|
-
c.bold(
|
|
14690
|
+
c.bold(
|
|
14691
|
+
oneLine(account.display_name ?? account.id ?? credentials.account_id, WIDTH.title)
|
|
14692
|
+
)
|
|
14097
14693
|
);
|
|
14098
|
-
if (account.email) printHuman(c.gray(` ${
|
|
14694
|
+
if (account.email) printHuman(c.gray(` ${oneLine(account.email, WIDTH.title)}`));
|
|
14099
14695
|
printHuman(c.gray(` on ${base}`));
|
|
14100
14696
|
if (live !== null) {
|
|
14101
14697
|
printHuman(
|
|
@@ -14110,6 +14706,8 @@ function whoamiCommand() {
|
|
|
14110
14706
|
}
|
|
14111
14707
|
|
|
14112
14708
|
// src/lib/next-step.ts
|
|
14709
|
+
init_api_client();
|
|
14710
|
+
init_credentials();
|
|
14113
14711
|
init_config();
|
|
14114
14712
|
function isSetUp(cfg) {
|
|
14115
14713
|
return Object.keys(cfg.system.installed).length > 0;
|
|
@@ -14163,7 +14761,16 @@ Then run \`memgineering onboard\`: it prints what to ask so the brain knows whos
|
|
|
14163
14761
|
it is. A brain whose \`01_BASE/\` is still template text answers almost nothing.
|
|
14164
14762
|
|
|
14165
14763
|
Read \`memgineering-setup\` for accounts and which of their tools this reaches.`;
|
|
14166
|
-
|
|
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;
|
|
14167
14774
|
if (!isSetUp(cfg)) return { id: "finish_setup", markdown: FINISH_SETUP };
|
|
14168
14775
|
if (cfg.brain.brains.length === 0 && cfg.brain.cloud === void 0) {
|
|
14169
14776
|
return { id: "connect_a_brain", markdown: CONNECT_A_BRAIN };
|
|
@@ -14172,7 +14779,8 @@ function nextStepFor(cfg) {
|
|
|
14172
14779
|
}
|
|
14173
14780
|
async function currentNextStep() {
|
|
14174
14781
|
try {
|
|
14175
|
-
|
|
14782
|
+
const stored = await readCredentials().catch(() => null);
|
|
14783
|
+
return nextStepFor(await loadConfig(), stored !== null && stored.api_url === apiUrl());
|
|
14176
14784
|
} catch {
|
|
14177
14785
|
return null;
|
|
14178
14786
|
}
|
|
@@ -14182,7 +14790,7 @@ async function currentNextStep() {
|
|
|
14182
14790
|
init_ui();
|
|
14183
14791
|
init_brand();
|
|
14184
14792
|
function buildProgram() {
|
|
14185
|
-
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) => {
|
|
14186
14794
|
const opts = thisCommand.optsWithGlobals();
|
|
14187
14795
|
if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
|
|
14188
14796
|
await announceNextStep(actionCommand.name());
|
|
@@ -14203,6 +14811,7 @@ function buildProgram() {
|
|
|
14203
14811
|
program.addCommand(initCommand());
|
|
14204
14812
|
program.addCommand(linkCommand());
|
|
14205
14813
|
program.addCommand(useCommand());
|
|
14814
|
+
program.addCommand(brainsCommand());
|
|
14206
14815
|
program.addCommand(unlinkCommand());
|
|
14207
14816
|
program.addCommand(reindexCommand());
|
|
14208
14817
|
program.addCommand(syncRulesCommand());
|
|
@@ -14242,8 +14851,15 @@ function makeParseErrorsActionable(program) {
|
|
|
14242
14851
|
outputError: (str, write) => {
|
|
14243
14852
|
write(str);
|
|
14244
14853
|
if (!/unknown option|unknown command|too many arguments/i.test(str)) return;
|
|
14245
|
-
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
14246
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);
|
|
14247
14863
|
write(
|
|
14248
14864
|
flags.length > 0 ? `hint: ${path} takes ${flags.join(" ")}
|
|
14249
14865
|
` : `hint: \`${path} --help\` lists what this takes.
|
|
@@ -14271,7 +14887,7 @@ async function handleError(err) {
|
|
|
14271
14887
|
if (isJsonMode()) {
|
|
14272
14888
|
printJson({ error: { code: err.code, message: err.message, hint: err.hint } });
|
|
14273
14889
|
} else {
|
|
14274
|
-
printHuman(c.red(`\u2717 ${
|
|
14890
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14275
14891
|
for (const line of stripControl(err.hint).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14276
14892
|
}
|
|
14277
14893
|
process.exit(1);
|
|
@@ -14288,7 +14904,7 @@ async function handleError(err) {
|
|
|
14288
14904
|
}
|
|
14289
14905
|
});
|
|
14290
14906
|
} else {
|
|
14291
|
-
printHuman(c.red(`\u2717 ${
|
|
14907
|
+
printHuman(c.red(`\u2717 ${oneLine(err.message, WIDTH.text)}`));
|
|
14292
14908
|
if (hintStr) {
|
|
14293
14909
|
for (const line of stripControl(hintStr).split("\n")) printHuman(c.gray(` \u2192 ${line}`));
|
|
14294
14910
|
}
|
|
@@ -14296,7 +14912,7 @@ async function handleError(err) {
|
|
|
14296
14912
|
process.exit(1);
|
|
14297
14913
|
}
|
|
14298
14914
|
if (isJsonMode()) printJson({ error: { code: "generic_failure", message: String(err) } });
|
|
14299
|
-
else printHuman(c.red(`\u2717 ${
|
|
14915
|
+
else printHuman(c.red(`\u2717 ${oneLine(String(err), WIDTH.text)}`));
|
|
14300
14916
|
process.exit(1);
|
|
14301
14917
|
}
|
|
14302
14918
|
|