memgineering 0.26.0 → 0.28.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 +237 -1
- package/README.md +11 -0
- package/assets/MEMGINEERING.md +4 -4
- package/assets/memgineering-recall/SKILL.md +64 -10
- package/assets/memgineering-setup/SKILL.md +92 -16
- package/dist/index.js +1903 -792
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -4971,9 +4971,112 @@ var init_version = __esm({
|
|
|
4971
4971
|
}
|
|
4972
4972
|
});
|
|
4973
4973
|
|
|
4974
|
+
// ../../packages/storage-adapter/src/local-file/atomic.ts
|
|
4975
|
+
import { rename as rename2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
4976
|
+
async function writeThenRename(abs, content) {
|
|
4977
|
+
const tmp = `${abs}.${process.pid}.tmp`;
|
|
4978
|
+
try {
|
|
4979
|
+
await writeFile2(tmp, content, { encoding: "utf8", mode: 384 });
|
|
4980
|
+
await rename2(tmp, abs);
|
|
4981
|
+
} catch (err) {
|
|
4982
|
+
await rm3(tmp, { force: true });
|
|
4983
|
+
throw err;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
var init_atomic = __esm({
|
|
4987
|
+
"../../packages/storage-adapter/src/local-file/atomic.ts"() {
|
|
4988
|
+
"use strict";
|
|
4989
|
+
}
|
|
4990
|
+
});
|
|
4991
|
+
|
|
4992
|
+
// src/lib/path-lock.ts
|
|
4993
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4994
|
+
import { readdir as readdir2, readFile as readFile3, stat, unlink, writeFile as writeFile3 } from "fs/promises";
|
|
4995
|
+
import { basename, dirname as dirname2, join as join5 } from "path";
|
|
4996
|
+
async function lockHolder(path) {
|
|
4997
|
+
try {
|
|
4998
|
+
const [info, token] = await Promise.all([stat(path), readFile3(path, "utf8")]);
|
|
4999
|
+
return { token, ageMs: Date.now() - info.mtimeMs };
|
|
5000
|
+
} catch {
|
|
5001
|
+
return null;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
async function unlinkIfStill(path, token) {
|
|
5005
|
+
const now = await readFile3(path, "utf8").catch(() => null);
|
|
5006
|
+
if (now === token) await unlink(path).catch(() => void 0);
|
|
5007
|
+
}
|
|
5008
|
+
async function takeOver(path, dead) {
|
|
5009
|
+
const takeoverPath = `${path}.takeover`;
|
|
5010
|
+
try {
|
|
5011
|
+
await writeFile3(takeoverPath, "", { flag: "wx", mode: 384 });
|
|
5012
|
+
} catch (err) {
|
|
5013
|
+
if (err.code === "EEXIST") {
|
|
5014
|
+
const age = await stat(takeoverPath).then(
|
|
5015
|
+
(info) => Date.now() - info.mtimeMs,
|
|
5016
|
+
() => null
|
|
5017
|
+
);
|
|
5018
|
+
if (age !== null && age > STALE_LOCK_MS) await unlink(takeoverPath).catch(() => void 0);
|
|
5019
|
+
}
|
|
5020
|
+
return false;
|
|
5021
|
+
}
|
|
5022
|
+
try {
|
|
5023
|
+
const held = await lockHolder(path);
|
|
5024
|
+
if (held !== null && held.token === dead && held.ageMs > STALE_LOCK_MS) {
|
|
5025
|
+
await unlink(path).catch(() => void 0);
|
|
5026
|
+
}
|
|
5027
|
+
return true;
|
|
5028
|
+
} finally {
|
|
5029
|
+
await unlink(takeoverPath).catch(() => void 0);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
async function withPathLock(path, fn, waitMs = 0) {
|
|
5033
|
+
const token = `${process.pid}-${randomUUID2()}`;
|
|
5034
|
+
const deadline = Date.now() + waitMs;
|
|
5035
|
+
for (let vanished = 0; ; ) {
|
|
5036
|
+
try {
|
|
5037
|
+
await writeFile3(path, token, { flag: "wx", mode: 384 });
|
|
5038
|
+
break;
|
|
5039
|
+
} catch (err) {
|
|
5040
|
+
if (err.code !== "EEXIST") return null;
|
|
5041
|
+
}
|
|
5042
|
+
const held = await lockHolder(path);
|
|
5043
|
+
if (held === null) {
|
|
5044
|
+
vanished += 1;
|
|
5045
|
+
if (vanished > 20) return null;
|
|
5046
|
+
continue;
|
|
5047
|
+
}
|
|
5048
|
+
if (held.ageMs > STALE_LOCK_MS && await takeOver(path, held.token)) continue;
|
|
5049
|
+
if (Date.now() >= deadline) return null;
|
|
5050
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
5051
|
+
}
|
|
5052
|
+
try {
|
|
5053
|
+
return await fn();
|
|
5054
|
+
} finally {
|
|
5055
|
+
await unlinkIfStill(path, token);
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
async function clearStaleTemps(path) {
|
|
5059
|
+
const dir = dirname2(path);
|
|
5060
|
+
const prefix = `${basename(path)}.`;
|
|
5061
|
+
const names = await readdir2(dir).catch(() => []);
|
|
5062
|
+
await Promise.all(
|
|
5063
|
+
names.filter((n) => n.startsWith(prefix) && /\.\d+\.tmp$/.test(n)).map(async (n) => {
|
|
5064
|
+
const age = await stat(join5(dir, n)).then((s) => Date.now() - s.mtimeMs).catch(() => 0);
|
|
5065
|
+
if (age > STALE_LOCK_MS) await unlink(join5(dir, n)).catch(() => void 0);
|
|
5066
|
+
})
|
|
5067
|
+
);
|
|
5068
|
+
}
|
|
5069
|
+
var STALE_LOCK_MS;
|
|
5070
|
+
var init_path_lock = __esm({
|
|
5071
|
+
"src/lib/path-lock.ts"() {
|
|
5072
|
+
"use strict";
|
|
5073
|
+
STALE_LOCK_MS = 3e4;
|
|
5074
|
+
}
|
|
5075
|
+
});
|
|
5076
|
+
|
|
4974
5077
|
// src/lib/credentials.ts
|
|
4975
|
-
import { mkdir as mkdir3, readFile as
|
|
4976
|
-
import { join as
|
|
5078
|
+
import { mkdir as mkdir3, readFile as readFile4, unlink as unlink2 } from "fs/promises";
|
|
5079
|
+
import { join as join6 } from "path";
|
|
4977
5080
|
import { z as z6 } from "zod";
|
|
4978
5081
|
function registryFromClaim(raw) {
|
|
4979
5082
|
const parsed = RegistryCredentialsSchema.omit({ linked_at: true }).safeParse(raw);
|
|
@@ -4981,34 +5084,53 @@ function registryFromClaim(raw) {
|
|
|
4981
5084
|
return { ...parsed.data, linked_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4982
5085
|
}
|
|
4983
5086
|
function credentialsPath() {
|
|
4984
|
-
return
|
|
5087
|
+
return join6(brandHome(), "credentials.json");
|
|
4985
5088
|
}
|
|
4986
5089
|
async function readCredentials() {
|
|
4987
5090
|
try {
|
|
4988
5091
|
const parsed = CredentialsSchema.safeParse(
|
|
4989
|
-
JSON.parse(await
|
|
5092
|
+
JSON.parse(await readFile4(credentialsPath(), "utf8"))
|
|
4990
5093
|
);
|
|
4991
5094
|
return parsed.success ? parsed.data : null;
|
|
4992
5095
|
} catch {
|
|
4993
5096
|
return null;
|
|
4994
5097
|
}
|
|
4995
5098
|
}
|
|
5099
|
+
async function underLock(change) {
|
|
5100
|
+
const held = await withPathLock(
|
|
5101
|
+
join6(brandHome(), "credentials.lock"),
|
|
5102
|
+
async () => ({ result: await change() }),
|
|
5103
|
+
LOCK_WAIT_MS
|
|
5104
|
+
);
|
|
5105
|
+
return held === null ? change() : held.result;
|
|
5106
|
+
}
|
|
5107
|
+
async function writeWhole(credentials) {
|
|
5108
|
+
await clearStaleTemps(credentialsPath());
|
|
5109
|
+
await writeThenRename(credentialsPath(), `${JSON.stringify(credentials, null, 2)}
|
|
5110
|
+
`);
|
|
5111
|
+
}
|
|
4996
5112
|
async function saveCredentials(credentials) {
|
|
4997
5113
|
await mkdir3(brandHome(), { recursive: true });
|
|
4998
|
-
await
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5114
|
+
await underLock(() => writeWhole(credentials));
|
|
5115
|
+
}
|
|
5116
|
+
async function saveRotatedRegistry(sent, rotated) {
|
|
5117
|
+
return underLock(async () => {
|
|
5118
|
+
const stored = await readCredentials();
|
|
5119
|
+
if (stored?.registry === void 0 || stored.registry.refresh_token !== sent) return false;
|
|
5120
|
+
await writeWhole({ ...stored, registry: { ...stored.registry, ...rotated } });
|
|
5121
|
+
return true;
|
|
5002
5122
|
});
|
|
5003
5123
|
}
|
|
5004
5124
|
async function clearCredentials() {
|
|
5005
|
-
await
|
|
5125
|
+
await underLock(() => unlink2(credentialsPath()).catch(() => void 0));
|
|
5006
5126
|
}
|
|
5007
|
-
var RegistryCredentialsSchema, CredentialsSchema;
|
|
5127
|
+
var RegistryCredentialsSchema, CredentialsSchema, LOCK_WAIT_MS;
|
|
5008
5128
|
var init_credentials = __esm({
|
|
5009
5129
|
"src/lib/credentials.ts"() {
|
|
5010
5130
|
"use strict";
|
|
5131
|
+
init_atomic();
|
|
5011
5132
|
init_brand();
|
|
5133
|
+
init_path_lock();
|
|
5012
5134
|
RegistryCredentialsSchema = z6.object({
|
|
5013
5135
|
api_url: z6.string().min(1),
|
|
5014
5136
|
refresh_token: z6.string().min(1),
|
|
@@ -5032,20 +5154,21 @@ var init_credentials = __esm({
|
|
|
5032
5154
|
*/
|
|
5033
5155
|
registry: RegistryCredentialsSchema.optional().catch(void 0)
|
|
5034
5156
|
});
|
|
5157
|
+
LOCK_WAIT_MS = 2e3;
|
|
5035
5158
|
}
|
|
5036
5159
|
});
|
|
5037
5160
|
|
|
5038
5161
|
// src/lib/pending-login.ts
|
|
5039
|
-
import { mkdir as mkdir4, readFile as
|
|
5040
|
-
import { join as
|
|
5162
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink3, writeFile as writeFile4 } from "fs/promises";
|
|
5163
|
+
import { join as join7 } from "path";
|
|
5041
5164
|
import { z as z7 } from "zod";
|
|
5042
5165
|
function pendingLoginPath() {
|
|
5043
|
-
return
|
|
5166
|
+
return join7(brandHome(), "pending-login.json");
|
|
5044
5167
|
}
|
|
5045
5168
|
async function readPendingLogin() {
|
|
5046
5169
|
try {
|
|
5047
5170
|
const parsed = PendingLoginSchema.safeParse(
|
|
5048
|
-
JSON.parse(await
|
|
5171
|
+
JSON.parse(await readFile5(pendingLoginPath(), "utf8"))
|
|
5049
5172
|
);
|
|
5050
5173
|
return parsed.success ? parsed.data : null;
|
|
5051
5174
|
} catch {
|
|
@@ -5054,14 +5177,14 @@ async function readPendingLogin() {
|
|
|
5054
5177
|
}
|
|
5055
5178
|
async function savePendingLogin(pending) {
|
|
5056
5179
|
await mkdir4(brandHome(), { recursive: true });
|
|
5057
|
-
await
|
|
5058
|
-
await
|
|
5180
|
+
await unlink3(pendingLoginPath()).catch(() => void 0);
|
|
5181
|
+
await writeFile4(pendingLoginPath(), JSON.stringify(pending, null, 2) + "\n", {
|
|
5059
5182
|
encoding: "utf8",
|
|
5060
5183
|
mode: 384
|
|
5061
5184
|
});
|
|
5062
5185
|
}
|
|
5063
5186
|
async function clearPendingLogin() {
|
|
5064
|
-
await
|
|
5187
|
+
await unlink3(pendingLoginPath()).catch(() => void 0);
|
|
5065
5188
|
}
|
|
5066
5189
|
function pendingExpired(pending, now = Date.now()) {
|
|
5067
5190
|
return Math.floor(now / 1e3) >= pending.expires_at_epoch;
|
|
@@ -5329,27 +5452,9 @@ var init_api_client = __esm({
|
|
|
5329
5452
|
}
|
|
5330
5453
|
});
|
|
5331
5454
|
|
|
5332
|
-
// ../../packages/storage-adapter/src/local-file/atomic.ts
|
|
5333
|
-
import { rename as rename2, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
5334
|
-
async function writeThenRename(abs, content) {
|
|
5335
|
-
const tmp = `${abs}.${process.pid}.tmp`;
|
|
5336
|
-
try {
|
|
5337
|
-
await writeFile4(tmp, content, { encoding: "utf8", mode: 384 });
|
|
5338
|
-
await rename2(tmp, abs);
|
|
5339
|
-
} catch (err) {
|
|
5340
|
-
await rm3(tmp, { force: true });
|
|
5341
|
-
throw err;
|
|
5342
|
-
}
|
|
5343
|
-
}
|
|
5344
|
-
var init_atomic = __esm({
|
|
5345
|
-
"../../packages/storage-adapter/src/local-file/atomic.ts"() {
|
|
5346
|
-
"use strict";
|
|
5347
|
-
}
|
|
5348
|
-
});
|
|
5349
|
-
|
|
5350
5455
|
// src/lib/config.ts
|
|
5351
|
-
import { mkdir as mkdir5, readFile as
|
|
5352
|
-
import { join as
|
|
5456
|
+
import { mkdir as mkdir5, readFile as readFile6 } from "fs/promises";
|
|
5457
|
+
import { join as join8, resolve } from "path";
|
|
5353
5458
|
import { z as z8 } from "zod";
|
|
5354
5459
|
function withDefaultBrain(brain) {
|
|
5355
5460
|
if (brain.default) return brain;
|
|
@@ -5357,11 +5462,11 @@ function withDefaultBrain(brain) {
|
|
|
5357
5462
|
return oldest ? { ...brain, default: oldest.root } : brain;
|
|
5358
5463
|
}
|
|
5359
5464
|
function configPath() {
|
|
5360
|
-
return
|
|
5465
|
+
return join8(brandHome(), "config.json");
|
|
5361
5466
|
}
|
|
5362
5467
|
async function loadConfig() {
|
|
5363
5468
|
try {
|
|
5364
|
-
const parsed = ConfigSchema.safeParse(JSON.parse(await
|
|
5469
|
+
const parsed = ConfigSchema.safeParse(JSON.parse(await readFile6(configPath(), "utf8")));
|
|
5365
5470
|
return parsed.success ? parsed.data : EMPTY_CONFIG;
|
|
5366
5471
|
} catch {
|
|
5367
5472
|
return EMPTY_CONFIG;
|
|
@@ -5745,8 +5850,8 @@ var init_cloud_brain = __esm({
|
|
|
5745
5850
|
});
|
|
5746
5851
|
|
|
5747
5852
|
// ../../packages/storage-adapter/src/local-file/walk.ts
|
|
5748
|
-
import { lstat, readdir as
|
|
5749
|
-
import { join as
|
|
5853
|
+
import { lstat, readdir as readdir4, realpath } from "fs/promises";
|
|
5854
|
+
import { join as join10, relative, resolve as resolve2, sep } from "path";
|
|
5750
5855
|
async function walkVault(root, opts = {}) {
|
|
5751
5856
|
const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
5752
5857
|
const rootAbs = resolve2(root);
|
|
@@ -5758,7 +5863,7 @@ async function walkVault(root, opts = {}) {
|
|
|
5758
5863
|
if (depth > maxDepth) return;
|
|
5759
5864
|
let entries;
|
|
5760
5865
|
try {
|
|
5761
|
-
entries = await
|
|
5866
|
+
entries = await readdir4(dirAbs, { withFileTypes: true });
|
|
5762
5867
|
} catch {
|
|
5763
5868
|
return;
|
|
5764
5869
|
}
|
|
@@ -5775,7 +5880,7 @@ async function walkVault(root, opts = {}) {
|
|
|
5775
5880
|
continue;
|
|
5776
5881
|
}
|
|
5777
5882
|
if (isDir) {
|
|
5778
|
-
await visit(
|
|
5883
|
+
await visit(join10(dirAbs, entry.name), rel, depth + 1);
|
|
5779
5884
|
} else if (entry.isFile()) {
|
|
5780
5885
|
files2.push(rel);
|
|
5781
5886
|
}
|
|
@@ -5806,7 +5911,7 @@ async function resolveRealInside(root, relPath, seenDirs) {
|
|
|
5806
5911
|
const segments = relativeTo(rootAbs, abs).split("/");
|
|
5807
5912
|
let cursor = rootAbs;
|
|
5808
5913
|
for (const [i, segment] of segments.entries()) {
|
|
5809
|
-
cursor =
|
|
5914
|
+
cursor = join10(cursor, segment);
|
|
5810
5915
|
const isLast = i === segments.length - 1;
|
|
5811
5916
|
if (!isLast && seenDirs?.has(cursor)) continue;
|
|
5812
5917
|
const st = await lstat(cursor);
|
|
@@ -5830,7 +5935,7 @@ var init_walk = __esm({
|
|
|
5830
5935
|
});
|
|
5831
5936
|
|
|
5832
5937
|
// ../../packages/storage-adapter/src/local-file/rules-file.ts
|
|
5833
|
-
import { readFile as
|
|
5938
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5834
5939
|
import { parse as parseYaml3 } from "yaml";
|
|
5835
5940
|
function requireStringList(value, key2) {
|
|
5836
5941
|
if (value === void 0 || value === null) return [];
|
|
@@ -5869,7 +5974,7 @@ function ignoreSetFrom(entries) {
|
|
|
5869
5974
|
async function readRulesFile(root) {
|
|
5870
5975
|
let raw;
|
|
5871
5976
|
try {
|
|
5872
|
-
raw = await
|
|
5977
|
+
raw = await readFile8(resolveInside(root, RULES_FILE), "utf8");
|
|
5873
5978
|
} catch (err) {
|
|
5874
5979
|
if (err?.code === "ENOENT") return EMPTY_VAULT_RULES;
|
|
5875
5980
|
throw new RulesFileError(
|
|
@@ -5975,7 +6080,7 @@ edit.
|
|
|
5975
6080
|
|
|
5976
6081
|
// ../../packages/storage-adapter/src/local-file/adapter.ts
|
|
5977
6082
|
import { createHash as createHash4 } from "crypto";
|
|
5978
|
-
import { lstat as lstat2, readFile as
|
|
6083
|
+
import { lstat as lstat2, readFile as readFile9 } from "fs/promises";
|
|
5979
6084
|
var LocalFileReadAdapter;
|
|
5980
6085
|
var init_adapter = __esm({
|
|
5981
6086
|
"../../packages/storage-adapter/src/local-file/adapter.ts"() {
|
|
@@ -6095,7 +6200,7 @@ var init_adapter = __esm({
|
|
|
6095
6200
|
/** Read `.memgdeny` / `.memgallow`. Missing is fine; unreadable is not. */
|
|
6096
6201
|
static async readDenyFile(root, name) {
|
|
6097
6202
|
try {
|
|
6098
|
-
return parseDenyFile(await
|
|
6203
|
+
return parseDenyFile(await readFile9(resolveInside(root, name), "utf8"));
|
|
6099
6204
|
} catch (err) {
|
|
6100
6205
|
if (err?.code === "ENOENT") return EMPTY_DENY_LIST;
|
|
6101
6206
|
throw err;
|
|
@@ -6114,7 +6219,7 @@ var init_adapter = __esm({
|
|
|
6114
6219
|
static async readIgnoreFiles(root) {
|
|
6115
6220
|
const load = async (name) => {
|
|
6116
6221
|
try {
|
|
6117
|
-
return parseIgnoreFile(await
|
|
6222
|
+
return parseIgnoreFile(await readFile9(resolveInside(root, name), "utf8"));
|
|
6118
6223
|
} catch (err) {
|
|
6119
6224
|
if (err?.code === "ENOENT") return EMPTY_IGNORE_SET;
|
|
6120
6225
|
throw err;
|
|
@@ -6150,7 +6255,7 @@ var init_adapter = __esm({
|
|
|
6150
6255
|
}
|
|
6151
6256
|
const st = await lstat2(abs);
|
|
6152
6257
|
if (!st.isFile()) throw new Error(`not a regular file: ${path}`);
|
|
6153
|
-
const bytes = await
|
|
6258
|
+
const bytes = await readFile9(abs);
|
|
6154
6259
|
if (this.#protect) {
|
|
6155
6260
|
let hits = secretContentHits(bytes.toString("utf8"));
|
|
6156
6261
|
if (isExactlyListed(rel, this.#allow)) {
|
|
@@ -6258,8 +6363,8 @@ var init_adapter = __esm({
|
|
|
6258
6363
|
});
|
|
6259
6364
|
|
|
6260
6365
|
// src/lib/vault.ts
|
|
6261
|
-
import { readFile as
|
|
6262
|
-
import { dirname as
|
|
6366
|
+
import { readFile as readFile10, realpath as realpath2, stat as stat2 } from "fs/promises";
|
|
6367
|
+
import { dirname as dirname3, isAbsolute, join as join11, resolve as resolve3 } from "path";
|
|
6263
6368
|
async function resolveBrain(explicit, cwd = process.cwd()) {
|
|
6264
6369
|
const cfg = await loadConfig();
|
|
6265
6370
|
const brains = cfg.brain.brains;
|
|
@@ -6324,7 +6429,7 @@ A folder marked (missing) is not on disk right now. If it is gone for good, \`me
|
|
|
6324
6429
|
}
|
|
6325
6430
|
async function liveBrains(brains) {
|
|
6326
6431
|
const checked = await Promise.all(
|
|
6327
|
-
brains.map(async (b) => (await
|
|
6432
|
+
brains.map(async (b) => (await stat2(b.root).catch(() => null))?.isDirectory() ? b : null)
|
|
6328
6433
|
);
|
|
6329
6434
|
return checked.filter((b) => b !== null);
|
|
6330
6435
|
}
|
|
@@ -6339,9 +6444,9 @@ function isInside(child, parent) {
|
|
|
6339
6444
|
async function readPointer(cwd) {
|
|
6340
6445
|
let dir = resolve3(cwd);
|
|
6341
6446
|
for (; ; ) {
|
|
6342
|
-
const candidate =
|
|
6447
|
+
const candidate = join11(dir, BRAND.pointerFileName);
|
|
6343
6448
|
try {
|
|
6344
|
-
const raw = (await
|
|
6449
|
+
const raw = (await readFile10(candidate, "utf8")).trim();
|
|
6345
6450
|
const target = raw.split("\n")[0]?.trim();
|
|
6346
6451
|
if (target) {
|
|
6347
6452
|
return {
|
|
@@ -6351,7 +6456,7 @@ async function readPointer(cwd) {
|
|
|
6351
6456
|
}
|
|
6352
6457
|
} catch {
|
|
6353
6458
|
}
|
|
6354
|
-
const parent =
|
|
6459
|
+
const parent = dirname3(dir);
|
|
6355
6460
|
if (parent === dir) return null;
|
|
6356
6461
|
dir = parent;
|
|
6357
6462
|
}
|
|
@@ -6408,7 +6513,8 @@ function heldBack(scan) {
|
|
|
6408
6513
|
byName,
|
|
6409
6514
|
explicitly,
|
|
6410
6515
|
byRule,
|
|
6411
|
-
symlinked
|
|
6516
|
+
symlinked,
|
|
6517
|
+
otherSymlinks: scan.skippedSymlinks.filter((path) => !isNote(path))
|
|
6412
6518
|
};
|
|
6413
6519
|
}
|
|
6414
6520
|
function heldBackJson(held, totalKey = "not_sent") {
|
|
@@ -6425,7 +6531,9 @@ function heldBackJson(held, totalKey = "not_sent") {
|
|
|
6425
6531
|
// Present so a zero total can be read correctly. A non-empty list here means
|
|
6426
6532
|
// the count above is a floor, not the answer — nothing inside these was
|
|
6427
6533
|
// walked, so nobody knows how many notes they hold.
|
|
6428
|
-
excluded_folders: held.folders
|
|
6534
|
+
excluded_folders: held.folders,
|
|
6535
|
+
// The same floor for links: a symlinked folder of notes was never walked.
|
|
6536
|
+
skipped_other_symlinks: held.otherSymlinks
|
|
6429
6537
|
};
|
|
6430
6538
|
}
|
|
6431
6539
|
function heldBackGroups(held) {
|
|
@@ -6451,11 +6559,14 @@ function describeHeldBack(held) {
|
|
|
6451
6559
|
),
|
|
6452
6560
|
...held.folders.map(
|
|
6453
6561
|
(dir) => `${oneLine(dir, WIDTH.path)}/ \u2014 an excluded folder, whose contents were never walked`
|
|
6562
|
+
),
|
|
6563
|
+
...held.otherSymlinks.map(
|
|
6564
|
+
(path) => `${oneLine(path, WIDTH.path)} \u2014 a symlink, never followed; a folder behind it was never walked`
|
|
6454
6565
|
)
|
|
6455
6566
|
];
|
|
6456
6567
|
}
|
|
6457
6568
|
function printHeldBack(held, wording) {
|
|
6458
|
-
if (held.total === 0 && held.folders.length === 0) return;
|
|
6569
|
+
if (held.total === 0 && held.folders.length === 0 && held.otherSymlinks.length === 0) return;
|
|
6459
6570
|
const guessed = held.byName;
|
|
6460
6571
|
const chosen = heldBackGroups(held).filter(({ label }) => label !== NAME_GUESS_LABEL);
|
|
6461
6572
|
const chosenCount = chosen.reduce((n, g) => n + g.rows.length, 0);
|
|
@@ -6509,6 +6620,21 @@ ${chosenCount} note(s) are not in ${wording.where}, because your rules say so:`
|
|
|
6509
6620
|
)
|
|
6510
6621
|
);
|
|
6511
6622
|
}
|
|
6623
|
+
if (held.otherSymlinks.length > 0) {
|
|
6624
|
+
printHuman(
|
|
6625
|
+
c.gray(
|
|
6626
|
+
`
|
|
6627
|
+
${held.otherSymlinks.length} symlink(s) were not followed, so nothing behind them is in ${wording.where} \u2014
|
|
6628
|
+
a folder of notes behind one is not counted above:`
|
|
6629
|
+
)
|
|
6630
|
+
);
|
|
6631
|
+
for (const path of held.otherSymlinks.slice(0, 10)) {
|
|
6632
|
+
printHuman(c.gray(` ${oneLine(path, WIDTH.path)}`));
|
|
6633
|
+
}
|
|
6634
|
+
if (held.otherSymlinks.length > 10) {
|
|
6635
|
+
printHuman(c.gray(` \u2026 and ${held.otherSymlinks.length - 10} more`));
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6512
6638
|
}
|
|
6513
6639
|
var isNote, NAME_GUESS_LABEL;
|
|
6514
6640
|
var init_held_back = __esm({
|
|
@@ -6537,8 +6663,8 @@ var init_index_build = __esm({
|
|
|
6537
6663
|
});
|
|
6538
6664
|
|
|
6539
6665
|
// src/commands/init.ts
|
|
6540
|
-
import { mkdir as mkdir9, readdir as
|
|
6541
|
-
import { join as
|
|
6666
|
+
import { mkdir as mkdir9, readdir as readdir6, writeFile as writeFile7 } from "fs/promises";
|
|
6667
|
+
import { join as join16, resolve as resolve6 } from "path";
|
|
6542
6668
|
import { Command as Command4 } from "commander";
|
|
6543
6669
|
function initCommand() {
|
|
6544
6670
|
return new Command4("init").description("create a new brain, laid out and ready to write into").argument("<path>", "where the brain should live").option("--name <name>", "what to call it in the hub", "My brain").action(async (path, opts) => {
|
|
@@ -6573,7 +6699,7 @@ function initCommand() {
|
|
|
6573
6699
|
}
|
|
6574
6700
|
async function createBrain(path, name) {
|
|
6575
6701
|
const root = resolve6(path);
|
|
6576
|
-
const existing = await
|
|
6702
|
+
const existing = await readdir6(root).catch(() => null);
|
|
6577
6703
|
if (existing && existing.filter((e) => !e.startsWith(".")).length > 0) {
|
|
6578
6704
|
throw memgError(
|
|
6579
6705
|
"already_initialized",
|
|
@@ -6583,9 +6709,9 @@ async function createBrain(path, name) {
|
|
|
6583
6709
|
);
|
|
6584
6710
|
}
|
|
6585
6711
|
await mkdir9(root, { recursive: true });
|
|
6586
|
-
for (const dir of FOLDERS) await mkdir9(
|
|
6712
|
+
for (const dir of FOLDERS) await mkdir9(join16(root, dir), { recursive: true });
|
|
6587
6713
|
for (const [file, content] of Object.entries(files(name))) {
|
|
6588
|
-
await writeFile7(
|
|
6714
|
+
await writeFile7(join16(root, file), content, "utf8");
|
|
6589
6715
|
}
|
|
6590
6716
|
const canonicalRoot2 = await canonicalize(root);
|
|
6591
6717
|
const adapter = await LocalFileReadAdapter.openVault(canonicalRoot2);
|
|
@@ -6739,10 +6865,10 @@ var init_cloud_pointer = __esm({
|
|
|
6739
6865
|
});
|
|
6740
6866
|
|
|
6741
6867
|
// src/commands/link.ts
|
|
6742
|
-
import { readFile as
|
|
6743
|
-
import { join as
|
|
6744
|
-
import { Command as
|
|
6745
|
-
import
|
|
6868
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
6869
|
+
import { join as join20 } from "path";
|
|
6870
|
+
import { Command as Command14 } from "commander";
|
|
6871
|
+
import prompts3 from "prompts";
|
|
6746
6872
|
function sameBasename(all, entry) {
|
|
6747
6873
|
const base2 = (entry.split("/").pop() ?? entry).normalize("NFC").toLowerCase();
|
|
6748
6874
|
return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() === base2);
|
|
@@ -6764,7 +6890,7 @@ function spreadAcrossFolders(rows, max) {
|
|
|
6764
6890
|
return picked;
|
|
6765
6891
|
}
|
|
6766
6892
|
function linkCommand() {
|
|
6767
|
-
return new
|
|
6893
|
+
return new Command14("link").description("read a notes folder as a brain \u2014 audits its rules first").argument("<path>", "path to the notes folder").option("--yes", "skip the confirmation").option("--dry-run", "show exactly what would be stored, then stop").option(
|
|
6768
6894
|
"--observations <dir>",
|
|
6769
6895
|
"where `remember` writes, relative to the folder",
|
|
6770
6896
|
DEFAULT_OBSERVATIONS_DIR
|
|
@@ -6944,7 +7070,7 @@ async function recordLink(adapter, observationsDir) {
|
|
|
6944
7070
|
}
|
|
6945
7071
|
async function refuseSilentRules(path, denied, excluded, unsupported) {
|
|
6946
7072
|
const denyEntries = (await LocalFileReadAdapter.readDenyFile(path, ".memgdeny")).entries;
|
|
6947
|
-
const rawDenyLines = await
|
|
7073
|
+
const rawDenyLines = await readFile20(join20(path, ".memgdeny"), "utf8").then(
|
|
6948
7074
|
(t) => t.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"))
|
|
6949
7075
|
).catch(() => []);
|
|
6950
7076
|
const asWritten = (folded) => rawDenyLines.find(
|
|
@@ -7090,7 +7216,7 @@ Brain: ${root}`));
|
|
|
7090
7216
|
"\n This tool writes into your notes without asking each time \u2014 new\n observations, and changes to the memory block of a note. Every one is\n recorded and reversible: memgineering log \xB7 memgineering undo"
|
|
7091
7217
|
)
|
|
7092
7218
|
);
|
|
7093
|
-
const { ok } = await
|
|
7219
|
+
const { ok } = await prompts3({
|
|
7094
7220
|
type: "confirm",
|
|
7095
7221
|
name: "ok",
|
|
7096
7222
|
message: "Build the index for this folder?",
|
|
@@ -7171,9 +7297,9 @@ var init_open_browser = __esm({
|
|
|
7171
7297
|
});
|
|
7172
7298
|
|
|
7173
7299
|
// src/commands/login.ts
|
|
7174
|
-
import { Command as
|
|
7300
|
+
import { Command as Command22 } from "commander";
|
|
7175
7301
|
function loginCommand() {
|
|
7176
|
-
return new
|
|
7302
|
+
return new Command22("login").description("sign in to a hosted brain").option(
|
|
7177
7303
|
"--emit-only",
|
|
7178
7304
|
"start the sign-in, open the browser, and return immediately \u2014 THE path an agent should use on a user\u2019s behalf; the next command claims the token automatically. Prefer this over the plain form, whose wait for a human click will outlast an agent\u2019s command timeout"
|
|
7179
7305
|
).option("--no-browser", "do not try to open a browser \u2014 just print the link").option("--print-url", "print the approval link even when a browser opened").addHelpText(
|
|
@@ -7373,9 +7499,9 @@ var push_exports = {};
|
|
|
7373
7499
|
__export(push_exports, {
|
|
7374
7500
|
pushCommand: () => pushCommand
|
|
7375
7501
|
});
|
|
7376
|
-
import { Command as
|
|
7502
|
+
import { Command as Command37 } from "commander";
|
|
7377
7503
|
function pushCommand() {
|
|
7378
|
-
return new
|
|
7504
|
+
return new Command37("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) => {
|
|
7379
7505
|
const { brain: local } = await resolveBrain(opts.vault);
|
|
7380
7506
|
const adapter = await openBrainOrExplain(local.root);
|
|
7381
7507
|
const scan = await adapter.scan();
|
|
@@ -7627,9 +7753,8 @@ var init_guard_shell = __esm({
|
|
|
7627
7753
|
});
|
|
7628
7754
|
|
|
7629
7755
|
// src/lib/hook-refresh.ts
|
|
7630
|
-
import {
|
|
7631
|
-
import {
|
|
7632
|
-
import { basename as basename3, dirname as dirname8, join as join24 } from "path";
|
|
7756
|
+
import { chmod, lstat as lstat5, readFile as readFile26 } from "fs/promises";
|
|
7757
|
+
import { join as join26 } from "path";
|
|
7633
7758
|
function moveShippedCommands(parsed, moves) {
|
|
7634
7759
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
7635
7760
|
const settings = parsed;
|
|
@@ -7659,80 +7784,11 @@ function moveShippedCommands(parsed, moves) {
|
|
|
7659
7784
|
}
|
|
7660
7785
|
return { next: changed === 0 ? settings : { ...settings, hooks: nextHooks }, changed };
|
|
7661
7786
|
}
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
const [info, token] = await Promise.all([stat7(lockPath2()), readFile24(lockPath2(), "utf8")]);
|
|
7665
|
-
return { token, ageMs: Date.now() - info.mtimeMs };
|
|
7666
|
-
} catch {
|
|
7667
|
-
return null;
|
|
7668
|
-
}
|
|
7669
|
-
}
|
|
7670
|
-
async function unlinkIfStill(token) {
|
|
7671
|
-
const now = await readFile24(lockPath2(), "utf8").catch(() => null);
|
|
7672
|
-
if (now === token) await unlink5(lockPath2()).catch(() => void 0);
|
|
7673
|
-
}
|
|
7674
|
-
async function takeOver(dead) {
|
|
7675
|
-
try {
|
|
7676
|
-
await writeFile14(takeoverPath(), "", { flag: "wx", mode: 384 });
|
|
7677
|
-
} catch (err) {
|
|
7678
|
-
if (err.code === "EEXIST") {
|
|
7679
|
-
const age = await stat7(takeoverPath()).then(
|
|
7680
|
-
(info) => Date.now() - info.mtimeMs,
|
|
7681
|
-
() => null
|
|
7682
|
-
);
|
|
7683
|
-
if (age !== null && age > STALE_LOCK_MS) await unlink5(takeoverPath()).catch(() => void 0);
|
|
7684
|
-
}
|
|
7685
|
-
return false;
|
|
7686
|
-
}
|
|
7687
|
-
try {
|
|
7688
|
-
const held = await lockHolder();
|
|
7689
|
-
if (held !== null && held.token === dead && held.ageMs > STALE_LOCK_MS) {
|
|
7690
|
-
await unlink5(lockPath2()).catch(() => void 0);
|
|
7691
|
-
}
|
|
7692
|
-
return true;
|
|
7693
|
-
} finally {
|
|
7694
|
-
await unlink5(takeoverPath()).catch(() => void 0);
|
|
7695
|
-
}
|
|
7696
|
-
}
|
|
7697
|
-
async function withHookFileLock(fn, waitMs = 0) {
|
|
7698
|
-
const token = `${process.pid}-${randomUUID4()}`;
|
|
7699
|
-
const deadline = Date.now() + waitMs;
|
|
7700
|
-
for (let vanished = 0; ; ) {
|
|
7701
|
-
try {
|
|
7702
|
-
await writeFile14(lockPath2(), token, { flag: "wx", mode: 384 });
|
|
7703
|
-
break;
|
|
7704
|
-
} catch (err) {
|
|
7705
|
-
if (err.code !== "EEXIST") return null;
|
|
7706
|
-
}
|
|
7707
|
-
const held = await lockHolder();
|
|
7708
|
-
if (held === null) {
|
|
7709
|
-
vanished += 1;
|
|
7710
|
-
if (vanished > 20) return null;
|
|
7711
|
-
continue;
|
|
7712
|
-
}
|
|
7713
|
-
if (held.ageMs > STALE_LOCK_MS && await takeOver(held.token)) continue;
|
|
7714
|
-
if (Date.now() >= deadline) return null;
|
|
7715
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
7716
|
-
}
|
|
7717
|
-
try {
|
|
7718
|
-
return await fn();
|
|
7719
|
-
} finally {
|
|
7720
|
-
await unlinkIfStill(token);
|
|
7721
|
-
}
|
|
7722
|
-
}
|
|
7723
|
-
async function clearStaleTemps(path) {
|
|
7724
|
-
const dir = dirname8(path);
|
|
7725
|
-
const prefix = `${basename3(path)}.`;
|
|
7726
|
-
const names = await readdir8(dir).catch(() => []);
|
|
7727
|
-
await Promise.all(
|
|
7728
|
-
names.filter((n) => n.startsWith(prefix) && /\.\d+\.tmp$/.test(n)).map(async (n) => {
|
|
7729
|
-
const age = await stat7(join24(dir, n)).then((s) => Date.now() - s.mtimeMs).catch(() => 0);
|
|
7730
|
-
if (age > STALE_LOCK_MS) await unlink5(join24(dir, n)).catch(() => void 0);
|
|
7731
|
-
})
|
|
7732
|
-
);
|
|
7787
|
+
function withHookFileLock(fn, waitMs = 0) {
|
|
7788
|
+
return withPathLock(join26(brandHome(), "hook-files.lock"), fn, waitMs);
|
|
7733
7789
|
}
|
|
7734
7790
|
async function readSettings(path) {
|
|
7735
|
-
const raw = await
|
|
7791
|
+
const raw = await readFile26(path, "utf8").catch(() => null);
|
|
7736
7792
|
if (raw === null) return void 0;
|
|
7737
7793
|
try {
|
|
7738
7794
|
return JSON.parse(raw);
|
|
@@ -7782,15 +7838,12 @@ function refreshNotice(report3, told) {
|
|
|
7782
7838
|
}
|
|
7783
7839
|
return { text: lines2.length === 0 ? null : lines2.join("\n"), told: nowTold };
|
|
7784
7840
|
}
|
|
7785
|
-
var STALE_LOCK_MS, lockPath2, takeoverPath;
|
|
7786
7841
|
var init_hook_refresh = __esm({
|
|
7787
7842
|
"src/lib/hook-refresh.ts"() {
|
|
7788
7843
|
"use strict";
|
|
7789
7844
|
init_atomic();
|
|
7790
7845
|
init_brand();
|
|
7791
|
-
|
|
7792
|
-
lockPath2 = () => join24(brandHome(), "hook-files.lock");
|
|
7793
|
-
takeoverPath = () => `${lockPath2()}.takeover`;
|
|
7846
|
+
init_path_lock();
|
|
7794
7847
|
}
|
|
7795
7848
|
});
|
|
7796
7849
|
|
|
@@ -7971,7 +8024,7 @@ var init_setup_brain = __esm({
|
|
|
7971
8024
|
});
|
|
7972
8025
|
|
|
7973
8026
|
// src/commands/setup-signin.ts
|
|
7974
|
-
import
|
|
8027
|
+
import prompts5 from "prompts";
|
|
7975
8028
|
async function runSignInStep(opts) {
|
|
7976
8029
|
const summary = await signInSummary();
|
|
7977
8030
|
if (summary.signedIn) return { status: "signed-in", displayName: summary.displayName };
|
|
@@ -7998,7 +8051,7 @@ async function runSignInStep(opts) {
|
|
|
7998
8051
|
}
|
|
7999
8052
|
if (opts.interactive) {
|
|
8000
8053
|
printHuman(c.bold("\nmemgineering \u2014 setup\n"));
|
|
8001
|
-
const { wants } = await
|
|
8054
|
+
const { wants } = await prompts5({
|
|
8002
8055
|
type: "toggle",
|
|
8003
8056
|
name: "wants",
|
|
8004
8057
|
message: "Sign in to a memgineering account first?",
|
|
@@ -8128,38 +8181,38 @@ var init_setup_signin = __esm({
|
|
|
8128
8181
|
});
|
|
8129
8182
|
|
|
8130
8183
|
// src/lib/next-step.ts
|
|
8131
|
-
import { mkdir as
|
|
8132
|
-
import { join as
|
|
8184
|
+
import { mkdir as mkdir17, readFile as readFile27, writeFile as writeFile14 } from "fs/promises";
|
|
8185
|
+
import { join as join27 } from "path";
|
|
8133
8186
|
function isSetUp(cfg) {
|
|
8134
8187
|
return Object.keys(cfg.system.installed).length > 0;
|
|
8135
8188
|
}
|
|
8136
8189
|
function connectedMarker(at) {
|
|
8137
|
-
return
|
|
8190
|
+
return join27(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
|
|
8138
8191
|
}
|
|
8139
8192
|
async function connectedNoticeShown(at) {
|
|
8140
|
-
return
|
|
8193
|
+
return readFile27(connectedMarker(at), "utf8").then(
|
|
8141
8194
|
() => true,
|
|
8142
8195
|
() => false
|
|
8143
8196
|
);
|
|
8144
8197
|
}
|
|
8145
8198
|
async function markConnectedNoticeShown(at) {
|
|
8146
8199
|
try {
|
|
8147
|
-
await
|
|
8148
|
-
await
|
|
8200
|
+
await mkdir17(brandHome(), { recursive: true });
|
|
8201
|
+
await writeFile14(connectedMarker(at), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
8149
8202
|
`);
|
|
8150
8203
|
} catch {
|
|
8151
8204
|
}
|
|
8152
8205
|
}
|
|
8153
8206
|
async function claimFullSetupNotice() {
|
|
8154
|
-
const marker =
|
|
8207
|
+
const marker = join27(brandHome(), "setup-notice-shown");
|
|
8155
8208
|
try {
|
|
8156
|
-
await
|
|
8209
|
+
await readFile27(marker, "utf8");
|
|
8157
8210
|
return false;
|
|
8158
8211
|
} catch {
|
|
8159
8212
|
}
|
|
8160
8213
|
try {
|
|
8161
|
-
await
|
|
8162
|
-
await
|
|
8214
|
+
await mkdir17(brandHome(), { recursive: true });
|
|
8215
|
+
await writeFile14(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
8163
8216
|
`);
|
|
8164
8217
|
} catch {
|
|
8165
8218
|
}
|
|
@@ -8325,7 +8378,7 @@ Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
|
|
|
8325
8378
|
// src/lib/provenance.ts
|
|
8326
8379
|
import { execFile, spawn as spawn2 } from "child_process";
|
|
8327
8380
|
import { realpath as realpath3 } from "fs/promises";
|
|
8328
|
-
import { dirname as dirname9, join as
|
|
8381
|
+
import { dirname as dirname9, join as join28, resolve as resolve14 } from "path";
|
|
8329
8382
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8330
8383
|
import { promisify } from "util";
|
|
8331
8384
|
async function detectProvenance(selfPath, opts = {}) {
|
|
@@ -8463,7 +8516,7 @@ var init_provenance = __esm({
|
|
|
8463
8516
|
kind: "npm",
|
|
8464
8517
|
bin: "npm",
|
|
8465
8518
|
prefixArgs: ["prefix", "-g"],
|
|
8466
|
-
moduleRoot: (prefix) => process.platform === "win32" ?
|
|
8519
|
+
moduleRoot: (prefix) => process.platform === "win32" ? join28(prefix, "node_modules") : join28(prefix, "lib", "node_modules"),
|
|
8467
8520
|
install: "npm i -g memgineering@latest"
|
|
8468
8521
|
},
|
|
8469
8522
|
{
|
|
@@ -8500,9 +8553,9 @@ __export(setup_cloud_exports, {
|
|
|
8500
8553
|
});
|
|
8501
8554
|
import { spawn as spawn3 } from "child_process";
|
|
8502
8555
|
import { realpathSync as realpathSync2 } from "fs";
|
|
8503
|
-
import { readdir as readdir9, stat as
|
|
8556
|
+
import { readdir as readdir9, stat as stat9 } from "fs/promises";
|
|
8504
8557
|
import { homedir as homedir5 } from "os";
|
|
8505
|
-
import { basename as basename4, dirname as dirname10, join as
|
|
8558
|
+
import { basename as basename4, dirname as dirname10, join as join29, resolve as resolve15, sep as sep4 } from "path";
|
|
8506
8559
|
async function report2(setupToken, state, detail, code) {
|
|
8507
8560
|
try {
|
|
8508
8561
|
await apiRequest({
|
|
@@ -8520,7 +8573,7 @@ async function report2(setupToken, state, detail, code) {
|
|
|
8520
8573
|
}
|
|
8521
8574
|
async function folderCandidates() {
|
|
8522
8575
|
const home = homedir5();
|
|
8523
|
-
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) =>
|
|
8576
|
+
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join29(home, d));
|
|
8524
8577
|
const found = [];
|
|
8525
8578
|
const countNotes = async (dir) => {
|
|
8526
8579
|
let count = 0;
|
|
@@ -8529,7 +8582,7 @@ async function folderCandidates() {
|
|
|
8529
8582
|
if (entry.name.startsWith(".")) continue;
|
|
8530
8583
|
if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) count += 1;
|
|
8531
8584
|
else if (entry.isDirectory()) {
|
|
8532
|
-
const inner = await readdir9(
|
|
8585
|
+
const inner = await readdir9(join29(dir, entry.name)).catch(() => []);
|
|
8533
8586
|
count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
|
|
8534
8587
|
}
|
|
8535
8588
|
if (count > 999) break;
|
|
@@ -8537,7 +8590,7 @@ async function folderCandidates() {
|
|
|
8537
8590
|
return count;
|
|
8538
8591
|
};
|
|
8539
8592
|
for (const root of roots) {
|
|
8540
|
-
const info = await
|
|
8593
|
+
const info = await stat9(root).catch(() => null);
|
|
8541
8594
|
if (!info?.isDirectory()) continue;
|
|
8542
8595
|
const notes = await countNotes(root);
|
|
8543
8596
|
if (notes > 0) found.push({ path: root, note_count: notes });
|
|
@@ -8882,8 +8935,8 @@ function insideFolder(target, folder) {
|
|
|
8882
8935
|
return landed === root || landed.startsWith(root + sep4);
|
|
8883
8936
|
}
|
|
8884
8937
|
async function askBeforeUpload(q) {
|
|
8885
|
-
const { default:
|
|
8886
|
-
const { yes } = await
|
|
8938
|
+
const { default: prompts7 } = await import("prompts");
|
|
8939
|
+
const { yes } = await prompts7({
|
|
8887
8940
|
type: "confirm",
|
|
8888
8941
|
name: "yes",
|
|
8889
8942
|
message: `Upload the notes in ${q.path} to the brain on ${q.email ?? "this account"}?`,
|
|
@@ -8921,14 +8974,14 @@ var init_setup_cloud = __esm({
|
|
|
8921
8974
|
|
|
8922
8975
|
// src/commands/setup.ts
|
|
8923
8976
|
import { createHash as createHash9 } from "crypto";
|
|
8924
|
-
import { access as access2, mkdir as
|
|
8925
|
-
import { dirname as dirname11, join as
|
|
8977
|
+
import { access as access2, mkdir as mkdir18, readFile as readFile28 } from "fs/promises";
|
|
8978
|
+
import { dirname as dirname11, join as join30 } from "path";
|
|
8926
8979
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8927
|
-
import { Command as
|
|
8928
|
-
import
|
|
8980
|
+
import { Command as Command39 } from "commander";
|
|
8981
|
+
import prompts6 from "prompts";
|
|
8929
8982
|
import { homedir as homedir6 } from "os";
|
|
8930
8983
|
function setupCommand() {
|
|
8931
|
-
return new
|
|
8984
|
+
return new Command39("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(
|
|
8932
8985
|
"--wait",
|
|
8933
8986
|
"with --web: block until the screen is finished, even with nobody watching the terminal"
|
|
8934
8987
|
).option(
|
|
@@ -8949,9 +9002,11 @@ function setupCommand() {
|
|
|
8949
9002
|
"",
|
|
8950
9003
|
"Chat apps with no shell need no setup. They read and write the brain on your",
|
|
8951
9004
|
`account (not a folder on this machine) at ${HOSTED_CONNECTOR_URL}:`,
|
|
8952
|
-
" ChatGPT: Settings \u2192 Developer mode \u2192 add an MCP server with that URL, sign in.",
|
|
8953
9005
|
" Claude.ai, Claude Desktop, Cowork: Customize \u2192 Connectors \u2192 Add custom",
|
|
8954
|
-
" connector with that URL, sign in."
|
|
9006
|
+
" connector with that URL, sign in.",
|
|
9007
|
+
" ChatGPT: Settings \u2192 Developer mode \u2192 add an MCP server with that URL, sign in",
|
|
9008
|
+
" (paused for now: the connect screen refuses ChatGPT while its notice is",
|
|
9009
|
+
" verified; use Claude or Gemini meanwhile)."
|
|
8955
9010
|
].join("\n")
|
|
8956
9011
|
).action(async (opts) => {
|
|
8957
9012
|
const detected = await detectTools();
|
|
@@ -9224,7 +9279,7 @@ function setupCommand() {
|
|
|
9224
9279
|
}
|
|
9225
9280
|
});
|
|
9226
9281
|
if (mode.kind === "human" && brains.length === 0 && !opts.dryRun && isInteractive()) {
|
|
9227
|
-
const { path } = await
|
|
9282
|
+
const { path } = await prompts6({
|
|
9228
9283
|
type: "text",
|
|
9229
9284
|
name: "path",
|
|
9230
9285
|
message: "Path to your notes folder (blank to skip)"
|
|
@@ -9289,7 +9344,7 @@ async function applySetup(choice, dryRun) {
|
|
|
9289
9344
|
}
|
|
9290
9345
|
async function write2() {
|
|
9291
9346
|
for (const tool of choice.tools) {
|
|
9292
|
-
const hubPath =
|
|
9347
|
+
const hubPath = join30(agentHome(), tool.hubFile);
|
|
9293
9348
|
const written = await injectBlock(
|
|
9294
9349
|
hubPath,
|
|
9295
9350
|
hub + tailFor(tool, choice.hook),
|
|
@@ -9304,8 +9359,8 @@ async function applySetup(choice, dryRun) {
|
|
|
9304
9359
|
});
|
|
9305
9360
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
9306
9361
|
for (const skill of BRAND.subSkills) {
|
|
9307
|
-
const content = await loadAsset(
|
|
9308
|
-
const path =
|
|
9362
|
+
const content = await loadAsset(join30(skill, "SKILL.md"));
|
|
9363
|
+
const path = join30(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
9309
9364
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
9310
9365
|
results.push({
|
|
9311
9366
|
tool: tool.label,
|
|
@@ -9395,7 +9450,7 @@ async function hooksArmed(tools) {
|
|
|
9395
9450
|
const capable = tools.filter((t) => t.hooks && t.hooksFile);
|
|
9396
9451
|
if (capable.length === 0) return void 0;
|
|
9397
9452
|
for (const tool of capable) {
|
|
9398
|
-
const raw = await
|
|
9453
|
+
const raw = await readFile28(join30(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
|
|
9399
9454
|
() => ""
|
|
9400
9455
|
);
|
|
9401
9456
|
if (hooksFor([tool]).some((spec) => raw.includes(spec.signature))) return "on";
|
|
@@ -9415,7 +9470,7 @@ async function resolveMode(opts) {
|
|
|
9415
9470
|
return { kind: "human", chosen: true };
|
|
9416
9471
|
}
|
|
9417
9472
|
if (!isInteractive() || isJsonMode()) return { kind: "agent", chosen: false };
|
|
9418
|
-
const { mode } = await
|
|
9473
|
+
const { mode } = await prompts6({
|
|
9419
9474
|
type: "select",
|
|
9420
9475
|
name: "mode",
|
|
9421
9476
|
message: "How would you like to set this up?",
|
|
@@ -9435,7 +9490,7 @@ async function detectTools() {
|
|
|
9435
9490
|
const home = agentHome();
|
|
9436
9491
|
const found = [];
|
|
9437
9492
|
for (const tool of TOOLS) {
|
|
9438
|
-
if (await exists2(
|
|
9493
|
+
if (await exists2(join30(home, tool.dir))) found.push(tool);
|
|
9439
9494
|
}
|
|
9440
9495
|
return found;
|
|
9441
9496
|
}
|
|
@@ -9482,7 +9537,7 @@ async function askHuman(detected, opts) {
|
|
|
9482
9537
|
printHuman(c.bold("\nmemgineering \u2014 setup\n"));
|
|
9483
9538
|
printHuman(c.gray(" Found: ") + detected.map((t) => t.label).join(", "));
|
|
9484
9539
|
printHuman("");
|
|
9485
|
-
const answers = await
|
|
9540
|
+
const answers = await prompts6([
|
|
9486
9541
|
{
|
|
9487
9542
|
type: "multiselect",
|
|
9488
9543
|
name: "tools",
|
|
@@ -9535,10 +9590,10 @@ async function injectBlock(filePath, content, dryRun, weWroteThis) {
|
|
|
9535
9590
|
const block = `${BRAND.hubMarkerStart}
|
|
9536
9591
|
${content.trimEnd()}
|
|
9537
9592
|
${BRAND.hubMarkerEnd}`;
|
|
9538
|
-
const existing = await
|
|
9593
|
+
const existing = await readFile28(filePath, "utf8").catch(() => null);
|
|
9539
9594
|
if (existing === null) {
|
|
9540
9595
|
if (!dryRun) {
|
|
9541
|
-
await
|
|
9596
|
+
await mkdir18(dirname11(filePath), { recursive: true });
|
|
9542
9597
|
await writeThenRename(filePath, `${block}
|
|
9543
9598
|
`);
|
|
9544
9599
|
}
|
|
@@ -9578,11 +9633,11 @@ function looksLikeOurGuide(text) {
|
|
|
9578
9633
|
return text.includes("memgineering recall") && text.includes("memgineering remember");
|
|
9579
9634
|
}
|
|
9580
9635
|
async function writeIfDifferent(filePath, content, dryRun) {
|
|
9581
|
-
const existing = await
|
|
9636
|
+
const existing = await readFile28(filePath, "utf8").catch(() => null);
|
|
9582
9637
|
const after = fingerprint(content);
|
|
9583
9638
|
if (existing === content) return { outcome: "unchanged", before: existing, after };
|
|
9584
9639
|
if (!dryRun) {
|
|
9585
|
-
await
|
|
9640
|
+
await mkdir18(dirname11(filePath), { recursive: true });
|
|
9586
9641
|
await writeThenRename(filePath, content);
|
|
9587
9642
|
}
|
|
9588
9643
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
@@ -9599,7 +9654,7 @@ async function refreshOwnHookCommands() {
|
|
|
9599
9654
|
const changed = [];
|
|
9600
9655
|
const skipped = [];
|
|
9601
9656
|
for (const tool of TOOLS.filter((t) => t.hooks && t.hooksFile && DEFAULT_HOOK_TOOLS.includes(t.id))) {
|
|
9602
|
-
const path =
|
|
9657
|
+
const path = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9603
9658
|
const outcome = await refreshHookFile(path, moves);
|
|
9604
9659
|
if (outcome === "changed") changed.push(path);
|
|
9605
9660
|
else if (outcome === "symlink" || outcome === "refused") skipped.push({ path, why: outcome });
|
|
@@ -9626,8 +9681,8 @@ function hooksFor(tools) {
|
|
|
9626
9681
|
);
|
|
9627
9682
|
}
|
|
9628
9683
|
async function installHook(spec, tool, dryRun) {
|
|
9629
|
-
const settingsPath =
|
|
9630
|
-
const raw = await
|
|
9684
|
+
const settingsPath = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9685
|
+
const raw = await readFile28(settingsPath, "utf8").catch(() => null);
|
|
9631
9686
|
let settings = {};
|
|
9632
9687
|
if (raw !== null) {
|
|
9633
9688
|
try {
|
|
@@ -9669,15 +9724,15 @@ async function installHook(spec, tool, dryRun) {
|
|
|
9669
9724
|
}
|
|
9670
9725
|
const next = { ...settings, hooks: { ...hooks, [spec.event]: existingGroups } };
|
|
9671
9726
|
if (!dryRun) {
|
|
9672
|
-
await
|
|
9727
|
+
await mkdir18(dirname11(settingsPath), { recursive: true });
|
|
9673
9728
|
await writeThenRename(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
9674
9729
|
`);
|
|
9675
9730
|
}
|
|
9676
9731
|
return raw === null ? "injected" : "updated";
|
|
9677
9732
|
}
|
|
9678
9733
|
async function removeHook(spec, tool, dryRun) {
|
|
9679
|
-
const settingsPath =
|
|
9680
|
-
const raw = await
|
|
9734
|
+
const settingsPath = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9735
|
+
const raw = await readFile28(settingsPath, "utf8").catch(() => null);
|
|
9681
9736
|
if (raw === null) return "unchanged";
|
|
9682
9737
|
let parsed;
|
|
9683
9738
|
try {
|
|
@@ -9701,7 +9756,7 @@ async function removeHook(spec, tool, dryRun) {
|
|
|
9701
9756
|
return "removed";
|
|
9702
9757
|
}
|
|
9703
9758
|
async function loadAsset(relPath) {
|
|
9704
|
-
const raw = await
|
|
9759
|
+
const raw = await readFile28(join30(await assetRoot(), relPath), "utf8");
|
|
9705
9760
|
if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
|
|
9706
9761
|
const end = raw.indexOf("\n---", 4);
|
|
9707
9762
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -9710,12 +9765,12 @@ async function loadAsset(relPath) {
|
|
|
9710
9765
|
}
|
|
9711
9766
|
async function assetRoot() {
|
|
9712
9767
|
const here = dirname11(fileURLToPath3(import.meta.url));
|
|
9713
|
-
const bundled =
|
|
9714
|
-
if (await exists2(
|
|
9768
|
+
const bundled = join30(here, "..", "assets");
|
|
9769
|
+
if (await exists2(join30(bundled, BRAND.hubFileName))) return bundled;
|
|
9715
9770
|
let dir = here;
|
|
9716
9771
|
for (; ; ) {
|
|
9717
|
-
const candidate =
|
|
9718
|
-
if (await exists2(
|
|
9772
|
+
const candidate = join30(dir, "packages", "skills");
|
|
9773
|
+
if (await exists2(join30(candidate, BRAND.hubFileName))) return candidate;
|
|
9719
9774
|
const parent = dirname11(dir);
|
|
9720
9775
|
if (parent === dir) break;
|
|
9721
9776
|
dir = parent;
|
|
@@ -9910,7 +9965,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
9910
9965
|
import net from "net";
|
|
9911
9966
|
|
|
9912
9967
|
// src/program.ts
|
|
9913
|
-
import { Command as
|
|
9968
|
+
import { Command as Command47 } from "commander";
|
|
9914
9969
|
|
|
9915
9970
|
// src/help.ts
|
|
9916
9971
|
init_ui();
|
|
@@ -9978,6 +10033,7 @@ var COMMAND_GROUPS = [
|
|
|
9978
10033
|
"create",
|
|
9979
10034
|
"rename",
|
|
9980
10035
|
"delete-brain",
|
|
10036
|
+
"delete-account",
|
|
9981
10037
|
"push",
|
|
9982
10038
|
"pull"
|
|
9983
10039
|
]
|
|
@@ -10234,6 +10290,21 @@ var ReportReasonSchema = z10.enum(["spam", "harmful", "incorrect", "copyright",
|
|
|
10234
10290
|
|
|
10235
10291
|
// ../../packages/contracts/src/errors.ts
|
|
10236
10292
|
import { z as z11 } from "zod";
|
|
10293
|
+
|
|
10294
|
+
// ../../packages/contracts/src/refresh-race.ts
|
|
10295
|
+
var REFRESH_RACE_FLAG = "refresh_rotated_by_another_request";
|
|
10296
|
+
function isRefreshRace(body) {
|
|
10297
|
+
if (typeof body !== "object" || body === null) return false;
|
|
10298
|
+
const error = body.error;
|
|
10299
|
+
if (typeof error !== "object" || error === null) return false;
|
|
10300
|
+
const { code, flags } = error;
|
|
10301
|
+
if (code !== "not_authenticated" || !Array.isArray(flags)) return false;
|
|
10302
|
+
return flags.some(
|
|
10303
|
+
(flag2) => typeof flag2 === "object" && flag2 !== null && flag2.code === REFRESH_RACE_FLAG
|
|
10304
|
+
);
|
|
10305
|
+
}
|
|
10306
|
+
|
|
10307
|
+
// ../../packages/contracts/src/errors.ts
|
|
10237
10308
|
var ErrorCodeSchema = z11.enum([
|
|
10238
10309
|
"generic_failure",
|
|
10239
10310
|
// 1
|
|
@@ -11183,16 +11254,33 @@ var PublishPackageRequestSchema = z19.object({
|
|
|
11183
11254
|
*/
|
|
11184
11255
|
content: z19.string().min(50).max(1e5),
|
|
11185
11256
|
/**
|
|
11186
|
-
*
|
|
11187
|
-
*
|
|
11257
|
+
* Optional, and refused when it disagrees. core-api stores the title,
|
|
11258
|
+
* description and type it reads from the frontmatter of `content`, which must
|
|
11259
|
+
* carry all three (the title may come from `name:`). A value here that
|
|
11260
|
+
* differs from the frontmatter's, compared trimmed, is refused as
|
|
11261
|
+
* `usage_error` (HTTP 400) with a hint naming the frontmatter key; absent
|
|
11262
|
+
* means no claim. A `type` the registry cannot publish at all (`scoped-rule`,
|
|
11263
|
+
* `mdc-rule`, `plugin`) names no key to write: its hint says the type cannot
|
|
11264
|
+
* be published yet and to drop `--type`. `kordis push` ≤0.15.2 sends
|
|
11265
|
+
* `--type` here, and writes `type: skill` into the frontmatter of a SKILL.md
|
|
11266
|
+
* or *.skill.md file, a folder or a .skill bundle that names no type — so
|
|
11267
|
+
* `--type hook` on one is refused, where until 2026-10 it published a skill.
|
|
11268
|
+
* Later versions send no `type`: they write `--type` into the frontmatter.
|
|
11188
11269
|
*/
|
|
11189
11270
|
title: z19.string().min(1).max(200).optional(),
|
|
11190
11271
|
description: z19.string().min(1).max(2048).optional(),
|
|
11191
11272
|
type: HarnessTypeSchema.optional(),
|
|
11273
|
+
/**
|
|
11274
|
+
* A new package is published under this slug instead of the one derived from
|
|
11275
|
+
* the frontmatter title. An update keeps the slug of the package it updates:
|
|
11276
|
+
* a slug here that differs from the target's is refused (`usage_error`).
|
|
11277
|
+
*/
|
|
11192
11278
|
slug: SlugSchema.optional(),
|
|
11193
11279
|
/**
|
|
11194
|
-
* When updating, the target full_slug
|
|
11195
|
-
*
|
|
11280
|
+
* When updating, the target full_slug; its presence is what makes a publish
|
|
11281
|
+
* an update (`PUT /api/packages/:u/:s` sets it from the path). Omitted for a
|
|
11282
|
+
* new package, whose slug is `slug` or else derived from the frontmatter
|
|
11283
|
+
* title — never derived on an update.
|
|
11196
11284
|
*/
|
|
11197
11285
|
target_full_slug: FullSlugSchema.optional(),
|
|
11198
11286
|
/**
|
|
@@ -11200,6 +11288,12 @@ var PublishPackageRequestSchema = z19.object({
|
|
|
11200
11288
|
*/
|
|
11201
11289
|
bump: z19.enum(["patch", "minor", "major"]).optional(),
|
|
11202
11290
|
changelog: z19.string().max(2e3).optional(),
|
|
11291
|
+
/**
|
|
11292
|
+
* Refused when non-empty (`usage_error`): a package's dependencies are read
|
|
11293
|
+
* from its frontmatter's `depends:` and `recommends:` lists only. Kept in the
|
|
11294
|
+
* schema so the server sees them to refuse them — this object is non-strict,
|
|
11295
|
+
* and an unlisted key would be stripped without a word.
|
|
11296
|
+
*/
|
|
11203
11297
|
depends: z19.array(z19.string()).optional(),
|
|
11204
11298
|
recommends: z19.array(z19.string()).optional(),
|
|
11205
11299
|
/**
|
|
@@ -11434,6 +11528,11 @@ var YankResponseSchema = z19.object({
|
|
|
11434
11528
|
yank_reason: z19.string().max(280).nullable(),
|
|
11435
11529
|
effective_current_version: SemverSchema
|
|
11436
11530
|
});
|
|
11531
|
+
var RestorePackageResponseSchema = z19.object({
|
|
11532
|
+
full_slug: FullSlugSchema,
|
|
11533
|
+
status: z19.literal("restored"),
|
|
11534
|
+
restored_at: IsoDateTimeSchema
|
|
11535
|
+
});
|
|
11437
11536
|
var ResolveVersionQuerySchema = z19.object({
|
|
11438
11537
|
range: SemverRangeSchema
|
|
11439
11538
|
});
|
|
@@ -11478,7 +11577,15 @@ var UserProfileResponseSchema = PublicUserSchema.extend({
|
|
|
11478
11577
|
* responses omit both. Optional so pre-P1 clients ignore them.
|
|
11479
11578
|
*/
|
|
11480
11579
|
viewer_is_following: z19.boolean().optional(),
|
|
11481
|
-
viewer_is_self: z19.boolean().optional()
|
|
11580
|
+
viewer_is_self: z19.boolean().optional(),
|
|
11581
|
+
/**
|
|
11582
|
+
* Whether other people can see this profile (migration 0031). A profile
|
|
11583
|
+
* becomes public with its owner's first public publish, comment or star,
|
|
11584
|
+
* and stays public. Anyone else asking for a non-public profile gets the
|
|
11585
|
+
* unknown-user `404`, so `false` only ever reaches the owner. Absent from
|
|
11586
|
+
* servers that predate it; treat absent as public.
|
|
11587
|
+
*/
|
|
11588
|
+
is_public: z19.boolean().optional()
|
|
11482
11589
|
});
|
|
11483
11590
|
var UserPackagesQuerySchema = z19.object({
|
|
11484
11591
|
sort: SortModeSchema.default("stars"),
|
|
@@ -11506,6 +11613,81 @@ var ListFollowersResponseSchema = z19.object({
|
|
|
11506
11613
|
results: PublicUserSchema.array(),
|
|
11507
11614
|
next_cursor: z19.string().nullable()
|
|
11508
11615
|
});
|
|
11616
|
+
var MeResponseSchema = SelfUserSchema.extend({
|
|
11617
|
+
/**
|
|
11618
|
+
* Whether the caller's registry profile is visible to other people
|
|
11619
|
+
* (migration 0031): false until their first public publish, comment or
|
|
11620
|
+
* star. Absent from servers that predate it; treat absent as public.
|
|
11621
|
+
*/
|
|
11622
|
+
profile_public: z19.boolean().optional()
|
|
11623
|
+
});
|
|
11624
|
+
var DeleteMeRequestSchema = z19.object({
|
|
11625
|
+
confirm_username: z19.string().min(1).max(32)
|
|
11626
|
+
});
|
|
11627
|
+
var AccountExportUserSchema = z19.object({
|
|
11628
|
+
id: z19.string(),
|
|
11629
|
+
username: z19.string(),
|
|
11630
|
+
display_name: z19.string().nullable(),
|
|
11631
|
+
email: z19.string().nullable(),
|
|
11632
|
+
bio: z19.string().nullable(),
|
|
11633
|
+
avatar_url: z19.string().nullable(),
|
|
11634
|
+
primary_provider: z19.string(),
|
|
11635
|
+
role: z19.string(),
|
|
11636
|
+
created_at: z19.string(),
|
|
11637
|
+
updated_at: z19.string(),
|
|
11638
|
+
last_active_at: z19.string().nullable()
|
|
11639
|
+
});
|
|
11640
|
+
var AccountExportSchema = z19.object({
|
|
11641
|
+
schema_version: z19.literal(1),
|
|
11642
|
+
exported_at: z19.string(),
|
|
11643
|
+
user: AccountExportUserSchema,
|
|
11644
|
+
packages: z19.array(
|
|
11645
|
+
z19.object({
|
|
11646
|
+
id: z19.string(),
|
|
11647
|
+
slug: z19.string(),
|
|
11648
|
+
full_slug: z19.string(),
|
|
11649
|
+
type: z19.string(),
|
|
11650
|
+
tier: z19.string(),
|
|
11651
|
+
title: z19.string(),
|
|
11652
|
+
description: z19.string(),
|
|
11653
|
+
content: z19.string(),
|
|
11654
|
+
current_version: z19.string(),
|
|
11655
|
+
visibility: z19.string(),
|
|
11656
|
+
created_at: z19.string(),
|
|
11657
|
+
updated_at: z19.string(),
|
|
11658
|
+
deleted_at: z19.string().nullable()
|
|
11659
|
+
})
|
|
11660
|
+
),
|
|
11661
|
+
package_versions: z19.array(
|
|
11662
|
+
z19.object({
|
|
11663
|
+
id: z19.string(),
|
|
11664
|
+
package_id: z19.string(),
|
|
11665
|
+
version: z19.string(),
|
|
11666
|
+
content: z19.string(),
|
|
11667
|
+
review_status: z19.string(),
|
|
11668
|
+
published_at: z19.string(),
|
|
11669
|
+
yanked_at: z19.string().nullable()
|
|
11670
|
+
})
|
|
11671
|
+
),
|
|
11672
|
+
comments: z19.array(
|
|
11673
|
+
z19.object({
|
|
11674
|
+
id: z19.string(),
|
|
11675
|
+
package_id: z19.string(),
|
|
11676
|
+
body: z19.string(),
|
|
11677
|
+
kind: z19.string(),
|
|
11678
|
+
title: z19.string().nullable(),
|
|
11679
|
+
status: z19.string().nullable(),
|
|
11680
|
+
parent_id: z19.string().nullable(),
|
|
11681
|
+
created_at: z19.string(),
|
|
11682
|
+
edited_at: z19.string().nullable(),
|
|
11683
|
+
deleted_at: z19.string().nullable()
|
|
11684
|
+
})
|
|
11685
|
+
),
|
|
11686
|
+
stars: z19.array(z19.object({ package_id: z19.string(), created_at: z19.string() })),
|
|
11687
|
+
follows: z19.array(
|
|
11688
|
+
z19.object({ follower_id: z19.string(), following_id: z19.string(), created_at: z19.string() })
|
|
11689
|
+
)
|
|
11690
|
+
});
|
|
11509
11691
|
|
|
11510
11692
|
// ../../packages/contracts/src/context.ts
|
|
11511
11693
|
import { z as z20 } from "zod";
|
|
@@ -11673,6 +11855,120 @@ var AdminTelemetrySummaryResponseSchema = z22.object({
|
|
|
11673
11855
|
daily: z22.array(TelemetryDailyBucketSchema)
|
|
11674
11856
|
});
|
|
11675
11857
|
|
|
11858
|
+
// ../../packages/contracts/src/review-patterns.ts
|
|
11859
|
+
var STAGE_1_REJECT_PATTERNS = [
|
|
11860
|
+
{
|
|
11861
|
+
code: "curl_pipe_shell",
|
|
11862
|
+
pattern: /\bcurl\s+[^|;]+\s*\|\s*(?:(?:sudo|doas)\s+)?(?:-E\s+|[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:sh|bash|zsh|ksh|dash|csh|tcsh|fish)\b/i,
|
|
11863
|
+
hint: "Replace `curl \u2026 | bash` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
11864
|
+
},
|
|
11865
|
+
{
|
|
11866
|
+
code: "wget_pipe_shell",
|
|
11867
|
+
pattern: /\bwget\s+[^|;]+\s*\|\s*(?:(?:sudo|doas)\s+)?(?:-E\s+|[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:sh|bash|zsh|ksh|dash|csh|tcsh|fish)\b/i,
|
|
11868
|
+
hint: "Replace `wget \u2026 | bash` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
11869
|
+
},
|
|
11870
|
+
{
|
|
11871
|
+
code: "shell_process_substitution",
|
|
11872
|
+
pattern: /(?:bash|sh|zsh)\s*<\(\s*(?:curl|wget)\b/i,
|
|
11873
|
+
hint: "Replace `bash <(curl \u2026)` with a verifiable install path: download the script to a file, show the user `cat` of it for inspection, then `bash <file>` only after confirmation."
|
|
11874
|
+
},
|
|
11875
|
+
{
|
|
11876
|
+
code: "eval_atob",
|
|
11877
|
+
pattern: /\beval\s*\(\s*atob\b/i,
|
|
11878
|
+
hint: "Encoded eval (`eval(atob(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
11879
|
+
},
|
|
11880
|
+
{
|
|
11881
|
+
code: "eval_buffer_from",
|
|
11882
|
+
pattern: /\beval\s*\(\s*Buffer\.from\b/i,
|
|
11883
|
+
hint: "Encoded eval (`eval(Buffer.from(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
11884
|
+
},
|
|
11885
|
+
{
|
|
11886
|
+
code: "aws_access_key",
|
|
11887
|
+
pattern: /\b(?:AKIA|ASIA|AGPA)[0-9A-Z]{16}\b/,
|
|
11888
|
+
hint: "Remove the AWS access key from the body. Reference credentials via env var (`AWS_ACCESS_KEY_ID`); rotate the leaked key immediately if it was real."
|
|
11889
|
+
},
|
|
11890
|
+
{
|
|
11891
|
+
code: "private_key_block",
|
|
11892
|
+
pattern: /-----BEGIN\s+(?:[A-Z0-9]+\s+)?PRIVATE\s+KEY(?:\s+BLOCK)?-----/,
|
|
11893
|
+
hint: "Remove the private key block from the body. Reference keys via env var or a secret manager; rotate the leaked key immediately if it was real."
|
|
11894
|
+
},
|
|
11895
|
+
{
|
|
11896
|
+
code: "google_api_key",
|
|
11897
|
+
pattern: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
11898
|
+
hint: "Remove the Google API key from the body. Reference credentials via env var; rotate the leaked key immediately if it was real."
|
|
11899
|
+
},
|
|
11900
|
+
{
|
|
11901
|
+
code: "slack_token",
|
|
11902
|
+
pattern: /\b(?:xox[abpors]|xapp|xoxe)-[0-9a-zA-Z-]{10,}\b/,
|
|
11903
|
+
hint: "Remove the Slack token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
11904
|
+
},
|
|
11905
|
+
{
|
|
11906
|
+
code: "github_token",
|
|
11907
|
+
pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{36,}|github_pat_[A-Za-z0-9_]{60,})\b/,
|
|
11908
|
+
hint: "Remove the GitHub token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
11909
|
+
}
|
|
11910
|
+
];
|
|
11911
|
+
var STAGE_1_5_AUP_PATTERNS = [
|
|
11912
|
+
{
|
|
11913
|
+
// Stolen-card / bank-credential trafficking storefronts. Anchored to the
|
|
11914
|
+
// trafficking jargon that only appears in carding markets, not in the
|
|
11915
|
+
// defensive "how to detect carding" literature. `dumps` is also an
|
|
11916
|
+
// ordinary developer verb — "this hook dumps vendor files", "dumps store
|
|
11917
|
+
// state", "dumps market data to CSV" all matched the shared noun list —
|
|
11918
|
+
// so after `dumps` only sale framing (`shop`, `for sale`) counts, while
|
|
11919
|
+
// `cvv` and `fullz` keep the full list. Narrowed when comments started
|
|
11920
|
+
// running this table (2026-10-14).
|
|
11921
|
+
code: "aup_stolen_financial_data_market",
|
|
11922
|
+
pattern: /\b(?:cvv|fullz)\s+(?:shop|store|for\s+sale|market|vendor)\b|\bdumps\s+(?:shop|for\s+sale)\b|\bdumps?\s+with\s+pin\b|\bsell(?:ing)?\s+(?:cvv|fullz|dumps)\b/i,
|
|
11923
|
+
hint: "This reads as a storefront for trafficking stolen financial data, which violates the Kordis AUP. If the package is about DETECTING or DEFENDING against card fraud, reword it so it does not read as an offer to buy/sell stolen data."
|
|
11924
|
+
},
|
|
11925
|
+
{
|
|
11926
|
+
// DDoS-for-hire (booter/stresser) services sold on a subscription.
|
|
11927
|
+
code: "aup_ddos_for_hire",
|
|
11928
|
+
pattern: /\b(?:booter|stresser)\s+(?:service|panel|subscription)\b|\b(?:ddos|dstat)\s+for\s+hire\b|\bhire\s+(?:a\s+)?(?:booter|stresser|ddos)\b/i,
|
|
11929
|
+
hint: "This reads as a DDoS-for-hire (booter/stresser) service, which violates the Kordis AUP. Load-testing tooling is fine \u2014 describe it as testing infrastructure you own/are authorized to test, not a for-hire attack service."
|
|
11930
|
+
},
|
|
11931
|
+
{
|
|
11932
|
+
// Malware/stealer/ransomware sold or distributed as a product ("builder
|
|
11933
|
+
// for sale", "buy our stealer"). A skill that DEFENDS against these, or a
|
|
11934
|
+
// malware-analysis skill, does not use buy/sell framing.
|
|
11935
|
+
code: "aup_malware_sale",
|
|
11936
|
+
pattern: /\b(?:ransomware|stealer|infostealer|rat|keylogger|botnet)\s+(?:builder|kit|panel|source)\s+(?:for\s+sale|for\s+rent)\b|\b(?:buy|sell(?:ing)?|rent)\s+(?:our\s+)?(?:ransomware|stealer|infostealer|botnet)\b/i,
|
|
11937
|
+
hint: "This reads as an offer to sell/rent malware, which violates the Kordis AUP. Malware ANALYSIS and DEFENSE content is welcome \u2014 reword so it does not read as distributing a weaponized build."
|
|
11938
|
+
},
|
|
11939
|
+
{
|
|
11940
|
+
// Child sexual abuse material. Unlike the rest of this table this entry is
|
|
11941
|
+
// NOT anchored to solicitation: it matches the spelled-out phrases ("child
|
|
11942
|
+
// porn", "child pornography", "child sexual abuse material") wherever they
|
|
11943
|
+
// appear, child-safety and moderation education included. Only the
|
|
11944
|
+
// acronym "CSAM" on its own does not match ("how platforms detect CSAM
|
|
11945
|
+
// for NCMEC reporting" passes). Kept this broad on purpose (2026-10-14):
|
|
11946
|
+
// comments have no Stage-2 review after this table, so a miss costs more
|
|
11947
|
+
// than a false positive.
|
|
11948
|
+
code: "aup_csam",
|
|
11949
|
+
pattern: /\bchild\s+(?:porn(?:ography)?|sexual\s+abuse\s+material)\b/i,
|
|
11950
|
+
hint: "This reads as solicitation or distribution of child sexual abuse material, which is illegal and violates the Kordis AUP. This is a terminal, non-appealable rejection."
|
|
11951
|
+
},
|
|
11952
|
+
{
|
|
11953
|
+
// Drug / untraceable-weapon brokering with explicit no-id / untraceable /
|
|
11954
|
+
// dark-web fulfillment language — not a bare mention of a substance or
|
|
11955
|
+
// firearm topic (e.g. harm-reduction or policy content does not use this
|
|
11956
|
+
// buy/sell + untraceable-fulfillment framing together).
|
|
11957
|
+
code: "aup_drug_weapon_brokering",
|
|
11958
|
+
pattern: /\b(?:buy|sell(?:ing)?)\b[\s\S]{0,40}\b(?:cocaine|heroin|fentanyl|methamphetamine|untraceable firearms?|untraceable guns?)\b[\s\S]{0,40}\b(?:no id|untraceable|worldwide shipping|dark ?web)\b/i,
|
|
11959
|
+
hint: "This reads as brokering illegal drugs or untraceable weapons for sale, which violates the Kordis AUP. If the package is about DETECTING or REPORTING such activity, reword it so it does not read as an offer to buy/sell."
|
|
11960
|
+
},
|
|
11961
|
+
{
|
|
11962
|
+
// A directive to dox or swat a specific, named/tagged target — not a
|
|
11963
|
+
// bare mention of doxxing/swatting as a topic (e.g. "how to protect
|
|
11964
|
+
// yourself from doxxing" does not match).
|
|
11965
|
+
code: "aup_targeted_harassment_directive",
|
|
11966
|
+
pattern: /\b(?:dox|swat)\s+(?:this (?:guy|girl|person|user)|him|her|@[\w.]+)\b/i,
|
|
11967
|
+
hint: "This reads as a directive to dox or swat a specific target, which violates the Kordis AUP. Remove the targeted-harassment instruction."
|
|
11968
|
+
}
|
|
11969
|
+
];
|
|
11970
|
+
var DETERMINISTIC_REJECT_PATTERNS = [...STAGE_1_REJECT_PATTERNS, ...STAGE_1_5_AUP_PATTERNS];
|
|
11971
|
+
|
|
11676
11972
|
// ../../packages/api-client/src/errors.ts
|
|
11677
11973
|
function httpStatusToCode(status) {
|
|
11678
11974
|
if (status === 401) return "not_authenticated";
|
|
@@ -11873,6 +12169,23 @@ var KordisClient = class {
|
|
|
11873
12169
|
me() {
|
|
11874
12170
|
return this.request("/api/me");
|
|
11875
12171
|
}
|
|
12172
|
+
/**
|
|
12173
|
+
* The account's own data — the one registry answer that carries its `email`.
|
|
12174
|
+
* A GET, so it is retried once on a failed fetch like every other GET.
|
|
12175
|
+
*/
|
|
12176
|
+
exportMe() {
|
|
12177
|
+
return this.request("/api/me/export");
|
|
12178
|
+
}
|
|
12179
|
+
/**
|
|
12180
|
+
* Delete the signed-in registry account. Irreversible: the server compares
|
|
12181
|
+
* `confirm_username` with the current username and answers 204. The account
|
|
12182
|
+
* is renamed `deleted-<id>`; its published packages and versions stay at their
|
|
12183
|
+
* existing addresses, which still contain the old username. Never retried
|
|
12184
|
+
* (a DELETE), like every write here.
|
|
12185
|
+
*/
|
|
12186
|
+
deleteMe(body) {
|
|
12187
|
+
return this.request("/api/me", { method: "DELETE", body });
|
|
12188
|
+
}
|
|
11876
12189
|
listMyPackages(opts = {}) {
|
|
11877
12190
|
return this.request("/api/me/packages", {
|
|
11878
12191
|
query: {
|
|
@@ -11904,6 +12217,18 @@ var KordisClient = class {
|
|
|
11904
12217
|
deletePackage(username, slug) {
|
|
11905
12218
|
return this.request(`/api/packages/${seg(username)}/${seg(slug)}`, { method: "DELETE" });
|
|
11906
12219
|
}
|
|
12220
|
+
/**
|
|
12221
|
+
* Undo a delete. Author only, within 30 days of the deletion and before the
|
|
12222
|
+
* package's content is deleted; refusals are `conflict` (not deleted, too
|
|
12223
|
+
* late, content already deleted), `not_authorized` and `not_found`, each
|
|
12224
|
+
* with a hint.
|
|
12225
|
+
*/
|
|
12226
|
+
restorePackage(username, slug) {
|
|
12227
|
+
return this.request(
|
|
12228
|
+
`/api/packages/${seg(username)}/${seg(slug)}/restore`,
|
|
12229
|
+
{ method: "POST" }
|
|
12230
|
+
);
|
|
12231
|
+
}
|
|
11907
12232
|
getContent(username, slug, opts = {}) {
|
|
11908
12233
|
return this.request(
|
|
11909
12234
|
`/api/packages/${seg(username)}/${seg(slug)}/content`,
|
|
@@ -12209,7 +12534,7 @@ Wait ${err.retryAfter}s before trying again.` : "";
|
|
|
12209
12534
|
|
|
12210
12535
|
// src/commands/base-write.ts
|
|
12211
12536
|
init_errors();
|
|
12212
|
-
import { readFile as
|
|
12537
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
12213
12538
|
import { resolve as resolve4 } from "path";
|
|
12214
12539
|
import { Command as Command2 } from "commander";
|
|
12215
12540
|
|
|
@@ -12219,8 +12544,8 @@ init_brand();
|
|
|
12219
12544
|
init_errors();
|
|
12220
12545
|
init_ui();
|
|
12221
12546
|
import { createHash as createHash3 } from "crypto";
|
|
12222
|
-
import { appendFile, mkdir as mkdir6, readFile as
|
|
12223
|
-
import { join as
|
|
12547
|
+
import { appendFile, mkdir as mkdir6, readFile as readFile7, readdir as readdir3, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
12548
|
+
import { join as join9 } from "path";
|
|
12224
12549
|
import { z as z23 } from "zod";
|
|
12225
12550
|
|
|
12226
12551
|
// src/lib/ids.ts
|
|
@@ -12443,20 +12768,20 @@ function sanitizeRationale(raw) {
|
|
|
12443
12768
|
return capped2;
|
|
12444
12769
|
}
|
|
12445
12770
|
function ledgerDir(vaultRoot) {
|
|
12446
|
-
return
|
|
12771
|
+
return join9(vaultRoot, BRAND.vaultLedgerDir, "ledger");
|
|
12447
12772
|
}
|
|
12448
12773
|
function ledgerFile(vaultRoot, ts) {
|
|
12449
|
-
return
|
|
12774
|
+
return join9(ledgerDir(vaultRoot), `ops-${ts.slice(0, 7)}.jsonl`);
|
|
12450
12775
|
}
|
|
12451
12776
|
function hashContent(content) {
|
|
12452
12777
|
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
12453
12778
|
}
|
|
12454
12779
|
function stateDirFor(vaultRoot) {
|
|
12455
12780
|
const hash = createHash3("sha256").update(vaultRoot).digest("hex").slice(0, 16);
|
|
12456
|
-
return
|
|
12781
|
+
return join9(brandHome(), "state", hash);
|
|
12457
12782
|
}
|
|
12458
12783
|
function snapshotDir(vaultRoot) {
|
|
12459
|
-
return
|
|
12784
|
+
return join9(stateDirFor(vaultRoot), "undo");
|
|
12460
12785
|
}
|
|
12461
12786
|
async function appendOp(vaultRoot, op) {
|
|
12462
12787
|
const ts = (op.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12478,7 +12803,7 @@ async function appendOp(vaultRoot, op) {
|
|
|
12478
12803
|
if (op.snapshotBefore != null) {
|
|
12479
12804
|
const dir = snapshotDir(vaultRoot);
|
|
12480
12805
|
await mkdir6(dir, { recursive: true });
|
|
12481
|
-
await writeFile5(
|
|
12806
|
+
await writeFile5(join9(dir, `${record.op_id}.txt`), op.snapshotBefore, {
|
|
12482
12807
|
encoding: "utf8",
|
|
12483
12808
|
mode: 384
|
|
12484
12809
|
});
|
|
@@ -12490,7 +12815,7 @@ async function appendOp(vaultRoot, op) {
|
|
|
12490
12815
|
}
|
|
12491
12816
|
async function readSnapshot(vaultRoot, opId) {
|
|
12492
12817
|
try {
|
|
12493
|
-
return await
|
|
12818
|
+
return await readFile7(join9(snapshotDir(vaultRoot), `${opId}.txt`), "utf8");
|
|
12494
12819
|
} catch {
|
|
12495
12820
|
return null;
|
|
12496
12821
|
}
|
|
@@ -12498,14 +12823,14 @@ async function readSnapshot(vaultRoot, opId) {
|
|
|
12498
12823
|
async function readOps(vaultRoot) {
|
|
12499
12824
|
let files2;
|
|
12500
12825
|
try {
|
|
12501
|
-
files2 = (await
|
|
12826
|
+
files2 = (await readdir3(ledgerDir(vaultRoot))).filter((f) => /^ops-\d{4}-\d{2}\.jsonl$/.test(f)).sort();
|
|
12502
12827
|
} catch {
|
|
12503
12828
|
return { ops: [], skipped: 0 };
|
|
12504
12829
|
}
|
|
12505
12830
|
const ops = [];
|
|
12506
12831
|
let skipped = 0;
|
|
12507
12832
|
for (const file of files2) {
|
|
12508
|
-
const raw = await
|
|
12833
|
+
const raw = await readFile7(join9(ledgerDir(vaultRoot), file), "utf8");
|
|
12509
12834
|
for (const line of raw.split("\n")) {
|
|
12510
12835
|
if (line.trim() === "") continue;
|
|
12511
12836
|
try {
|
|
@@ -12801,7 +13126,7 @@ async function readBody(source) {
|
|
|
12801
13126
|
process.stdin.on("error", reject);
|
|
12802
13127
|
});
|
|
12803
13128
|
}
|
|
12804
|
-
return
|
|
13129
|
+
return readFile11(source, "utf8").catch(() => source);
|
|
12805
13130
|
}
|
|
12806
13131
|
|
|
12807
13132
|
// src/commands/brain-admin.ts
|
|
@@ -12814,8 +13139,8 @@ init_errors();
|
|
|
12814
13139
|
init_cloud_brain();
|
|
12815
13140
|
init_config();
|
|
12816
13141
|
init_held_back();
|
|
12817
|
-
import { readFile as
|
|
12818
|
-
import { join as
|
|
13142
|
+
import { readFile as readFile12, rm as rm5 } from "fs/promises";
|
|
13143
|
+
import { join as join12 } from "path";
|
|
12819
13144
|
import { Command as Command3 } from "commander";
|
|
12820
13145
|
init_vault();
|
|
12821
13146
|
init_ui();
|
|
@@ -13033,7 +13358,7 @@ function syncRulesCommand() {
|
|
|
13033
13358
|
return new Command3("sync-rules").description("copy this brain\u2019s read/index exclusions into a file that follows you").option("--vault <path>", "which brain").action(async (opts) => {
|
|
13034
13359
|
const { brain } = await resolveBrain(opts.vault);
|
|
13035
13360
|
const root = brain.root;
|
|
13036
|
-
const existingRaw = await
|
|
13361
|
+
const existingRaw = await readFile12(join12(root, RULES_FILE), "utf8").catch(() => void 0);
|
|
13037
13362
|
const current = await readRulesFile(root);
|
|
13038
13363
|
const [dotDeny, dotIgnore] = await Promise.all([
|
|
13039
13364
|
readLines(root, ".memgdeny"),
|
|
@@ -13061,7 +13386,7 @@ function syncRulesCommand() {
|
|
|
13061
13386
|
});
|
|
13062
13387
|
return;
|
|
13063
13388
|
}
|
|
13064
|
-
await writeThenRename(
|
|
13389
|
+
await writeThenRename(join12(root, RULES_FILE), next);
|
|
13065
13390
|
const after = await readRulesFile(root);
|
|
13066
13391
|
const lost = [
|
|
13067
13392
|
...neverRead.filter((e) => !after.raw.never_read.includes(e)),
|
|
@@ -13094,7 +13419,7 @@ function syncRulesCommand() {
|
|
|
13094
13419
|
});
|
|
13095
13420
|
}
|
|
13096
13421
|
async function readLines(root, name) {
|
|
13097
|
-
const raw = await
|
|
13422
|
+
const raw = await readFile12(join12(root, name), "utf8").catch(() => "");
|
|
13098
13423
|
return raw.split("\n").map((l) => l.replace(/\r$/, "").trim()).filter((l) => l !== "" && !l.startsWith("#"));
|
|
13099
13424
|
}
|
|
13100
13425
|
async function stopReadingHosted(path) {
|
|
@@ -13304,11 +13629,11 @@ init_index_build();
|
|
|
13304
13629
|
|
|
13305
13630
|
// src/lib/lock.ts
|
|
13306
13631
|
init_errors();
|
|
13307
|
-
import { mkdir as mkdir7, readFile as
|
|
13308
|
-
import { join as
|
|
13632
|
+
import { mkdir as mkdir7, readFile as readFile13, rm as rm6, writeFile as writeFile6 } from "fs/promises";
|
|
13633
|
+
import { join as join13 } from "path";
|
|
13309
13634
|
var STALE_MS = 6e4;
|
|
13310
13635
|
function lockPath(vaultRoot) {
|
|
13311
|
-
return
|
|
13636
|
+
return join13(stateDirFor(vaultRoot), "write.lock");
|
|
13312
13637
|
}
|
|
13313
13638
|
function holderIsGone(pid) {
|
|
13314
13639
|
try {
|
|
@@ -13351,7 +13676,7 @@ async function withFileLock(path, dir, fn) {
|
|
|
13351
13676
|
}
|
|
13352
13677
|
async function readLock(path) {
|
|
13353
13678
|
try {
|
|
13354
|
-
const parsed = JSON.parse(await
|
|
13679
|
+
const parsed = JSON.parse(await readFile13(path, "utf8"));
|
|
13355
13680
|
if (typeof parsed.pid === "number" && typeof parsed.at === "string") {
|
|
13356
13681
|
return { pid: parsed.pid, at: parsed.at };
|
|
13357
13682
|
}
|
|
@@ -13370,15 +13695,15 @@ init_errors();
|
|
|
13370
13695
|
import { Command as Command5 } from "commander";
|
|
13371
13696
|
|
|
13372
13697
|
// src/lib/events.ts
|
|
13373
|
-
import { appendFile as appendFile2, mkdir as mkdir8, readFile as
|
|
13374
|
-
import { join as
|
|
13698
|
+
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile15, readdir as readdir5 } from "fs/promises";
|
|
13699
|
+
import { join as join15 } from "path";
|
|
13375
13700
|
import { z as z24 } from "zod";
|
|
13376
13701
|
|
|
13377
13702
|
// src/lib/project.ts
|
|
13378
13703
|
init_config();
|
|
13379
13704
|
init_vault();
|
|
13380
|
-
import { readFile as
|
|
13381
|
-
import { basename, dirname as
|
|
13705
|
+
import { readFile as readFile14, stat as stat3 } from "fs/promises";
|
|
13706
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join14, resolve as resolve5 } from "path";
|
|
13382
13707
|
async function projectOf(cwd = process.cwd()) {
|
|
13383
13708
|
const here = await canonicalize(resolve5(cwd));
|
|
13384
13709
|
const repo = await findRepository(here);
|
|
@@ -13409,37 +13734,37 @@ async function boundName(key2) {
|
|
|
13409
13734
|
return cfg.projects.bindings.find((b) => resolve5(b.dir) === key2)?.name ?? null;
|
|
13410
13735
|
}
|
|
13411
13736
|
async function derivedName(repo) {
|
|
13412
|
-
const url = await originUrl(
|
|
13737
|
+
const url = await originUrl(join14(repo.commonDir, "config"));
|
|
13413
13738
|
const fromRemote = url === null ? null : ownerRepo(url);
|
|
13414
13739
|
if (fromRemote !== null) return { name: fromRemote, via: "remote" };
|
|
13415
|
-
const folder =
|
|
13740
|
+
const folder = basename2(repo.root);
|
|
13416
13741
|
return folder === "" ? { name: null, via: "none" } : { name: folder, via: "dirname" };
|
|
13417
13742
|
}
|
|
13418
13743
|
async function findRepository(from) {
|
|
13419
13744
|
let dir = from;
|
|
13420
13745
|
for (; ; ) {
|
|
13421
|
-
const found = await readGitLink(
|
|
13746
|
+
const found = await readGitLink(join14(dir, ".git"), dir);
|
|
13422
13747
|
if (found !== null) return found;
|
|
13423
|
-
const parent =
|
|
13748
|
+
const parent = dirname4(dir);
|
|
13424
13749
|
if (parent === dir) return null;
|
|
13425
13750
|
dir = parent;
|
|
13426
13751
|
}
|
|
13427
13752
|
}
|
|
13428
13753
|
async function readGitLink(dot, dir) {
|
|
13429
|
-
const info = await
|
|
13754
|
+
const info = await stat3(dot).catch(() => null);
|
|
13430
13755
|
if (info === null) return null;
|
|
13431
13756
|
if (info.isDirectory()) return { root: dir, commonDir: dot };
|
|
13432
|
-
const raw = await
|
|
13757
|
+
const raw = await readFile14(dot, "utf8").catch(() => null);
|
|
13433
13758
|
const pointed = raw === null ? null : /^gitdir:\s*(.+)$/m.exec(raw)?.[1]?.trim();
|
|
13434
13759
|
if (pointed === null || pointed === void 0 || pointed === "") return null;
|
|
13435
13760
|
const gitDir = isAbsolute2(pointed) ? resolve5(pointed) : resolve5(dir, pointed);
|
|
13436
|
-
const common = (await
|
|
13761
|
+
const common = (await readFile14(join14(gitDir, "commondir"), "utf8").catch(() => null))?.trim();
|
|
13437
13762
|
const commonDir = common === void 0 || common === "" ? gitDir : isAbsolute2(common) ? resolve5(common) : resolve5(gitDir, common);
|
|
13438
|
-
const root =
|
|
13763
|
+
const root = basename2(commonDir) === ".git" ? await canonicalize(dirname4(commonDir)) : dir;
|
|
13439
13764
|
return { root, commonDir };
|
|
13440
13765
|
}
|
|
13441
13766
|
async function originUrl(configPath2) {
|
|
13442
|
-
const text = await
|
|
13767
|
+
const text = await readFile14(configPath2, "utf8").catch(() => null);
|
|
13443
13768
|
if (text === null) return null;
|
|
13444
13769
|
let inOrigin = false;
|
|
13445
13770
|
let url = null;
|
|
@@ -13541,7 +13866,7 @@ var EventSchema = z24.object({
|
|
|
13541
13866
|
opened_id: z24.string().nullable().default(null)
|
|
13542
13867
|
});
|
|
13543
13868
|
function eventsFile(stateDir, ts) {
|
|
13544
|
-
return
|
|
13869
|
+
return join15(stateDir, `events-${ts.slice(0, 7)}.jsonl`);
|
|
13545
13870
|
}
|
|
13546
13871
|
function ledgerStateDir(target) {
|
|
13547
13872
|
return target.kind === "cloud" ? stateDirFor(`cloud:${target.brainId}`) : stateDirFor(target.brain.root);
|
|
@@ -13585,13 +13910,13 @@ async function readEvents(vaultRoot) {
|
|
|
13585
13910
|
async function readEventsAt(stateDir) {
|
|
13586
13911
|
let files2;
|
|
13587
13912
|
try {
|
|
13588
|
-
files2 = (await
|
|
13913
|
+
files2 = (await readdir5(stateDir)).filter((f) => /^events-\d{4}-\d{2}\.jsonl$/.test(f)).sort();
|
|
13589
13914
|
} catch {
|
|
13590
13915
|
return [];
|
|
13591
13916
|
}
|
|
13592
13917
|
const events = [];
|
|
13593
13918
|
for (const file of files2) {
|
|
13594
|
-
const raw = await
|
|
13919
|
+
const raw = await readFile15(join15(stateDir, file), "utf8");
|
|
13595
13920
|
for (const line of raw.split("\n")) {
|
|
13596
13921
|
if (line.trim() === "") continue;
|
|
13597
13922
|
try {
|
|
@@ -14039,8 +14364,8 @@ init_exclude();
|
|
|
14039
14364
|
init_adapter();
|
|
14040
14365
|
init_walk();
|
|
14041
14366
|
import { createHash as createHash5 } from "crypto";
|
|
14042
|
-
import { link, lstat as lstat3, mkdir as mkdir10, readFile as
|
|
14043
|
-
import { dirname as
|
|
14367
|
+
import { link, lstat as lstat3, mkdir as mkdir10, readFile as readFile16, rm as rm7, writeFile as writeFile8 } from "fs/promises";
|
|
14368
|
+
import { dirname as dirname5, join as join17, resolve as resolve7 } from "path";
|
|
14044
14369
|
var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
14045
14370
|
tier = 1;
|
|
14046
14371
|
#read;
|
|
@@ -14131,7 +14456,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14131
14456
|
}
|
|
14132
14457
|
const gate = await this.#gateExisting(path, content);
|
|
14133
14458
|
if (!gate.ok) throw gate.error;
|
|
14134
|
-
const current = await
|
|
14459
|
+
const current = await readFile16(gate.abs);
|
|
14135
14460
|
const currentHash = createHash5("sha256").update(current).digest("hex");
|
|
14136
14461
|
if (currentHash !== opts.baseRevision.contentHash) {
|
|
14137
14462
|
const st = await lstat3(gate.abs);
|
|
@@ -14155,7 +14480,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14155
14480
|
async create(path, content) {
|
|
14156
14481
|
const gate = await this.#gateNew(path, content);
|
|
14157
14482
|
if (!gate.ok) throw gate.error;
|
|
14158
|
-
await mkdir10(
|
|
14483
|
+
await mkdir10(dirname5(gate.abs), { recursive: true });
|
|
14159
14484
|
const tmp = `${gate.abs}.${process.pid}.tmp`;
|
|
14160
14485
|
try {
|
|
14161
14486
|
await writeFile8(tmp, content, { encoding: "utf8", mode: 384 });
|
|
@@ -14166,7 +14491,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14166
14491
|
if (st.isSymbolicLink()) {
|
|
14167
14492
|
throw new Error(`refusing to follow a symlink inside the vault: ${path}`);
|
|
14168
14493
|
}
|
|
14169
|
-
const existing = await
|
|
14494
|
+
const existing = await readFile16(gate.abs);
|
|
14170
14495
|
return {
|
|
14171
14496
|
ok: false,
|
|
14172
14497
|
reason: "revision_conflict",
|
|
@@ -14224,8 +14549,8 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14224
14549
|
return { ok: false, error: err };
|
|
14225
14550
|
}
|
|
14226
14551
|
const rel = relativeTo(this.#root, abs);
|
|
14227
|
-
const
|
|
14228
|
-
return
|
|
14552
|
+
const refusal2 = this.#refuse(rel, path, content);
|
|
14553
|
+
return refusal2 ? { ok: false, error: refusal2 } : { ok: true, abs, rel };
|
|
14229
14554
|
}
|
|
14230
14555
|
/**
|
|
14231
14556
|
* Gates for a path that must NOT already exist.
|
|
@@ -14250,7 +14575,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14250
14575
|
}
|
|
14251
14576
|
let cursor = resolve7(this.#root);
|
|
14252
14577
|
for (const segment of rel.split("/").slice(0, -1)) {
|
|
14253
|
-
cursor =
|
|
14578
|
+
cursor = join17(cursor, segment);
|
|
14254
14579
|
try {
|
|
14255
14580
|
const st = await lstat3(cursor);
|
|
14256
14581
|
if (st.isSymbolicLink()) {
|
|
@@ -14264,8 +14589,8 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14264
14589
|
return { ok: false, error: err };
|
|
14265
14590
|
}
|
|
14266
14591
|
}
|
|
14267
|
-
const
|
|
14268
|
-
return
|
|
14592
|
+
const refusal2 = this.#refuse(rel, path, content);
|
|
14593
|
+
return refusal2 ? { ok: false, error: refusal2 } : { ok: true, abs, rel };
|
|
14269
14594
|
}
|
|
14270
14595
|
/**
|
|
14271
14596
|
* The rules, applied to a write. Returns the refusal or null.
|
|
@@ -14335,7 +14660,7 @@ init_ui();
|
|
|
14335
14660
|
|
|
14336
14661
|
// src/commands/revise.ts
|
|
14337
14662
|
init_src();
|
|
14338
|
-
import { readFile as
|
|
14663
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
14339
14664
|
import { Command as Command7 } from "commander";
|
|
14340
14665
|
init_errors();
|
|
14341
14666
|
init_index_build();
|
|
@@ -14353,10 +14678,10 @@ init_vault();
|
|
|
14353
14678
|
|
|
14354
14679
|
// src/lib/guard-fast.ts
|
|
14355
14680
|
init_brand();
|
|
14356
|
-
import { randomUUID as
|
|
14357
|
-
import { mkdir as mkdir11, readdir as
|
|
14358
|
-
import { join as
|
|
14359
|
-
var fastDir = () =>
|
|
14681
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
14682
|
+
import { mkdir as mkdir11, readdir as readdir7, rename as rename4, stat as stat4, unlink as unlink4, utimes, writeFile as writeFile9 } from "fs/promises";
|
|
14683
|
+
import { join as join18 } from "path";
|
|
14684
|
+
var fastDir = () => join18(brandHome(), "guard", "fast");
|
|
14360
14685
|
var EMPTY_TTL_S = 120;
|
|
14361
14686
|
var SESSION_ID = /^[A-Za-z0-9-]{1,128}$/;
|
|
14362
14687
|
var EMPTY_MARK = /^(.+)\.empty\.(\d+)$/;
|
|
@@ -14364,14 +14689,14 @@ var RULES_CHANGED = ".rules-changed";
|
|
|
14364
14689
|
var TMP_PREFIX = ".tmp-";
|
|
14365
14690
|
async function place(name, asOfMs) {
|
|
14366
14691
|
await mkdir11(fastDir(), { recursive: true, mode: 448 });
|
|
14367
|
-
const tmp =
|
|
14692
|
+
const tmp = join18(fastDir(), `${TMP_PREFIX}${process.pid}-${randomUUID3()}`);
|
|
14368
14693
|
try {
|
|
14369
14694
|
await writeFile9(tmp, "", { mode: 384 });
|
|
14370
14695
|
const dated = (asOfMs - 1e3) / 1e3;
|
|
14371
14696
|
await utimes(tmp, dated, dated);
|
|
14372
|
-
await rename4(tmp,
|
|
14697
|
+
await rename4(tmp, join18(fastDir(), name));
|
|
14373
14698
|
} catch (err) {
|
|
14374
|
-
await
|
|
14699
|
+
await unlink4(tmp).catch(() => void 0);
|
|
14375
14700
|
throw err;
|
|
14376
14701
|
}
|
|
14377
14702
|
}
|
|
@@ -14383,31 +14708,31 @@ async function markEmpty(sessionId, asOfMs) {
|
|
|
14383
14708
|
if (!SESSION_ID.test(sessionId)) return;
|
|
14384
14709
|
const until = Math.floor(asOfMs / 1e3) + EMPTY_TTL_S;
|
|
14385
14710
|
await place(`${sessionId}.empty.${until}`, asOfMs).catch(() => void 0);
|
|
14386
|
-
await
|
|
14711
|
+
await unlink4(join18(fastDir(), sessionId)).catch(() => void 0);
|
|
14387
14712
|
}
|
|
14388
14713
|
async function forgetEmptyChecks() {
|
|
14389
|
-
await mkdir11(fastDir(), { recursive: true, mode: 448 }).then(() => writeFile9(
|
|
14390
|
-
const names = await
|
|
14714
|
+
await mkdir11(fastDir(), { recursive: true, mode: 448 }).then(() => writeFile9(join18(fastDir(), RULES_CHANGED), "", { mode: 384 })).catch(() => void 0);
|
|
14715
|
+
const names = await readdir7(fastDir()).catch(() => []);
|
|
14391
14716
|
await Promise.all(
|
|
14392
|
-
names.filter((name) => EMPTY_MARK.test(name)).map((name) =>
|
|
14717
|
+
names.filter((name) => EMPTY_MARK.test(name)).map((name) => unlink4(join18(fastDir(), name)).catch(() => void 0))
|
|
14393
14718
|
);
|
|
14394
14719
|
}
|
|
14395
14720
|
var SERVED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14396
14721
|
var TMP_TTL_MS = 60 * 1e3;
|
|
14397
14722
|
async function pruneFast(nowMs = Date.now()) {
|
|
14398
|
-
const names = await
|
|
14723
|
+
const names = await readdir7(fastDir()).catch(() => []);
|
|
14399
14724
|
const nowS = Math.floor(nowMs / 1e3);
|
|
14400
14725
|
await Promise.all(
|
|
14401
14726
|
names.map(async (name) => {
|
|
14402
|
-
const path =
|
|
14727
|
+
const path = join18(fastDir(), name);
|
|
14403
14728
|
const empty = EMPTY_MARK.exec(name);
|
|
14404
14729
|
if (empty) {
|
|
14405
|
-
if (Number(empty[2]) <= nowS) await
|
|
14730
|
+
if (Number(empty[2]) <= nowS) await unlink4(path).catch(() => void 0);
|
|
14406
14731
|
return;
|
|
14407
14732
|
}
|
|
14408
|
-
const info = await
|
|
14733
|
+
const info = await stat4(path).catch(() => null);
|
|
14409
14734
|
const ttl = name.startsWith(TMP_PREFIX) ? TMP_TTL_MS : SERVED_TTL_MS;
|
|
14410
|
-
if (info && nowMs - info.mtimeMs > ttl) await
|
|
14735
|
+
if (info && nowMs - info.mtimeMs > ttl) await unlink4(path).catch(() => void 0);
|
|
14411
14736
|
})
|
|
14412
14737
|
);
|
|
14413
14738
|
}
|
|
@@ -15365,7 +15690,7 @@ async function readInput(source) {
|
|
|
15365
15690
|
process.stdin.on("data", (chunk) => buf += chunk);
|
|
15366
15691
|
process.stdin.on("end", () => resolve17(buf));
|
|
15367
15692
|
process.stdin.on("error", reject);
|
|
15368
|
-
}) : await
|
|
15693
|
+
}) : await readFile17(source, "utf8").catch(() => source);
|
|
15369
15694
|
try {
|
|
15370
15695
|
return JSON.parse(raw);
|
|
15371
15696
|
} catch {
|
|
@@ -16685,16 +17010,645 @@ function nextStep3(renamed) {
|
|
|
16685
17010
|
return `Other machines that point at this brain keep showing the old name until their next command against it; nothing on them breaks \u2014 the pointer holds the id. \`--brain "${oneLine(renamed.renamed_from, WIDTH.title)}"\` no longer matches anywhere; use the new name or the id.`;
|
|
16686
17011
|
}
|
|
16687
17012
|
|
|
16688
|
-
// src/commands/delete-
|
|
17013
|
+
// src/commands/delete-account.ts
|
|
17014
|
+
import { Command as Command12 } from "commander";
|
|
17015
|
+
import prompts from "prompts";
|
|
16689
17016
|
init_errors();
|
|
16690
17017
|
init_api_client();
|
|
16691
|
-
|
|
16692
|
-
|
|
17018
|
+
|
|
17019
|
+
// src/lib/account-deletion-marker.ts
|
|
17020
|
+
init_atomic();
|
|
17021
|
+
init_brand();
|
|
17022
|
+
import { mkdir as mkdir12, readFile as readFile18, stat as stat5, unlink as unlink5 } from "fs/promises";
|
|
17023
|
+
import { join as join19 } from "path";
|
|
17024
|
+
import { z as z25 } from "zod";
|
|
17025
|
+
var SideSchema = z25.object({
|
|
17026
|
+
api_url: z25.string().min(1),
|
|
17027
|
+
/** When the delete request was sent — set before it goes, so a lost answer is visible. */
|
|
17028
|
+
requested_at: z25.string().min(1).nullable().default(null),
|
|
17029
|
+
/** When the server confirmed it. Null while that side is still to do. */
|
|
17030
|
+
deleted_at: z25.string().min(1).nullable().default(null)
|
|
17031
|
+
});
|
|
17032
|
+
var MarkerSchema = z25.object({
|
|
17033
|
+
version: z25.literal(1),
|
|
17034
|
+
started_at: z25.string().min(1),
|
|
17035
|
+
/** How the two accounts were matched before anything was deleted. */
|
|
17036
|
+
matched_by: z25.enum(["email", "federation"]),
|
|
17037
|
+
brain: SideSchema.extend({ account_id: z25.string().min(1) }),
|
|
17038
|
+
registry: SideSchema.extend({
|
|
17039
|
+
user_id: z25.string().min(1),
|
|
17040
|
+
username: z25.string().min(1)
|
|
17041
|
+
})
|
|
17042
|
+
});
|
|
17043
|
+
function accountDeletionMarkerPath() {
|
|
17044
|
+
return join19(brandHome(), "delete-account.json");
|
|
17045
|
+
}
|
|
17046
|
+
async function readAccountDeletionMarker() {
|
|
17047
|
+
try {
|
|
17048
|
+
const parsed = MarkerSchema.safeParse(
|
|
17049
|
+
JSON.parse(await readFile18(accountDeletionMarkerPath(), "utf8"))
|
|
17050
|
+
);
|
|
17051
|
+
return parsed.success ? parsed.data : null;
|
|
17052
|
+
} catch {
|
|
17053
|
+
return null;
|
|
17054
|
+
}
|
|
17055
|
+
}
|
|
17056
|
+
async function accountDeletionMarkerExists() {
|
|
17057
|
+
return stat5(accountDeletionMarkerPath()).then(
|
|
17058
|
+
() => true,
|
|
17059
|
+
() => false
|
|
17060
|
+
);
|
|
17061
|
+
}
|
|
17062
|
+
async function saveAccountDeletionMarker(marker) {
|
|
17063
|
+
await mkdir12(brandHome(), { recursive: true });
|
|
17064
|
+
await writeThenRename(accountDeletionMarkerPath(), `${JSON.stringify(marker, null, 2)}
|
|
17065
|
+
`);
|
|
17066
|
+
}
|
|
17067
|
+
async function clearAccountDeletionMarker() {
|
|
17068
|
+
await unlink5(accountDeletionMarkerPath()).catch(() => void 0);
|
|
17069
|
+
}
|
|
17070
|
+
|
|
17071
|
+
// src/commands/delete-account.ts
|
|
17072
|
+
init_config();
|
|
17073
|
+
init_credentials();
|
|
17074
|
+
init_pending_login();
|
|
17075
|
+
|
|
17076
|
+
// src/lib/registry-session.ts
|
|
17077
|
+
init_errors();
|
|
17078
|
+
init_credentials();
|
|
17079
|
+
init_version();
|
|
17080
|
+
function registryApiUrl(stored) {
|
|
17081
|
+
const override = process.env["KORDIS_API_URL"];
|
|
17082
|
+
return override !== void 0 && override !== "" ? override : stored;
|
|
17083
|
+
}
|
|
17084
|
+
async function requireRegistryCredentials() {
|
|
17085
|
+
const credentials = await readCredentials();
|
|
17086
|
+
const registry = credentials?.registry;
|
|
17087
|
+
if (!registry) {
|
|
17088
|
+
throw memgError(
|
|
17089
|
+
"not_linked",
|
|
17090
|
+
"this machine holds no registry identity",
|
|
17091
|
+
credentials === null ? "Nothing was sent. Sign in with `memgineering login` \u2014 it links the registry identity at the same time, so one sign-in covers both." : "Nothing was sent. This machine is signed in to a brain, and the registry half of that sign-in is missing \u2014 which is what a credential file written before federation looks like. Run `memgineering login` again; it links the registry identity too."
|
|
17092
|
+
);
|
|
17093
|
+
}
|
|
17094
|
+
return registry;
|
|
17095
|
+
}
|
|
17096
|
+
var RACE_WAIT_MS = 2e3;
|
|
17097
|
+
var RACE_POLL_MS = 50;
|
|
17098
|
+
function lostRace(err) {
|
|
17099
|
+
return err instanceof KordisApiError && err.status === 401 && isRefreshRace({ error: { code: err.code, flags: err.flags } });
|
|
17100
|
+
}
|
|
17101
|
+
async function newerRegistry(sent) {
|
|
17102
|
+
const deadline = Date.now() + RACE_WAIT_MS;
|
|
17103
|
+
for (; ; ) {
|
|
17104
|
+
const registry = (await readCredentials())?.registry;
|
|
17105
|
+
if (registry !== void 0 && registry.refresh_token !== sent) return registry;
|
|
17106
|
+
if (Date.now() >= deadline) return null;
|
|
17107
|
+
await new Promise((resolve17) => setTimeout(resolve17, RACE_POLL_MS));
|
|
17108
|
+
}
|
|
17109
|
+
}
|
|
17110
|
+
async function refreshRegistrySession(stored, opts = {}) {
|
|
17111
|
+
const attempt = async (registry) => {
|
|
17112
|
+
const baseUrl = (opts.apiUrlOf ?? ((r) => registryApiUrl(r.api_url)))(registry);
|
|
17113
|
+
const anonymous = new KordisClient({
|
|
17114
|
+
baseUrl,
|
|
17115
|
+
userAgent: `memgineering/${VERSION}`,
|
|
17116
|
+
...opts.fetchImpl ? { fetch: opts.fetchImpl } : {},
|
|
17117
|
+
...opts.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: opts.requestTimeoutMs }
|
|
17118
|
+
});
|
|
17119
|
+
return { registry, baseUrl, tokens: await anonymous.refreshSession(registry.refresh_token) };
|
|
17120
|
+
};
|
|
17121
|
+
try {
|
|
17122
|
+
return await attempt(stored);
|
|
17123
|
+
} catch (err) {
|
|
17124
|
+
if (!lostRace(err)) throw err;
|
|
17125
|
+
const newer = await newerRegistry(stored.refresh_token);
|
|
17126
|
+
if (newer === null) throw err;
|
|
17127
|
+
return attempt(newer);
|
|
17128
|
+
}
|
|
17129
|
+
}
|
|
17130
|
+
function refusal(err) {
|
|
17131
|
+
if (lostRace(err)) {
|
|
17132
|
+
return memgError(
|
|
17133
|
+
"not_authenticated",
|
|
17134
|
+
err.message,
|
|
17135
|
+
"This machine's registry sign-in was refreshed by another request at the same moment, so this command could not use it. Run this command again."
|
|
17136
|
+
);
|
|
17137
|
+
}
|
|
17138
|
+
const busyOrUnreachable = err instanceof KordisNetworkError || err instanceof KordisApiError && (err.status === 429 || err.status >= 500);
|
|
17139
|
+
return (busyOrUnreachable ? adaptRegistryError(err) : null) ?? memgError(
|
|
17140
|
+
"not_linked",
|
|
17141
|
+
`the registry refused this machine's stored sign-in: ${err instanceof Error ? err.message : String(err)}`,
|
|
17142
|
+
"Nothing was sent. The link between this brain and your registry account has expired or been revoked. `memgineering login` re-links both."
|
|
17143
|
+
);
|
|
17144
|
+
}
|
|
17145
|
+
async function openRegistrySession(fetchImpl) {
|
|
17146
|
+
const stored = await requireRegistryCredentials();
|
|
17147
|
+
let refreshed;
|
|
17148
|
+
try {
|
|
17149
|
+
refreshed = await refreshRegistrySession(stored, fetchImpl ? { fetchImpl } : {});
|
|
17150
|
+
} catch (err) {
|
|
17151
|
+
throw refusal(err);
|
|
17152
|
+
}
|
|
17153
|
+
const { registry, baseUrl, tokens } = refreshed;
|
|
17154
|
+
const credentialsRotated = await persistRotatedRefresh(
|
|
17155
|
+
registry,
|
|
17156
|
+
tokens.refresh_token,
|
|
17157
|
+
tokens.session_id
|
|
17158
|
+
);
|
|
17159
|
+
const client = new KordisClient({
|
|
17160
|
+
baseUrl,
|
|
17161
|
+
userAgent: `memgineering/${VERSION}`,
|
|
17162
|
+
getAccessToken: () => tokens.access_token,
|
|
17163
|
+
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
17164
|
+
});
|
|
17165
|
+
return { client, username: registry.username, apiUrl: baseUrl, credentialsRotated };
|
|
17166
|
+
}
|
|
17167
|
+
async function persistRotatedRefresh(sent, refreshToken, sessionId) {
|
|
17168
|
+
if (refreshToken === sent.refresh_token && sessionId === sent.session_id) return false;
|
|
17169
|
+
try {
|
|
17170
|
+
return await saveRotatedRegistry(sent.refresh_token, {
|
|
17171
|
+
refresh_token: refreshToken,
|
|
17172
|
+
session_id: sessionId
|
|
17173
|
+
});
|
|
17174
|
+
} catch {
|
|
17175
|
+
return false;
|
|
17176
|
+
}
|
|
17177
|
+
}
|
|
17178
|
+
|
|
17179
|
+
// src/commands/delete-account.ts
|
|
16693
17180
|
init_ui();
|
|
16694
|
-
|
|
16695
|
-
|
|
17181
|
+
var DELETE_ACCOUNT_PHRASE = "delete my account";
|
|
17182
|
+
var SUPPORT_ADDRESS = "support@memgineering.com";
|
|
17183
|
+
function deleteAccountCommand() {
|
|
17184
|
+
return new Command12("delete-account").description("delete your hosted brain account and your registry account, for good").option(
|
|
17185
|
+
"--confirm <phrase>",
|
|
17186
|
+
`the sentence "${DELETE_ACCOUNT_PHRASE}", typed exactly, authorising the deletion`
|
|
17187
|
+
).addHelpText(
|
|
17188
|
+
"after",
|
|
17189
|
+
`
|
|
17190
|
+
Irreversible, and server-side, in this order: the hosted brain account (every
|
|
17191
|
+
brain on it, every note, every token), then the registry account made by the
|
|
17192
|
+
same Google sign-in (profile, stars, follows, comment text, sign-ins; the account
|
|
17193
|
+
is renamed deleted-\u2026, and published packages stay at their existing addresses).
|
|
17194
|
+
Then this machine is signed out.
|
|
17195
|
+
Folders on this disk are never touched. Run this ONLY when the person has
|
|
17196
|
+
explicitly asked for their account to be deleted, and pass --confirm "${DELETE_ACCOUNT_PHRASE}".
|
|
17197
|
+
If a run stops halfway, run it again: it finishes the side that is left.
|
|
17198
|
+
\`memgineering delete-brain\` deletes one brain and keeps the account;
|
|
17199
|
+
\`memgineering logout\` only signs this machine out.`
|
|
17200
|
+
).action(async (opts) => {
|
|
17201
|
+
const confirmFlag = opts.confirm;
|
|
17202
|
+
if (confirmFlag !== void 0 && confirmFlag.startsWith("--")) {
|
|
17203
|
+
throw memgError(
|
|
17204
|
+
"invalid_input",
|
|
17205
|
+
`\`--confirm\` was given the flag \`${oneLine(confirmFlag, WIDTH.title)}\` as its sentence`,
|
|
17206
|
+
`Nothing was deleted. \`--confirm\` takes the sentence "${DELETE_ACCOUNT_PHRASE}", and the flag written after it was read as that sentence. Put the sentence, in quotes, immediately after \`--confirm\`.`
|
|
17207
|
+
);
|
|
17208
|
+
}
|
|
17209
|
+
const server = apiUrl();
|
|
17210
|
+
const stored = await readCredentials();
|
|
17211
|
+
const saved = await readAccountDeletionMarker();
|
|
17212
|
+
const marker = saved !== null && (stored === null || stored.account_id === saved.brain.account_id) ? saved : null;
|
|
17213
|
+
const brainDone = marker?.brain.deleted_at != null;
|
|
17214
|
+
const registryDone = marker?.registry.deleted_at != null;
|
|
17215
|
+
if (!brainDone) requireBrainSignIn(stored, server);
|
|
17216
|
+
if (!registryDone && stored?.registry === void 0) {
|
|
17217
|
+
throw noRegistrySignIn(marker);
|
|
17218
|
+
}
|
|
17219
|
+
if (confirmFlag !== void 0 && confirmFlag !== DELETE_ACCOUNT_PHRASE) {
|
|
17220
|
+
throw memgError(
|
|
17221
|
+
"invalid_input",
|
|
17222
|
+
"the confirmation is not the sentence this needs",
|
|
17223
|
+
`Nothing was deleted. The sentence is "${DELETE_ACCOUNT_PHRASE}" \u2014 exactly that, lower case, no punctuation. It deletes the hosted brain account with every brain and note on it, and the registry account; none of it comes back. Pass --confirm "${DELETE_ACCOUNT_PHRASE}" only if the person explicitly asked for their account to be deleted.`
|
|
17224
|
+
);
|
|
17225
|
+
}
|
|
17226
|
+
if (confirmFlag === void 0 && !isInteractive()) {
|
|
17227
|
+
throw memgError(
|
|
17228
|
+
"invalid_input",
|
|
17229
|
+
"cannot ask for confirmation here \u2014 this is not an interactive terminal",
|
|
17230
|
+
`Nothing was deleted. Two ways forward: run this in a terminal the person can see and let them type "${DELETE_ACCOUNT_PHRASE}"; or, once they have explicitly asked for their account to be deleted, pass --confirm "${DELETE_ACCOUNT_PHRASE}". Deciding for them is the one thing this gate exists to prevent.`
|
|
17231
|
+
);
|
|
17232
|
+
}
|
|
17233
|
+
const brain = brainDone ? null : await brainAccount(stored, marker);
|
|
17234
|
+
const registry = registryDone ? null : await registryAccount(stored?.registry, marker);
|
|
17235
|
+
const match = matchAccounts({ stored, marker, brain, registry });
|
|
17236
|
+
if (confirmFlag === void 0) {
|
|
17237
|
+
const typed = await retypeThePhrase({ brain, registry, marker, match });
|
|
17238
|
+
if (typed !== DELETE_ACCOUNT_PHRASE) {
|
|
17239
|
+
printHuman(c.yellow("Cancelled \u2014 nothing was deleted."));
|
|
17240
|
+
return;
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17243
|
+
const record = marker ?? {
|
|
17244
|
+
version: 1,
|
|
17245
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17246
|
+
matched_by: match.by === "earlier-run" ? match.matched_by : match.by,
|
|
17247
|
+
brain: {
|
|
17248
|
+
api_url: stored.api_url,
|
|
17249
|
+
account_id: brain.id,
|
|
17250
|
+
requested_at: null,
|
|
17251
|
+
deleted_at: null
|
|
17252
|
+
},
|
|
17253
|
+
registry: {
|
|
17254
|
+
api_url: registry.apiUrl,
|
|
17255
|
+
user_id: registry.id,
|
|
17256
|
+
username: registry.username,
|
|
17257
|
+
requested_at: null,
|
|
17258
|
+
deleted_at: null
|
|
17259
|
+
}
|
|
17260
|
+
};
|
|
17261
|
+
try {
|
|
17262
|
+
await saveAccountDeletionMarker(record);
|
|
17263
|
+
} catch (err) {
|
|
17264
|
+
throw memgError(
|
|
17265
|
+
"filesystem_error",
|
|
17266
|
+
`could not write ${accountDeletionMarkerPath()}`,
|
|
17267
|
+
`Nothing was deleted. This command records its progress there so a run that stops halfway can be finished, and it will not start without it. Free the disk or fix the permissions of that folder, then run this again. Underlying cause: ${err instanceof Error ? err.message : String(err)}`
|
|
17268
|
+
);
|
|
17269
|
+
}
|
|
17270
|
+
if (brain !== null) {
|
|
17271
|
+
record.brain.requested_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17272
|
+
await saveBestEffort(record);
|
|
17273
|
+
await deleteBrainAccount(stored, registry);
|
|
17274
|
+
record.brain.deleted_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17275
|
+
await saveBestEffort(record);
|
|
17276
|
+
}
|
|
17277
|
+
if (registry !== null) {
|
|
17278
|
+
record.registry.requested_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17279
|
+
await saveBestEffort(record);
|
|
17280
|
+
await deleteRegistryAccount(registry);
|
|
17281
|
+
record.registry.deleted_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17282
|
+
await saveBestEffort(record);
|
|
17283
|
+
}
|
|
17284
|
+
const pointerCleared = await signOutHere(record.brain.api_url);
|
|
17285
|
+
await clearAccountDeletionMarker();
|
|
17286
|
+
const hint = "Both accounts are gone and this machine is signed out. Signing in again with the same Google account creates a new, empty account. Folders on this disk were not touched.";
|
|
17287
|
+
printDual({
|
|
17288
|
+
json: {
|
|
17289
|
+
deleted: true,
|
|
17290
|
+
brain: {
|
|
17291
|
+
status: brain === null ? "deleted-earlier" : "deleted",
|
|
17292
|
+
account_id: record.brain.account_id,
|
|
17293
|
+
api_url: record.brain.api_url
|
|
17294
|
+
},
|
|
17295
|
+
registry: {
|
|
17296
|
+
status: registry === null ? "deleted-earlier" : "deleted",
|
|
17297
|
+
user_id: record.registry.user_id,
|
|
17298
|
+
username: record.registry.username,
|
|
17299
|
+
api_url: record.registry.api_url
|
|
17300
|
+
},
|
|
17301
|
+
matched: match,
|
|
17302
|
+
credentials_cleared: true,
|
|
17303
|
+
pointer_cleared: pointerCleared,
|
|
17304
|
+
local_folders: "untouched",
|
|
17305
|
+
hint
|
|
17306
|
+
},
|
|
17307
|
+
human: () => {
|
|
17308
|
+
printHuman(c.green("\u2713 Account deleted."));
|
|
17309
|
+
printHuman(
|
|
17310
|
+
c.gray(
|
|
17311
|
+
brain === null ? " brain the hosted account was deleted by an earlier run" : " brain the hosted account, every brain and note on it, and every token \u2014 gone"
|
|
17312
|
+
)
|
|
17313
|
+
);
|
|
17314
|
+
printHuman(
|
|
17315
|
+
c.gray(
|
|
17316
|
+
` registry ${oneLine(record.registry.username, WIDTH.title)} \u2014 ` + (registry === null ? "deleted by an earlier run" : "profile, stars, follows, comment text and sign-ins removed; renamed deleted-\u2026; published packages stay at their existing addresses")
|
|
17317
|
+
)
|
|
17318
|
+
);
|
|
17319
|
+
printHuman(c.gray(` matched ${matchLine(match)}`));
|
|
17320
|
+
printHuman(c.gray(" here signed out; this machine\u2019s credentials are removed"));
|
|
17321
|
+
if (pointerCleared) {
|
|
17322
|
+
printHuman(c.gray(" and it no longer points at a hosted brain"));
|
|
17323
|
+
}
|
|
17324
|
+
printHuman(c.gray(`
|
|
17325
|
+
${hint}`));
|
|
17326
|
+
}
|
|
17327
|
+
});
|
|
17328
|
+
});
|
|
17329
|
+
}
|
|
17330
|
+
function requireBrainSignIn(stored, server) {
|
|
17331
|
+
if (stored !== null && stored.api_url === server) return;
|
|
17332
|
+
if (stored !== null) {
|
|
17333
|
+
throw memgError(
|
|
17334
|
+
"not_signed_in",
|
|
17335
|
+
`this machine is signed in to ${oneLine(stored.api_url, WIDTH.path)}, not ${oneLine(server, WIDTH.path)}`,
|
|
17336
|
+
"Nothing was deleted. The account to delete is on the server this machine signed in to. If MEMGINEERING_API_URL points somewhere else, unset it and run this again."
|
|
17337
|
+
);
|
|
17338
|
+
}
|
|
17339
|
+
throw memgError(
|
|
17340
|
+
"not_signed_in",
|
|
17341
|
+
"this machine is not signed in to a memgineering account",
|
|
17342
|
+
`Nothing was deleted, and there may be nothing to delete. A brain kept in a folder on this machine has no account \u2014 it is the person\u2019s own files, and this command never touches them; deleting the folder deletes it (\`memgineering unlink <folder>\` first makes this tool forget it). To delete a hosted account, sign in to it first \u2014 \`memgineering login --emit-only\`, show the person the code it prints, and run this again once they approve. Without a sign-in, mail ${SUPPORT_ADDRESS} from the Google address of the account.`
|
|
17343
|
+
);
|
|
17344
|
+
}
|
|
17345
|
+
function noRegistrySignIn(marker) {
|
|
17346
|
+
if (marker !== null) {
|
|
17347
|
+
return memgError(
|
|
17348
|
+
"not_linked",
|
|
17349
|
+
"the brain account is already deleted, and this machine holds no registry sign-in to finish with",
|
|
17350
|
+
`The hosted brain account was deleted by an earlier run of this command and cannot be undone. The registry account \`${oneLine(marker.registry.username, WIDTH.title)}\` still needs deleting, and that needs this machine's registry sign-in, which is gone. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${oneLine(marker.registry.username, WIDTH.title)}\`, and it is deleted by hand.`
|
|
17351
|
+
);
|
|
17352
|
+
}
|
|
17353
|
+
return memgError(
|
|
17354
|
+
"not_linked",
|
|
17355
|
+
"this machine is signed in to a brain account but holds no registry sign-in",
|
|
17356
|
+
`Nothing was deleted. This command deletes the brain account and the registry account together, and will not delete one alone. The registry half of the sign-in is missing \u2014 which is what a sign-in made before the two were linked looks like. Run \`memgineering logout\` and then \`memgineering login\` (it links the registry account too), then run this again. Or mail ${SUPPORT_ADDRESS} from the Google address of the account.`
|
|
17357
|
+
);
|
|
17358
|
+
}
|
|
17359
|
+
async function brainAccount(stored, marker) {
|
|
17360
|
+
let body;
|
|
17361
|
+
try {
|
|
17362
|
+
body = (await apiRequest({
|
|
17363
|
+
method: "GET",
|
|
17364
|
+
path: "/v1/me",
|
|
17365
|
+
token: stored.token,
|
|
17366
|
+
baseUrl: stored.api_url
|
|
17367
|
+
})).body;
|
|
17368
|
+
} catch (err) {
|
|
17369
|
+
if (!(err instanceof MemgError)) throw err;
|
|
17370
|
+
if (err.code === "not_authenticated" && marker?.brain.requested_at) {
|
|
17371
|
+
throw memgError(
|
|
17372
|
+
"not_authenticated",
|
|
17373
|
+
"this machine\u2019s brain sign-in no longer works, after an earlier run asked for the account to be deleted",
|
|
17374
|
+
`Nothing more was deleted. An earlier run of this command asked the brain server to delete this account at ${marker.brain.requested_at} and did not hear the answer. A sign-in that stops working is what a deleted account looks like, and also what a revoked sign-in looks like, so this command will not guess and delete the registry account on that basis. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming registry account \`${oneLine(marker.registry.username, WIDTH.title)}\`, and both are finished by hand.`
|
|
17375
|
+
);
|
|
17376
|
+
}
|
|
17377
|
+
if (err.code === "not_authenticated") {
|
|
17378
|
+
const name = stored.registry === void 0 ? "" : ` \`${oneLine(stored.registry.username, WIDTH.title)}\``;
|
|
17379
|
+
const file = await accountDeletionMarkerExists() ? ` A progress file from an earlier run is on disk (${accountDeletionMarkerPath()}) but could not be used for this account.` : "";
|
|
17380
|
+
throw memgError(
|
|
17381
|
+
"not_authenticated",
|
|
17382
|
+
"the brain server no longer accepts this machine\u2019s sign-in",
|
|
17383
|
+
`This run deleted nothing. Either the sign-in was revoked or has expired \u2014 then \`memgineering login --emit-only\`, show the person the code it prints, and run this again once they approve \u2014 or this account was already deleted by an earlier run of this command whose progress file is gone.${file} If that may be so, the registry account${name} may still exist: mail ${SUPPORT_ADDRESS} from the Google address of the account${name === "" ? "" : `, naming${name}`}.`
|
|
17384
|
+
);
|
|
17385
|
+
}
|
|
17386
|
+
if (err.code === "server_unreachable") {
|
|
17387
|
+
throw memgError(
|
|
17388
|
+
"server_unreachable",
|
|
17389
|
+
err.message,
|
|
17390
|
+
`This run deleted nothing \u2014 neither the brain account nor the registry account. The brain server could not be reached: check the connection, or MEMGINEERING_API_URL if the brain server is somewhere else, then run this again.`
|
|
17391
|
+
);
|
|
17392
|
+
}
|
|
17393
|
+
throw new MemgError({
|
|
17394
|
+
code: err.code,
|
|
17395
|
+
message: err.message,
|
|
17396
|
+
hint: `This run deleted nothing \u2014 neither the brain account nor the registry account. ${err.hint}`
|
|
17397
|
+
});
|
|
17398
|
+
}
|
|
17399
|
+
const account = body?.account;
|
|
17400
|
+
if (!account || typeof account.id !== "string") {
|
|
17401
|
+
throw memgError(
|
|
17402
|
+
"server_refused",
|
|
17403
|
+
`${stored.api_url} answered /v1/me without an account`,
|
|
17404
|
+
"Nothing was deleted. This is not an answer a memgineering brain server gives. If MEMGINEERING_API_URL points at something else, unset it and run this again."
|
|
17405
|
+
);
|
|
17406
|
+
}
|
|
17407
|
+
if (account.id !== stored.account_id) {
|
|
17408
|
+
throw mismatch(
|
|
17409
|
+
`this machine's credentials name brain account ${oneLine(stored.account_id, WIDTH.handle)}, and its token belongs to ${oneLine(account.id, WIDTH.handle)}`
|
|
17410
|
+
);
|
|
17411
|
+
}
|
|
17412
|
+
return {
|
|
17413
|
+
id: account.id,
|
|
17414
|
+
email: typeof account.email === "string" && account.email !== "" ? account.email : null,
|
|
17415
|
+
display_name: typeof account.display_name === "string" ? account.display_name : null
|
|
17416
|
+
};
|
|
17417
|
+
}
|
|
17418
|
+
async function registryAccount(snapshot, marker) {
|
|
17419
|
+
let session;
|
|
17420
|
+
try {
|
|
17421
|
+
session = await openRegistrySession();
|
|
17422
|
+
} catch (err) {
|
|
17423
|
+
if (err instanceof MemgError && err.code === "not_linked") {
|
|
17424
|
+
throw memgError(
|
|
17425
|
+
"not_linked",
|
|
17426
|
+
err.message,
|
|
17427
|
+
marker !== null && marker.brain.deleted_at !== null ? `The hosted brain account was deleted by an earlier run and cannot be undone. The registry account \`${oneLine(marker.registry.username, WIDTH.title)}\` still needs deleting, and this machine's registry sign-in no longer works \u2014 if an earlier run's registry request reached the server, the account may already be gone, which ends sign-ins the same way. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${oneLine(marker.registry.username, WIDTH.title)}\`, and it is finished by hand.` : `Nothing was deleted. The registry sign-in on this machine has expired or been revoked. Run \`memgineering logout\` and then \`memgineering login\` (it links the registry account again), then run this again. Or mail ${SUPPORT_ADDRESS} from the Google address of the account.`
|
|
17428
|
+
);
|
|
17429
|
+
}
|
|
17430
|
+
throw withNothingDeleted(err, marker);
|
|
17431
|
+
}
|
|
17432
|
+
let exported;
|
|
17433
|
+
try {
|
|
17434
|
+
exported = await session.client.exportMe();
|
|
17435
|
+
} catch (err) {
|
|
17436
|
+
throw withNothingDeleted(err, marker);
|
|
17437
|
+
}
|
|
17438
|
+
const user = exported?.user;
|
|
17439
|
+
if (!user || typeof user.id !== "string" || typeof user.username !== "string") {
|
|
17440
|
+
throw memgError(
|
|
17441
|
+
"server_refused",
|
|
17442
|
+
`${session.apiUrl} answered /api/me/export without an account`,
|
|
17443
|
+
"Nothing was deleted. This is not an answer the registry gives. If KORDIS_API_URL points at something else, unset it and run this again."
|
|
17444
|
+
);
|
|
17445
|
+
}
|
|
17446
|
+
if (snapshot.user_id !== user.id) {
|
|
17447
|
+
throw mismatch(
|
|
17448
|
+
`this run began with a registry sign-in for registry account ${oneLine(snapshot.user_id, WIDTH.handle)}, and the registry answered for ${oneLine(user.id, WIDTH.handle)}`
|
|
17449
|
+
);
|
|
17450
|
+
}
|
|
17451
|
+
return {
|
|
17452
|
+
client: session.client,
|
|
17453
|
+
apiUrl: session.apiUrl,
|
|
17454
|
+
id: user.id,
|
|
17455
|
+
username: user.username,
|
|
17456
|
+
email: typeof user.email === "string" && user.email !== "" ? user.email : null,
|
|
17457
|
+
linkedAt: snapshot.linked_at
|
|
17458
|
+
};
|
|
17459
|
+
}
|
|
17460
|
+
function withNothingDeleted(err, marker) {
|
|
17461
|
+
const adapted = err instanceof MemgError ? err : adaptRegistryError(err);
|
|
17462
|
+
if (adapted === null) return err;
|
|
17463
|
+
const prefix = marker !== null && marker.brain.deleted_at !== null ? "Nothing more was deleted: the brain account is already gone, and the registry account is not deleted yet \u2014 run this again once the registry answers." : "Nothing was deleted \u2014 neither the brain account nor the registry account.";
|
|
17464
|
+
return new MemgError({
|
|
17465
|
+
code: adapted.code,
|
|
17466
|
+
message: adapted.message,
|
|
17467
|
+
hint: `${prefix} ${adapted.hint}`
|
|
17468
|
+
});
|
|
17469
|
+
}
|
|
17470
|
+
function matchAccounts(args) {
|
|
17471
|
+
const { marker, brain, registry } = args;
|
|
17472
|
+
if (marker !== null) {
|
|
17473
|
+
if (registry !== null && registry.id !== marker.registry.user_id) {
|
|
17474
|
+
throw mismatch(
|
|
17475
|
+
`an earlier run matched registry account \`${oneLine(marker.registry.username, WIDTH.title)}\`, and this machine's registry sign-in is now for \`${oneLine(registry.username, WIDTH.title)}\``
|
|
17476
|
+
);
|
|
17477
|
+
}
|
|
17478
|
+
return { by: "earlier-run", matched_by: marker.matched_by };
|
|
17479
|
+
}
|
|
17480
|
+
const b = brain;
|
|
17481
|
+
const r = registry;
|
|
17482
|
+
if (b.email !== null && r.email !== null) {
|
|
17483
|
+
if (normaliseEmail(b.email) !== normaliseEmail(r.email)) {
|
|
17484
|
+
throw mismatch(
|
|
17485
|
+
`the brain account's email is ${oneLine(b.email, WIDTH.title)} and the registry account's is ${oneLine(r.email, WIDTH.title)}`
|
|
17486
|
+
);
|
|
17487
|
+
}
|
|
17488
|
+
return { by: "email", email: b.email };
|
|
17489
|
+
}
|
|
17490
|
+
return { by: "federation", linked_at: r.linkedAt };
|
|
17491
|
+
}
|
|
17492
|
+
function normaliseEmail(email) {
|
|
17493
|
+
return email.trim().toLowerCase();
|
|
17494
|
+
}
|
|
17495
|
+
function mismatch(detail) {
|
|
17496
|
+
return memgError(
|
|
17497
|
+
"account_mismatch",
|
|
17498
|
+
`the brain account and the registry account do not look like the same person: ${detail}`,
|
|
17499
|
+
`Nothing was deleted. This command deletes both accounts together and only when they are one person's. Run \`memgineering logout\` and then \`memgineering login\` with the Google account whose accounts should be deleted, then run this again. If they really are one person's, mail ${SUPPORT_ADDRESS} from that Google address.`
|
|
17500
|
+
);
|
|
17501
|
+
}
|
|
17502
|
+
function matchLine(match) {
|
|
17503
|
+
switch (match.by) {
|
|
17504
|
+
case "email":
|
|
17505
|
+
return `same person \u2014 both accounts carry ${oneLine(match.email, WIDTH.title)}`;
|
|
17506
|
+
case "federation":
|
|
17507
|
+
return `same person \u2014 one of the two accounts has no email on record, so the match is this machine's registry sign-in, which the brain sign-in issued for the same Google account (linked ${oneLine(match.linked_at, WIDTH.title)})`;
|
|
17508
|
+
case "earlier-run":
|
|
17509
|
+
return `same person \u2014 matched by ${match.matched_by} before an earlier run deleted anything`;
|
|
17510
|
+
}
|
|
17511
|
+
}
|
|
17512
|
+
async function retypeThePhrase(args) {
|
|
17513
|
+
const { brain, registry, marker, match } = args;
|
|
17514
|
+
printHuman(c.bold("\nDelete your account?"));
|
|
17515
|
+
if (brain !== null) {
|
|
17516
|
+
const who = brain.email ?? brain.display_name ?? brain.id;
|
|
17517
|
+
printHuman(
|
|
17518
|
+
c.gray(
|
|
17519
|
+
` brain ${oneLine(who, WIDTH.title)} \u2014 every brain on it, every note, every token`
|
|
17520
|
+
)
|
|
17521
|
+
);
|
|
17522
|
+
} else {
|
|
17523
|
+
printHuman(c.gray(" brain already deleted by an earlier run"));
|
|
17524
|
+
}
|
|
17525
|
+
const username = registry?.username ?? marker?.registry.username ?? "";
|
|
17526
|
+
printHuman(
|
|
17527
|
+
c.gray(
|
|
17528
|
+
registry !== null ? ` registry ${oneLine(username, WIDTH.title)} \u2014 profile, stars, follows, comment text, sign-ins` : ` registry ${oneLine(username, WIDTH.title)} \u2014 already deleted by an earlier run`
|
|
17529
|
+
)
|
|
17530
|
+
);
|
|
17531
|
+
printHuman(c.gray(` matched ${matchLine(match)}`));
|
|
17532
|
+
printHuman(
|
|
17533
|
+
c.yellow(
|
|
17534
|
+
"\n None of this comes back. The registry account is renamed deleted-\u2026; published\n packages stay at their existing addresses. Folders on this disk are not\n touched. Signing in again later makes a new, empty account."
|
|
17535
|
+
)
|
|
17536
|
+
);
|
|
17537
|
+
const { typed } = await prompts({
|
|
17538
|
+
type: "text",
|
|
17539
|
+
name: "typed",
|
|
17540
|
+
message: `Type "${DELETE_ACCOUNT_PHRASE}" to delete both accounts`
|
|
17541
|
+
});
|
|
17542
|
+
return typed;
|
|
17543
|
+
}
|
|
17544
|
+
async function deleteBrainAccount(stored, registry) {
|
|
17545
|
+
try {
|
|
17546
|
+
await apiRequest({
|
|
17547
|
+
method: "DELETE",
|
|
17548
|
+
path: "/v1/account",
|
|
17549
|
+
token: stored.token,
|
|
17550
|
+
baseUrl: stored.api_url,
|
|
17551
|
+
body: { confirm: DELETE_ACCOUNT_PHRASE }
|
|
17552
|
+
});
|
|
17553
|
+
return;
|
|
17554
|
+
} catch (err) {
|
|
17555
|
+
if (!(err instanceof MemgError)) throw err;
|
|
17556
|
+
if (err.code === "server_unreachable" && await brainTokenIsDead(stored)) {
|
|
17557
|
+
return;
|
|
17558
|
+
}
|
|
17559
|
+
const registryLine = registry === null ? "" : ` The registry account \`${oneLine(registry.username, WIDTH.title)}\` was not touched.`;
|
|
17560
|
+
if (err.code === "server_unreachable") {
|
|
17561
|
+
throw new MemgError({
|
|
17562
|
+
code: err.code,
|
|
17563
|
+
message: err.message,
|
|
17564
|
+
hint: `The brain server did not confirm the deletion, and the request may have reached it.${registryLine} Check the connection, then run this command again: it asks the server first. If it then says this machine's brain sign-in no longer works, the brain account was deleted \u2014 it will not delete the registry account on that basis; it says how to finish by mail.`
|
|
17565
|
+
});
|
|
17566
|
+
}
|
|
17567
|
+
throw new MemgError({
|
|
17568
|
+
code: err.code,
|
|
17569
|
+
message: err.message,
|
|
17570
|
+
hint: `The brain account was not confirmed deleted.${registryLine} ${err.hint} Then run this again.`
|
|
17571
|
+
});
|
|
17572
|
+
}
|
|
17573
|
+
}
|
|
17574
|
+
async function brainTokenIsDead(stored) {
|
|
17575
|
+
try {
|
|
17576
|
+
await apiRequest({
|
|
17577
|
+
method: "GET",
|
|
17578
|
+
path: "/v1/me",
|
|
17579
|
+
token: stored.token,
|
|
17580
|
+
baseUrl: stored.api_url
|
|
17581
|
+
});
|
|
17582
|
+
return false;
|
|
17583
|
+
} catch (err) {
|
|
17584
|
+
return err instanceof MemgError && err.code === "not_authenticated";
|
|
17585
|
+
}
|
|
17586
|
+
}
|
|
17587
|
+
async function deleteRegistryAccount(registry) {
|
|
17588
|
+
try {
|
|
17589
|
+
await registry.client.deleteMe({ confirm_username: registry.username });
|
|
17590
|
+
return;
|
|
17591
|
+
} catch (err) {
|
|
17592
|
+
if (err instanceof KordisNetworkError && await registrySessionIsDead(registry)) {
|
|
17593
|
+
return;
|
|
17594
|
+
}
|
|
17595
|
+
const adapted = adaptRegistryError(err) ?? memgError(
|
|
17596
|
+
"server_refused",
|
|
17597
|
+
err instanceof Error ? err.message : String(err),
|
|
17598
|
+
"The registry refused the request without saying why."
|
|
17599
|
+
);
|
|
17600
|
+
const name = oneLine(registry.username, WIDTH.title);
|
|
17601
|
+
const unconfirmed = err instanceof KordisNetworkError;
|
|
17602
|
+
const said = err instanceof KordisApiError && err.hint !== void 0 && err.hint !== "" ? ` The registry said: ${err.hint}` : "";
|
|
17603
|
+
throw memgError(
|
|
17604
|
+
"account_partly_deleted",
|
|
17605
|
+
unconfirmed ? `the hosted brain account is deleted; the registry did not confirm deleting \`${name}\` \u2014 ${adapted.code}: ${oneLine(adapted.message, WIDTH.text)}` : `the hosted brain account is deleted; the registry account \`${name}\` is not \u2014 ${adapted.code}: ${oneLine(adapted.message, WIDTH.text)}`,
|
|
17606
|
+
`The brain half is done and cannot be undone. ${unconfirmed ? `The registry did not answer, so it is not known whether \`${name}\` was deleted.` : `The registry account \`${name}\` still exists.`}${said}
|
|
17607
|
+
Run \`memgineering delete-account --confirm "${DELETE_ACCOUNT_PHRASE}"\` again: it skips the brain and deletes only the registry account${unconfirmed ? " if it is still there; if this machine's registry sign-in then no longer works, the account is gone or the sign-in was revoked, and it says how to finish by mail" : ""}. If it keeps failing, mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${name}\`.`
|
|
17608
|
+
);
|
|
17609
|
+
}
|
|
17610
|
+
}
|
|
17611
|
+
async function registrySessionIsDead(registry) {
|
|
17612
|
+
try {
|
|
17613
|
+
await registry.client.exportMe();
|
|
17614
|
+
return false;
|
|
17615
|
+
} catch (err) {
|
|
17616
|
+
return err instanceof KordisApiError && err.status === 401;
|
|
17617
|
+
}
|
|
17618
|
+
}
|
|
17619
|
+
async function signOutHere(brainApiUrl) {
|
|
17620
|
+
await clearPendingLogin();
|
|
17621
|
+
await clearSetupFollowUps();
|
|
17622
|
+
await clearCredentials();
|
|
17623
|
+
const cfg = await loadConfig();
|
|
17624
|
+
const pointer = cfg.brain.cloud;
|
|
17625
|
+
if (pointer === void 0 || pointer.api_url !== brainApiUrl) return false;
|
|
17626
|
+
await saveConfig({
|
|
17627
|
+
...cfg,
|
|
17628
|
+
brain: {
|
|
17629
|
+
brains: cfg.brain.brains,
|
|
17630
|
+
...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
|
|
17631
|
+
}
|
|
17632
|
+
});
|
|
17633
|
+
return true;
|
|
17634
|
+
}
|
|
17635
|
+
async function saveBestEffort(marker) {
|
|
17636
|
+
try {
|
|
17637
|
+
await saveAccountDeletionMarker(marker);
|
|
17638
|
+
} catch {
|
|
17639
|
+
}
|
|
17640
|
+
}
|
|
17641
|
+
|
|
17642
|
+
// src/commands/delete-brain.ts
|
|
17643
|
+
init_errors();
|
|
17644
|
+
init_api_client();
|
|
17645
|
+
init_cloud_brain();
|
|
17646
|
+
init_config();
|
|
17647
|
+
init_ui();
|
|
17648
|
+
import { Command as Command13 } from "commander";
|
|
17649
|
+
import prompts2 from "prompts";
|
|
16696
17650
|
function deleteBrainCommand() {
|
|
16697
|
-
return new
|
|
17651
|
+
return new Command13("delete-brain").description("delete a hosted brain and everything in it, for good").argument("<name-or-id>", "which hosted brain to delete, by its name or id").option("--confirm <exact-name>", "the brain\u2019s name, typed exactly, authorising the deletion").addHelpText(
|
|
16698
17652
|
"after",
|
|
16699
17653
|
"\nIrreversible, and server-side: the brain, every note in it, its ledger and its\nmeasurements. `memgineering unlink --brain` is the other thing \u2014 it only stops\nthis machine reading a brain and deletes nothing. Run this ONLY when the person\nhas explicitly asked for this brain to be deleted, and pass --confirm with the\nexact name. A wrongly-named brain is a rename, not a delete."
|
|
16700
17654
|
).action(async (named, opts) => {
|
|
@@ -16831,7 +17785,7 @@ Delete \u201C${oneLine(target.name, WIDTH.title)}\u201D?`));
|
|
|
16831
17785
|
"\n `memgineering unlink --brain` is the other thing: it stops this machine\n reading a brain and deletes nothing. A wrongly-named brain is a rename."
|
|
16832
17786
|
)
|
|
16833
17787
|
);
|
|
16834
|
-
const { typed } = await
|
|
17788
|
+
const { typed } = await prompts2({
|
|
16835
17789
|
type: "text",
|
|
16836
17790
|
name: "typed",
|
|
16837
17791
|
message: "Type the brain\u2019s name exactly to delete it"
|
|
@@ -16865,21 +17819,21 @@ async function deleteCloudBrain(args) {
|
|
|
16865
17819
|
|
|
16866
17820
|
// src/commands/curation.ts
|
|
16867
17821
|
init_src();
|
|
16868
|
-
import { stat as
|
|
17822
|
+
import { stat as stat6 } from "fs/promises";
|
|
16869
17823
|
import { resolve as resolve8 } from "path";
|
|
16870
|
-
import { Command as
|
|
17824
|
+
import { Command as Command15 } from "commander";
|
|
16871
17825
|
|
|
16872
17826
|
// ../../packages/storage-adapter/src/local-file/ignore-file.ts
|
|
16873
17827
|
init_atomic();
|
|
16874
17828
|
init_exclude();
|
|
16875
17829
|
init_walk();
|
|
16876
17830
|
import { createHash as createHash6 } from "crypto";
|
|
16877
|
-
import { readFile as
|
|
17831
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
16878
17832
|
var FILE = ".memgignore";
|
|
16879
17833
|
var hashOf = (content) => createHash6("sha256").update(Buffer.from(content, "utf8")).digest("hex");
|
|
16880
17834
|
async function readIgnoreFileState(root) {
|
|
16881
17835
|
try {
|
|
16882
|
-
const content = await
|
|
17836
|
+
const content = await readFile19(resolveInside(root, FILE), "utf8");
|
|
16883
17837
|
return { content, exists: true, hash: hashOf(content) };
|
|
16884
17838
|
} catch (err) {
|
|
16885
17839
|
if (err?.code === "ENOENT") {
|
|
@@ -16969,7 +17923,7 @@ init_vault();
|
|
|
16969
17923
|
init_ui();
|
|
16970
17924
|
init_link();
|
|
16971
17925
|
function lifecycleCommand(action) {
|
|
16972
|
-
return new
|
|
17926
|
+
return new Command15(action).description(
|
|
16973
17927
|
action === "retire" ? "mark a memory as no longer current \u2014 the note itself is not changed" : "mark a retired memory as current again"
|
|
16974
17928
|
).argument("<ref>", "handle, id, path, or exact title").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (ref, opts) => {
|
|
16975
17929
|
const where = await resolveTarget({
|
|
@@ -17056,7 +18010,7 @@ async function lifecycleLocal(vault, action, ref, reason) {
|
|
|
17056
18010
|
return { entry, record };
|
|
17057
18011
|
}
|
|
17058
18012
|
function ignoreCommand(mode) {
|
|
17059
|
-
return new
|
|
18013
|
+
return new Command15(mode).description(
|
|
17060
18014
|
mode === "exclude" ? "stop reading a note entirely \u2014 the note itself is not changed" : "read a note again after excluding it"
|
|
17061
18015
|
).argument("<path>", "path inside the brain, e.g. decisions/launch-date.md").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the note is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (path, opts) => {
|
|
17062
18016
|
const rationale = sanitizeRationale(opts.reason);
|
|
@@ -17181,7 +18135,7 @@ async function mustMatchAFile(root, adapter, path) {
|
|
|
17181
18135
|
const wanted = path.normalize("NFC").replace(/^\/+/, "");
|
|
17182
18136
|
const absolute = resolve8(root, wanted);
|
|
17183
18137
|
if (isInside(absolute, root)) {
|
|
17184
|
-
const found = await
|
|
18138
|
+
const found = await stat6(absolute).catch(() => null);
|
|
17185
18139
|
if (found?.isFile()) return wanted;
|
|
17186
18140
|
}
|
|
17187
18141
|
const hits = sameBasename(await adapter.list(""), path);
|
|
@@ -17198,14 +18152,14 @@ var unexcludeCommand = () => ignoreCommand("unexclude");
|
|
|
17198
18152
|
|
|
17199
18153
|
// src/commands/scope-validity.ts
|
|
17200
18154
|
init_src();
|
|
17201
|
-
import { Command as
|
|
18155
|
+
import { Command as Command16 } from "commander";
|
|
17202
18156
|
init_errors();
|
|
17203
18157
|
init_index_build();
|
|
17204
18158
|
init_cloud_brain();
|
|
17205
18159
|
init_vault();
|
|
17206
18160
|
init_ui();
|
|
17207
18161
|
function rescopeCommand() {
|
|
17208
|
-
return new
|
|
18162
|
+
return new Command16("rescope").description("change which project a memory binds in \u2014 nothing it says changes").argument("<ref>", "handle, id, path, or exact title").option("--scope <name>", "the project it binds in from now on").option("--global", "it binds everywhere \u2014 say this rather than leaving the scope off").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
|
|
17209
18163
|
async (ref, opts) => {
|
|
17210
18164
|
const scope = scopeAskedFor(opts);
|
|
17211
18165
|
const where = await resolveTarget({
|
|
@@ -17241,7 +18195,7 @@ Undo: \`${undoHint(record.op_id, opts)}\``));
|
|
|
17241
18195
|
);
|
|
17242
18196
|
}
|
|
17243
18197
|
function expireCommand() {
|
|
17244
|
-
return new
|
|
18198
|
+
return new Command16("expire").description("give a memory an end date \u2014 it is NOT retired, and recall still returns it").argument("<ref>", "handle, id, path, or exact title").requiredOption("--until <date>", "the day it stops being current, e.g. 2026-10-31").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
|
|
17245
18199
|
async (ref, opts) => {
|
|
17246
18200
|
const where = await resolveTarget({
|
|
17247
18201
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
@@ -17460,13 +18414,13 @@ Undo: \`memgineering undo ${result.op_id}\``));
|
|
|
17460
18414
|
|
|
17461
18415
|
// src/commands/evidence.ts
|
|
17462
18416
|
init_src();
|
|
17463
|
-
import { Command as
|
|
18417
|
+
import { Command as Command17 } from "commander";
|
|
17464
18418
|
init_index_build();
|
|
17465
18419
|
init_cloud_brain();
|
|
17466
18420
|
init_vault();
|
|
17467
18421
|
init_ui();
|
|
17468
18422
|
function evidenceCommand() {
|
|
17469
|
-
return new
|
|
18423
|
+
return new Command17("evidence").description("how much a memory has been used, and why it last changed").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
17470
18424
|
"--brain <name>",
|
|
17471
18425
|
"read from one existing hosted brain by name or id, without changing the pointer"
|
|
17472
18426
|
).action(async (ref, opts) => {
|
|
@@ -17755,9 +18709,9 @@ function ago(iso, now) {
|
|
|
17755
18709
|
init_brand();
|
|
17756
18710
|
init_config();
|
|
17757
18711
|
import { createHash as createHash7 } from "crypto";
|
|
17758
|
-
import { mkdir as
|
|
17759
|
-
import { dirname as
|
|
17760
|
-
import { Command as
|
|
18712
|
+
import { mkdir as mkdir13, readdir as readdir8, stat as stat7, unlink as unlink6, writeFile as writeFile10 } from "fs/promises";
|
|
18713
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3, join as join21, resolve as resolve9 } from "path";
|
|
18714
|
+
import { Command as Command18 } from "commander";
|
|
17761
18715
|
|
|
17762
18716
|
// src/lib/rules.ts
|
|
17763
18717
|
init_src();
|
|
@@ -17829,7 +18783,7 @@ init_deadline();
|
|
|
17829
18783
|
init_cloud_brain();
|
|
17830
18784
|
init_vault();
|
|
17831
18785
|
function guardCommand() {
|
|
17832
|
-
return new
|
|
18786
|
+
return new Command18("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) => {
|
|
17833
18787
|
try {
|
|
17834
18788
|
const out = await withDeadline(HOOK_DEADLINE_MS, async () => {
|
|
17835
18789
|
const payload = await readPayload();
|
|
@@ -17859,7 +18813,7 @@ async function runGuard(payload, contextDirFlag) {
|
|
|
17859
18813
|
if (!FILE_TOOLS.has(toolName)) return null;
|
|
17860
18814
|
const filePath = fileFrom(payload);
|
|
17861
18815
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : process.cwd();
|
|
17862
|
-
const contextDir = contextDirFlag ?? (filePath === null ? cwd :
|
|
18816
|
+
const contextDir = contextDirFlag ?? (filePath === null ? cwd : dirname6(resolve9(cwd, filePath)));
|
|
17863
18817
|
const sessionId = typeof payload.session_id === "string" ? payload.session_id : null;
|
|
17864
18818
|
if (sessionId === null) return null;
|
|
17865
18819
|
const misdelivered = await misdeliveryNotice(toolName, filePath, sessionId);
|
|
@@ -17976,7 +18930,7 @@ async function readPayload() {
|
|
|
17976
18930
|
return null;
|
|
17977
18931
|
}
|
|
17978
18932
|
}
|
|
17979
|
-
var guardDir = () =>
|
|
18933
|
+
var guardDir = () => join21(brandHome(), "guard");
|
|
17980
18934
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
17981
18935
|
"Write",
|
|
17982
18936
|
"Edit",
|
|
@@ -17997,7 +18951,7 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
|
|
|
17997
18951
|
return 'This file is this harness\u2019s own memory \u2014 no other tool the user works with will ever read it. Durable facts about the user belong in their brain, which every tool shares: memgineering remember "<the fact>" --reason "<why>" (add --rule if they stated a standing rule). Write it there instead, or in addition.';
|
|
17998
18952
|
}
|
|
17999
18953
|
function markerFor(sessionId, brain) {
|
|
18000
|
-
return
|
|
18954
|
+
return join21(
|
|
18001
18955
|
guardDir(),
|
|
18002
18956
|
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
18003
18957
|
);
|
|
@@ -18005,7 +18959,7 @@ function markerFor(sessionId, brain) {
|
|
|
18005
18959
|
async function claimSession(sessionId, brain) {
|
|
18006
18960
|
const marker = markerFor(sessionId, brain);
|
|
18007
18961
|
try {
|
|
18008
|
-
await
|
|
18962
|
+
await mkdir13(guardDir(), { recursive: true, mode: 448 });
|
|
18009
18963
|
await writeFile10(marker, "", { flag: "wx", mode: 384 });
|
|
18010
18964
|
} catch {
|
|
18011
18965
|
return false;
|
|
@@ -18014,21 +18968,21 @@ async function claimSession(sessionId, brain) {
|
|
|
18014
18968
|
return true;
|
|
18015
18969
|
}
|
|
18016
18970
|
async function releaseSession(sessionId, brain) {
|
|
18017
|
-
await
|
|
18971
|
+
await unlink6(markerFor(sessionId, brain)).catch(() => void 0);
|
|
18018
18972
|
}
|
|
18019
18973
|
async function claimExists(sessionId, brain) {
|
|
18020
|
-
return
|
|
18974
|
+
return stat7(markerFor(sessionId, brain)).then(() => true).catch(() => false);
|
|
18021
18975
|
}
|
|
18022
18976
|
var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
18023
18977
|
async function pruneOldMarkers() {
|
|
18024
18978
|
try {
|
|
18025
18979
|
const dir = guardDir();
|
|
18026
18980
|
const now = Date.now();
|
|
18027
|
-
for (const name of await
|
|
18028
|
-
const path =
|
|
18981
|
+
for (const name of await readdir8(dir)) {
|
|
18982
|
+
const path = join21(dir, name);
|
|
18029
18983
|
if (!isAbsolute3(path)) continue;
|
|
18030
|
-
const info = await
|
|
18031
|
-
if (info && now - info.mtimeMs > MARKER_TTL_MS) await
|
|
18984
|
+
const info = await stat7(path).catch(() => null);
|
|
18985
|
+
if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink6(path).catch(() => void 0);
|
|
18032
18986
|
}
|
|
18033
18987
|
await pruneFast(now);
|
|
18034
18988
|
} catch {
|
|
@@ -18072,15 +19026,15 @@ init_link();
|
|
|
18072
19026
|
// src/commands/log.ts
|
|
18073
19027
|
init_src();
|
|
18074
19028
|
init_walk();
|
|
18075
|
-
import { lstat as lstat4, readFile as
|
|
18076
|
-
import { Command as
|
|
19029
|
+
import { lstat as lstat4, readFile as readFile21 } from "fs/promises";
|
|
19030
|
+
import { Command as Command20 } from "commander";
|
|
18077
19031
|
init_errors();
|
|
18078
19032
|
init_cloud_brain();
|
|
18079
19033
|
|
|
18080
19034
|
// src/commands/retract.ts
|
|
18081
19035
|
init_errors();
|
|
18082
19036
|
init_cloud_brain();
|
|
18083
|
-
import { Command as
|
|
19037
|
+
import { Command as Command19 } from "commander";
|
|
18084
19038
|
init_vault();
|
|
18085
19039
|
init_ui();
|
|
18086
19040
|
var CONTENT_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -18151,7 +19105,7 @@ function parseInstant(flag2, raw) {
|
|
|
18151
19105
|
return { ms, iso: new Date(ms).toISOString() };
|
|
18152
19106
|
}
|
|
18153
19107
|
function retractCommand() {
|
|
18154
|
-
return new
|
|
19108
|
+
return new Command19("retract").description(
|
|
18155
19109
|
"retire every memory one writer or one stretch of time wrote \u2014 shows the set first; --yes applies it"
|
|
18156
19110
|
).option("--actor <actor>", 'writes recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option(
|
|
18157
19111
|
"--token <id>",
|
|
@@ -18366,7 +19320,7 @@ ${failed.length} could not be retired:`);
|
|
|
18366
19320
|
init_vault();
|
|
18367
19321
|
init_ui();
|
|
18368
19322
|
function logCommand() {
|
|
18369
|
-
return new
|
|
19323
|
+
return new Command20("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
18370
19324
|
"--brain <name>",
|
|
18371
19325
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
18372
19326
|
).option("--actor <actor>", 'only operations recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option("--token <id>", "only operations made with this token (id or 5+ char prefix) \u2014 hosted brains").option("--since <when>", "only operations at or after this date / ISO instant").option("--until <when>", "only operations before this date / ISO instant").action(async (opts) => {
|
|
@@ -18638,16 +19592,16 @@ async function readOrNull(root, path) {
|
|
|
18638
19592
|
if (absolute === null) return null;
|
|
18639
19593
|
const info = await lstat4(absolute).catch(() => null);
|
|
18640
19594
|
if (!info || !info.isFile()) return null;
|
|
18641
|
-
return
|
|
19595
|
+
return readFile21(absolute, "utf8").catch(() => null);
|
|
18642
19596
|
}
|
|
18643
19597
|
|
|
18644
19598
|
// src/commands/project.ts
|
|
18645
19599
|
init_errors();
|
|
18646
19600
|
import { resolve as resolve10 } from "path";
|
|
18647
|
-
import { Command as
|
|
19601
|
+
import { Command as Command21 } from "commander";
|
|
18648
19602
|
init_ui();
|
|
18649
19603
|
function projectCommand() {
|
|
18650
|
-
return new
|
|
19604
|
+
return new Command21("project").description("show or set what the work in this folder is called").argument("[name]", "what to call it \u2014 omit to show what this folder answers to").option("--here <dir>", "act on this directory instead of the current one").option("--unset", "forget the name set here and go back to what the repository says").addHelpText(
|
|
18651
19605
|
"after",
|
|
18652
19606
|
[
|
|
18653
19607
|
"",
|
|
@@ -18774,10 +19728,10 @@ init_api_client();
|
|
|
18774
19728
|
init_credentials();
|
|
18775
19729
|
init_config();
|
|
18776
19730
|
init_pending_login();
|
|
19731
|
+
import { Command as Command23 } from "commander";
|
|
18777
19732
|
init_ui();
|
|
18778
|
-
import { Command as Command22 } from "commander";
|
|
18779
19733
|
function logoutCommand() {
|
|
18780
|
-
return new
|
|
19734
|
+
return new Command23("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(
|
|
18781
19735
|
"--local",
|
|
18782
19736
|
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
18783
19737
|
).action(async (opts) => {
|
|
@@ -18786,9 +19740,18 @@ function logoutCommand() {
|
|
|
18786
19740
|
await clearPendingLogin();
|
|
18787
19741
|
await clearSetupFollowUps();
|
|
18788
19742
|
if (!credentials) {
|
|
19743
|
+
const hint = opts.all ? NOT_SIGNED_IN_ALL_HINT : void 0;
|
|
18789
19744
|
printDual({
|
|
18790
|
-
json: {
|
|
18791
|
-
|
|
19745
|
+
json: {
|
|
19746
|
+
signed_out: true,
|
|
19747
|
+
was_signed_in: false,
|
|
19748
|
+
api_url: base2,
|
|
19749
|
+
...hint ? { hint } : {}
|
|
19750
|
+
},
|
|
19751
|
+
human: () => {
|
|
19752
|
+
printHuman("Not signed in \u2014 nothing to do.");
|
|
19753
|
+
if (hint) printHuman(c.gray(` ${hint}`));
|
|
19754
|
+
}
|
|
18792
19755
|
});
|
|
18793
19756
|
return;
|
|
18794
19757
|
}
|
|
@@ -18808,6 +19771,8 @@ function logoutCommand() {
|
|
|
18808
19771
|
return;
|
|
18809
19772
|
}
|
|
18810
19773
|
let revoked = null;
|
|
19774
|
+
let registrySessions = null;
|
|
19775
|
+
let revokedBefore = null;
|
|
18811
19776
|
try {
|
|
18812
19777
|
if (opts.all) {
|
|
18813
19778
|
const res = await apiRequest({
|
|
@@ -18817,6 +19782,8 @@ function logoutCommand() {
|
|
|
18817
19782
|
baseUrl: credentials.api_url
|
|
18818
19783
|
});
|
|
18819
19784
|
revoked = res.body?.revoked ?? null;
|
|
19785
|
+
registrySessions = readRegistrySessions(res.body?.registry_sessions);
|
|
19786
|
+
revokedBefore = typeof res.body?.revoked_before === "string" ? res.body.revoked_before : null;
|
|
18820
19787
|
} else {
|
|
18821
19788
|
const me = await apiRequest({
|
|
18822
19789
|
method: "GET",
|
|
@@ -18845,16 +19812,19 @@ function logoutCommand() {
|
|
|
18845
19812
|
}) : err;
|
|
18846
19813
|
}
|
|
18847
19814
|
}
|
|
18848
|
-
const
|
|
19815
|
+
const registry = (await readCredentials())?.registry ?? credentials.registry;
|
|
19816
|
+
const registryRevoked = registrySessions?.status === "revoked" ? true : await revokeRegistrySession(registry);
|
|
18849
19817
|
await clearCredentials();
|
|
18850
19818
|
printDual({
|
|
18851
19819
|
json: {
|
|
18852
19820
|
signed_out: true,
|
|
18853
|
-
registry:
|
|
19821
|
+
registry: registry === void 0 ? "not-linked" : registryRevoked ? "revoked" : "revoke-failed-expires-on-its-own",
|
|
18854
19822
|
revoked_on_server: true,
|
|
18855
19823
|
revoked,
|
|
18856
19824
|
all: opts.all === true,
|
|
18857
|
-
api_url: credentials.api_url
|
|
19825
|
+
api_url: credentials.api_url,
|
|
19826
|
+
...registrySessions ? { registry_sessions: registrySessions } : {},
|
|
19827
|
+
...revokedBefore ? { revoked_before: revokedBefore } : {}
|
|
18858
19828
|
},
|
|
18859
19829
|
human: () => {
|
|
18860
19830
|
printHuman(opts.all ? "Signed out everywhere." : "Signed out.");
|
|
@@ -18863,6 +19833,8 @@ function logoutCommand() {
|
|
|
18863
19833
|
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."
|
|
18864
19834
|
)
|
|
18865
19835
|
);
|
|
19836
|
+
const registryLine = registrySessions ? registrySessionsLine(registrySessions) : null;
|
|
19837
|
+
if (registryLine !== null) printHuman(registryLine);
|
|
18866
19838
|
printHuman(
|
|
18867
19839
|
c.gray(
|
|
18868
19840
|
" 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."
|
|
@@ -18872,21 +19844,47 @@ function logoutCommand() {
|
|
|
18872
19844
|
});
|
|
18873
19845
|
});
|
|
18874
19846
|
}
|
|
19847
|
+
var NOT_SIGNED_IN_ALL_HINT = 'Nothing is signed in on this machine, so nothing could be ended from here. To disconnect everything, press "disconnect everything" on the website, or run `memgineering login` and then `memgineering logout --all`.';
|
|
19848
|
+
function readRegistrySessions(raw) {
|
|
19849
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
19850
|
+
const r = raw;
|
|
19851
|
+
if (r["status"] === "revoked" && typeof r["count"] === "number") {
|
|
19852
|
+
return { status: "revoked", count: r["count"] };
|
|
19853
|
+
}
|
|
19854
|
+
if (r["status"] === "not_linked") return { status: "not_linked" };
|
|
19855
|
+
if (r["status"] === "unreachable" && typeof r["hint"] === "string") {
|
|
19856
|
+
return { status: "unreachable", hint: r["hint"] };
|
|
19857
|
+
}
|
|
19858
|
+
return null;
|
|
19859
|
+
}
|
|
19860
|
+
function registrySessionsLine(r) {
|
|
19861
|
+
switch (r.status) {
|
|
19862
|
+
case "revoked":
|
|
19863
|
+
if (r.count === 0) return null;
|
|
19864
|
+
return c.gray(
|
|
19865
|
+
` Registry sign-ins ended too: ${r.count}. Anything approved before now cannot sign back in.`
|
|
19866
|
+
);
|
|
19867
|
+
case "not_linked":
|
|
19868
|
+
return c.gray(
|
|
19869
|
+
" No registry account is linked to this Google account; nothing to end there."
|
|
19870
|
+
);
|
|
19871
|
+
case "unreachable":
|
|
19872
|
+
return c.yellow(` Registry sign-ins could not be ended: ${oneLineWhole(r.hint)}`);
|
|
19873
|
+
}
|
|
19874
|
+
}
|
|
18875
19875
|
async function revokeRegistrySession(reg) {
|
|
18876
19876
|
if (!reg) return false;
|
|
18877
19877
|
try {
|
|
18878
|
-
const
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
body: JSON.stringify({ refresh_token: reg.refresh_token }),
|
|
18882
|
-
signal: AbortSignal.timeout(3e3)
|
|
19878
|
+
const { registry, tokens } = await refreshRegistrySession(reg, {
|
|
19879
|
+
apiUrlOf: (r) => r.api_url,
|
|
19880
|
+
requestTimeoutMs: 3e3
|
|
18883
19881
|
});
|
|
18884
|
-
if (
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
const del = await fetch(`${
|
|
19882
|
+
if (typeof tokens.access_token !== "string" || typeof tokens.session_id !== "string") {
|
|
19883
|
+
return false;
|
|
19884
|
+
}
|
|
19885
|
+
const del = await fetch(`${registry.api_url}/auth/sessions/${tokens.session_id}`, {
|
|
18888
19886
|
method: "DELETE",
|
|
18889
|
-
headers: { authorization: `Bearer ${
|
|
19887
|
+
headers: { authorization: `Bearer ${tokens.access_token}` },
|
|
18890
19888
|
signal: AbortSignal.timeout(3e3)
|
|
18891
19889
|
});
|
|
18892
19890
|
return del.ok;
|
|
@@ -18897,12 +19895,12 @@ async function revokeRegistrySession(reg) {
|
|
|
18897
19895
|
|
|
18898
19896
|
// src/commands/onboard.ts
|
|
18899
19897
|
init_src();
|
|
18900
|
-
import { Command as
|
|
19898
|
+
import { Command as Command24 } from "commander";
|
|
18901
19899
|
init_cloud_brain();
|
|
18902
19900
|
init_vault();
|
|
18903
19901
|
init_ui();
|
|
18904
19902
|
function onboardCommand() {
|
|
18905
|
-
return new
|
|
19903
|
+
return new Command24("onboard").description("what to ask so the brain knows who it belongs to").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
18906
19904
|
"--brain <name>",
|
|
18907
19905
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
18908
19906
|
).action(async (opts) => {
|
|
@@ -19048,11 +20046,11 @@ init_api_client();
|
|
|
19048
20046
|
init_config();
|
|
19049
20047
|
init_cloud_brain();
|
|
19050
20048
|
init_ui();
|
|
19051
|
-
import { mkdir as
|
|
19052
|
-
import { dirname as
|
|
19053
|
-
import { Command as
|
|
20049
|
+
import { mkdir as mkdir14, readFile as readFile22, writeFile as writeFile11 } from "fs/promises";
|
|
20050
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join22, resolve as resolve11, sep as sep2 } from "path";
|
|
20051
|
+
import { Command as Command25 } from "commander";
|
|
19054
20052
|
function pullCommand() {
|
|
19055
|
-
return new
|
|
20053
|
+
return new Command25("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) => {
|
|
19056
20054
|
const { brain: source, others } = await resolveSource(opts.brain);
|
|
19057
20055
|
const target = resolve11(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
|
|
19058
20056
|
const first = await cloudListNotes(source, { limit: 1 });
|
|
@@ -19117,7 +20115,7 @@ function pullCommand() {
|
|
|
19117
20115
|
already.push(note.path);
|
|
19118
20116
|
continue;
|
|
19119
20117
|
}
|
|
19120
|
-
await
|
|
20118
|
+
await mkdir14(dirname7(file), { recursive: true });
|
|
19121
20119
|
await writeFile11(file, note.content ?? "", "utf8");
|
|
19122
20120
|
written.push(note.path);
|
|
19123
20121
|
if (note.excluded === true) excluded.push(note.path);
|
|
@@ -19232,7 +20230,7 @@ function safeJoin(root, relative3) {
|
|
|
19232
20230
|
"Nothing was written for it. This should not happen \u2014 please report it."
|
|
19233
20231
|
);
|
|
19234
20232
|
}
|
|
19235
|
-
const full = resolve11(
|
|
20233
|
+
const full = resolve11(join22(root, relative3));
|
|
19236
20234
|
if (full !== root && !full.startsWith(root + sep2)) {
|
|
19237
20235
|
throw memgError(
|
|
19238
20236
|
"server_refused",
|
|
@@ -19244,7 +20242,7 @@ function safeJoin(root, relative3) {
|
|
|
19244
20242
|
}
|
|
19245
20243
|
async function exists(file) {
|
|
19246
20244
|
try {
|
|
19247
|
-
await
|
|
20245
|
+
await readFile22(file);
|
|
19248
20246
|
return true;
|
|
19249
20247
|
} catch {
|
|
19250
20248
|
return false;
|
|
@@ -19252,8 +20250,8 @@ async function exists(file) {
|
|
|
19252
20250
|
}
|
|
19253
20251
|
|
|
19254
20252
|
// src/commands/publish.ts
|
|
19255
|
-
import { Command as
|
|
19256
|
-
import { z as
|
|
20253
|
+
import { Command as Command27 } from "commander";
|
|
20254
|
+
import { z as z33 } from "zod";
|
|
19257
20255
|
init_src();
|
|
19258
20256
|
init_errors();
|
|
19259
20257
|
|
|
@@ -19393,72 +20391,6 @@ ${body}`;
|
|
|
19393
20391
|
init_cloud_brain();
|
|
19394
20392
|
init_index_build();
|
|
19395
20393
|
|
|
19396
|
-
// src/lib/registry-session.ts
|
|
19397
|
-
init_errors();
|
|
19398
|
-
init_credentials();
|
|
19399
|
-
init_version();
|
|
19400
|
-
function registryApiUrl(stored) {
|
|
19401
|
-
const override = process.env["KORDIS_API_URL"];
|
|
19402
|
-
return override !== void 0 && override !== "" ? override : stored;
|
|
19403
|
-
}
|
|
19404
|
-
async function requireRegistryCredentials() {
|
|
19405
|
-
const credentials = await readCredentials();
|
|
19406
|
-
const registry = credentials?.registry;
|
|
19407
|
-
if (!registry) {
|
|
19408
|
-
throw memgError(
|
|
19409
|
-
"not_linked",
|
|
19410
|
-
"this machine holds no registry identity",
|
|
19411
|
-
credentials === null ? "Nothing was sent. Sign in with `memgineering login` \u2014 it links the registry identity at the same time, so one sign-in covers both." : "Nothing was sent. This machine is signed in to a brain, and the registry half of that sign-in is missing \u2014 which is what a credential file written before federation looks like. Run `memgineering login` again; it links the registry identity too."
|
|
19412
|
-
);
|
|
19413
|
-
}
|
|
19414
|
-
return registry;
|
|
19415
|
-
}
|
|
19416
|
-
async function openRegistrySession(fetchImpl) {
|
|
19417
|
-
const registry = await requireRegistryCredentials();
|
|
19418
|
-
const baseUrl = registryApiUrl(registry.api_url);
|
|
19419
|
-
const anonymous = new KordisClient({
|
|
19420
|
-
baseUrl,
|
|
19421
|
-
userAgent: `memgineering/${VERSION}`,
|
|
19422
|
-
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
19423
|
-
});
|
|
19424
|
-
let tokens;
|
|
19425
|
-
try {
|
|
19426
|
-
tokens = await anonymous.refreshSession(registry.refresh_token);
|
|
19427
|
-
} catch (err) {
|
|
19428
|
-
throw memgError(
|
|
19429
|
-
"not_linked",
|
|
19430
|
-
`the registry refused this machine's stored sign-in: ${err instanceof Error ? err.message : String(err)}`,
|
|
19431
|
-
"Nothing was sent. The link between this brain and your registry account has expired or been revoked. `memgineering login` re-links both."
|
|
19432
|
-
);
|
|
19433
|
-
}
|
|
19434
|
-
const credentialsRotated = await persistRotatedRefresh(
|
|
19435
|
-
registry,
|
|
19436
|
-
tokens.refresh_token,
|
|
19437
|
-
tokens.session_id
|
|
19438
|
-
);
|
|
19439
|
-
const client = new KordisClient({
|
|
19440
|
-
baseUrl,
|
|
19441
|
-
userAgent: `memgineering/${VERSION}`,
|
|
19442
|
-
getAccessToken: () => tokens.access_token,
|
|
19443
|
-
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
19444
|
-
});
|
|
19445
|
-
return { client, username: registry.username, apiUrl: baseUrl, credentialsRotated };
|
|
19446
|
-
}
|
|
19447
|
-
async function persistRotatedRefresh(registry, refreshToken, sessionId) {
|
|
19448
|
-
if (refreshToken === registry.refresh_token && sessionId === registry.session_id) return false;
|
|
19449
|
-
try {
|
|
19450
|
-
const credentials = await readCredentials();
|
|
19451
|
-
if (!credentials?.registry) return false;
|
|
19452
|
-
await saveCredentials({
|
|
19453
|
-
...credentials,
|
|
19454
|
-
registry: { ...credentials.registry, refresh_token: refreshToken, session_id: sessionId }
|
|
19455
|
-
});
|
|
19456
|
-
return true;
|
|
19457
|
-
} catch {
|
|
19458
|
-
return false;
|
|
19459
|
-
}
|
|
19460
|
-
}
|
|
19461
|
-
|
|
19462
20394
|
// src/lib/publish-version.ts
|
|
19463
20395
|
import semver from "semver";
|
|
19464
20396
|
|
|
@@ -19466,9 +20398,9 @@ import semver from "semver";
|
|
|
19466
20398
|
init_parser();
|
|
19467
20399
|
|
|
19468
20400
|
// ../../packages/harness-schema/src/deps.ts
|
|
19469
|
-
import { z as
|
|
20401
|
+
import { z as z26 } from "zod";
|
|
19470
20402
|
var StringDepRe = /^([a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*)(?:@(.+))?$/;
|
|
19471
|
-
var ObjectDepSchema =
|
|
20403
|
+
var ObjectDepSchema = z26.object({
|
|
19472
20404
|
full_slug: FullSlugSchema,
|
|
19473
20405
|
version_range: SemverRangeSchema.default("*")
|
|
19474
20406
|
});
|
|
@@ -19503,33 +20435,33 @@ function normaliseDep(entry, idx) {
|
|
|
19503
20435
|
init_parser();
|
|
19504
20436
|
|
|
19505
20437
|
// ../../packages/harness-schema/src/types/skill.ts
|
|
19506
|
-
import { z as
|
|
20438
|
+
import { z as z27 } from "zod";
|
|
19507
20439
|
var SKILL_DESCRIPTION_SPEC_MAX = 1024;
|
|
19508
20440
|
var SKILL_DESCRIPTION_HARD_MAX = 2048;
|
|
19509
20441
|
var SKILL_COMPATIBILITY_MAX = 500;
|
|
19510
|
-
var MetadataValueSchema =
|
|
19511
|
-
var SkillSpecFrontmatterSchema =
|
|
19512
|
-
name:
|
|
19513
|
-
description:
|
|
20442
|
+
var MetadataValueSchema = z27.union([z27.string(), z27.number(), z27.boolean()]).transform((v) => String(v));
|
|
20443
|
+
var SkillSpecFrontmatterSchema = z27.object({
|
|
20444
|
+
name: z27.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
|
|
20445
|
+
description: z27.string().min(1).max(SKILL_DESCRIPTION_HARD_MAX),
|
|
19514
20446
|
// The spec puts no length limit on license (it is an SPDX identifier or a
|
|
19515
20447
|
// short phrase); the old 64-char cap here was Kordis-invented.
|
|
19516
|
-
license:
|
|
19517
|
-
compatibility:
|
|
20448
|
+
license: z27.string().optional(),
|
|
20449
|
+
compatibility: z27.string().max(SKILL_COMPATIBILITY_MAX).optional(),
|
|
19518
20450
|
// Spec form is a space-separated STRING. The array form is what Kordis
|
|
19519
20451
|
// accepted before the spec settled — still parsed, flagged
|
|
19520
20452
|
// `legacy_frontmatter`, and re-emitted unchanged by the translators.
|
|
19521
|
-
"allowed-tools":
|
|
19522
|
-
metadata:
|
|
20453
|
+
"allowed-tools": z27.union([z27.string(), z27.array(z27.string())]).optional(),
|
|
20454
|
+
metadata: z27.record(z27.string(), MetadataValueSchema).optional()
|
|
19523
20455
|
}).strict();
|
|
19524
|
-
var LegacySkillExtrasSchema =
|
|
20456
|
+
var LegacySkillExtrasSchema = z27.object({
|
|
19525
20457
|
version: SemverSchema.optional(),
|
|
19526
|
-
homepage:
|
|
19527
|
-
model:
|
|
20458
|
+
homepage: z27.string().url().optional(),
|
|
20459
|
+
model: z27.string().optional(),
|
|
19528
20460
|
// model override hint
|
|
19529
|
-
"disable-model-invocation":
|
|
19530
|
-
title:
|
|
19531
|
-
tags:
|
|
19532
|
-
tools:
|
|
20461
|
+
"disable-model-invocation": z27.boolean().optional(),
|
|
20462
|
+
title: z27.string().min(1).max(200).optional(),
|
|
20463
|
+
tags: z27.array(TagSchema).max(16).optional(),
|
|
20464
|
+
tools: z27.array(ToolSlugSchema).optional()
|
|
19533
20465
|
});
|
|
19534
20466
|
var LEGACY_SKILL_EXTRA_KEYS = [
|
|
19535
20467
|
"version",
|
|
@@ -19544,10 +20476,10 @@ var LEGACY_KEY_SET = new Set(LEGACY_SKILL_EXTRA_KEYS);
|
|
|
19544
20476
|
var NONSTANDARD_HINT = 'These are not Agent Skills fields. Move them to `metadata` (string values: `metadata: {version: "1.2.3", tags: "a, b", tools: "claude-code, codex"}`). Accepted with a warning for now; a future release will make this an error and will say so in its changelog.';
|
|
19545
20477
|
var METADATA_READBACK = [
|
|
19546
20478
|
{ field: "version", schema: SemverSchema, list: false },
|
|
19547
|
-
{ field: "homepage", schema:
|
|
19548
|
-
{ field: "model", schema:
|
|
19549
|
-
{ field: "tags", schema:
|
|
19550
|
-
{ field: "tools", schema:
|
|
20479
|
+
{ field: "homepage", schema: z27.string().url(), list: false },
|
|
20480
|
+
{ field: "model", schema: z27.string().min(1), list: false },
|
|
20481
|
+
{ field: "tags", schema: z27.array(TagSchema).max(16), list: true },
|
|
20482
|
+
{ field: "tools", schema: z27.array(ToolSlugSchema), list: true }
|
|
19551
20483
|
];
|
|
19552
20484
|
function splitList(raw) {
|
|
19553
20485
|
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -19642,30 +20574,30 @@ function parseSkillFrontmatter(raw) {
|
|
|
19642
20574
|
}
|
|
19643
20575
|
|
|
19644
20576
|
// ../../packages/harness-schema/src/types/mcp-server.ts
|
|
19645
|
-
import { z as
|
|
19646
|
-
var McpTransportSchema =
|
|
19647
|
-
|
|
19648
|
-
type:
|
|
19649
|
-
command:
|
|
19650
|
-
args:
|
|
19651
|
-
env:
|
|
20577
|
+
import { z as z28 } from "zod";
|
|
20578
|
+
var McpTransportSchema = z28.union([
|
|
20579
|
+
z28.object({
|
|
20580
|
+
type: z28.literal("stdio"),
|
|
20581
|
+
command: z28.string().min(1),
|
|
20582
|
+
args: z28.array(z28.string()).optional().default([]),
|
|
20583
|
+
env: z28.record(z28.string()).optional().default({})
|
|
19652
20584
|
}),
|
|
19653
|
-
|
|
19654
|
-
type:
|
|
19655
|
-
url:
|
|
19656
|
-
headers:
|
|
20585
|
+
z28.object({
|
|
20586
|
+
type: z28.enum(["http", "sse"]),
|
|
20587
|
+
url: z28.string().url(),
|
|
20588
|
+
headers: z28.record(z28.string()).optional().default({})
|
|
19657
20589
|
})
|
|
19658
20590
|
]);
|
|
19659
|
-
var McpCapabilitiesSchema =
|
|
19660
|
-
tools:
|
|
19661
|
-
resources:
|
|
19662
|
-
prompts:
|
|
20591
|
+
var McpCapabilitiesSchema = z28.object({
|
|
20592
|
+
tools: z28.array(z28.string()).optional().default([]),
|
|
20593
|
+
resources: z28.array(z28.string()).optional().default([]),
|
|
20594
|
+
prompts: z28.array(z28.string()).optional().default([])
|
|
19663
20595
|
});
|
|
19664
|
-
var McpServerFrontmatterSchema =
|
|
19665
|
-
name:
|
|
19666
|
-
description:
|
|
20596
|
+
var McpServerFrontmatterSchema = z28.object({
|
|
20597
|
+
name: z28.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
20598
|
+
description: z28.string().min(1).max(2048),
|
|
19667
20599
|
version: SemverSchema,
|
|
19668
|
-
mcp_spec_version:
|
|
20600
|
+
mcp_spec_version: z28.string().default("2026-03"),
|
|
19669
20601
|
transport: McpTransportSchema,
|
|
19670
20602
|
capabilities: McpCapabilitiesSchema.optional().default({
|
|
19671
20603
|
tools: [],
|
|
@@ -19673,34 +20605,34 @@ var McpServerFrontmatterSchema = z27.object({
|
|
|
19673
20605
|
prompts: []
|
|
19674
20606
|
}),
|
|
19675
20607
|
/** Human-readable setup / auth instructions. Surfaced in `kordis info`. */
|
|
19676
|
-
auth_notes:
|
|
20608
|
+
auth_notes: z28.string().optional(),
|
|
19677
20609
|
/** Required env vars with description + whether they must be secret. */
|
|
19678
|
-
env_schema:
|
|
19679
|
-
|
|
19680
|
-
description:
|
|
19681
|
-
required:
|
|
19682
|
-
secret:
|
|
20610
|
+
env_schema: z28.record(
|
|
20611
|
+
z28.object({
|
|
20612
|
+
description: z28.string(),
|
|
20613
|
+
required: z28.boolean().default(true),
|
|
20614
|
+
secret: z28.boolean().default(false)
|
|
19683
20615
|
})
|
|
19684
20616
|
).optional(),
|
|
19685
|
-
homepage:
|
|
19686
|
-
repository:
|
|
19687
|
-
license:
|
|
19688
|
-
title:
|
|
20617
|
+
homepage: z28.string().url().optional(),
|
|
20618
|
+
repository: z28.string().url().optional(),
|
|
20619
|
+
license: z28.string().max(64).optional(),
|
|
20620
|
+
title: z28.string().min(1).max(200).optional()
|
|
19689
20621
|
}).strict();
|
|
19690
20622
|
|
|
19691
20623
|
// ../../packages/harness-schema/src/types/project-rules.ts
|
|
19692
|
-
import { z as
|
|
19693
|
-
var ProjectRulesFrontmatterSchema =
|
|
19694
|
-
title:
|
|
19695
|
-
description:
|
|
20624
|
+
import { z as z29 } from "zod";
|
|
20625
|
+
var ProjectRulesFrontmatterSchema = z29.object({
|
|
20626
|
+
title: z29.string().min(1).max(200),
|
|
20627
|
+
description: z29.string().min(1).max(2048),
|
|
19696
20628
|
version: SemverSchema.optional(),
|
|
19697
|
-
scope:
|
|
19698
|
-
target_path:
|
|
19699
|
-
globs:
|
|
19700
|
-
target_tools:
|
|
20629
|
+
scope: z29.enum(["root", "nested", "globs"]).default("root"),
|
|
20630
|
+
target_path: z29.string().optional(),
|
|
20631
|
+
globs: z29.array(z29.string()).optional(),
|
|
20632
|
+
target_tools: z29.array(ToolSlugSchema).optional().default([]),
|
|
19701
20633
|
/** When true, installation is idempotent via `<!-- kordis:... -->` markers. */
|
|
19702
|
-
mergeable:
|
|
19703
|
-
license:
|
|
20634
|
+
mergeable: z29.boolean().default(true),
|
|
20635
|
+
license: z29.string().max(64).optional()
|
|
19704
20636
|
}).strict().refine((v) => v.scope !== "nested" || !!v.target_path, {
|
|
19705
20637
|
message: 'scope="nested" requires target_path'
|
|
19706
20638
|
}).refine((v) => v.scope !== "globs" || v.globs && v.globs.length > 0, {
|
|
@@ -19708,32 +20640,32 @@ var ProjectRulesFrontmatterSchema = z28.object({
|
|
|
19708
20640
|
});
|
|
19709
20641
|
|
|
19710
20642
|
// ../../packages/harness-schema/src/types/custom-agent.ts
|
|
19711
|
-
import { z as
|
|
19712
|
-
var CustomAgentFrontmatterSchema =
|
|
19713
|
-
name:
|
|
19714
|
-
description:
|
|
19715
|
-
title:
|
|
20643
|
+
import { z as z30 } from "zod";
|
|
20644
|
+
var CustomAgentFrontmatterSchema = z30.object({
|
|
20645
|
+
name: z30.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
20646
|
+
description: z30.string().min(1).max(2048),
|
|
20647
|
+
title: z30.string().min(1).max(200).optional(),
|
|
19716
20648
|
version: SemverSchema.optional(),
|
|
19717
20649
|
/** System prompt that defines the agent's persona + rules. Required. */
|
|
19718
|
-
system_prompt:
|
|
20650
|
+
system_prompt: z30.string().min(1).max(5e4),
|
|
19719
20651
|
/** Optional model override — string passed verbatim to the host. */
|
|
19720
|
-
model:
|
|
20652
|
+
model: z30.string().optional(),
|
|
19721
20653
|
/** Tools this agent can call. Host-specific names (no uniform namespace). */
|
|
19722
|
-
allowed_tools:
|
|
19723
|
-
disallowed_tools:
|
|
20654
|
+
allowed_tools: z30.array(z30.string()).optional().default([]),
|
|
20655
|
+
disallowed_tools: z30.array(z30.string()).optional().default([]),
|
|
19724
20656
|
/** Fork/context behaviour hint (Claude: `context: fork` etc.). */
|
|
19725
|
-
context:
|
|
20657
|
+
context: z30.enum(["inherit", "fork", "isolated"]).optional(),
|
|
19726
20658
|
/** Known-good tool runtimes. Empty = untested everywhere. */
|
|
19727
|
-
tool_compat:
|
|
20659
|
+
tool_compat: z30.array(ToolSlugSchema).optional().default([]),
|
|
19728
20660
|
/** Pre-load skills when this agent activates. */
|
|
19729
|
-
preload_skills:
|
|
19730
|
-
license:
|
|
19731
|
-
homepage:
|
|
20661
|
+
preload_skills: z30.array(z30.string()).optional().default([]),
|
|
20662
|
+
license: z30.string().max(64).optional(),
|
|
20663
|
+
homepage: z30.string().url().optional()
|
|
19732
20664
|
}).strict();
|
|
19733
20665
|
|
|
19734
20666
|
// ../../packages/harness-schema/src/types/hook.ts
|
|
19735
|
-
import { z as
|
|
19736
|
-
var HookEventSchema =
|
|
20667
|
+
import { z as z31 } from "zod";
|
|
20668
|
+
var HookEventSchema = z31.enum([
|
|
19737
20669
|
"PreToolUse",
|
|
19738
20670
|
"PostToolUse",
|
|
19739
20671
|
"UserPromptSubmit",
|
|
@@ -19745,78 +20677,78 @@ var HookEventSchema = z30.enum([
|
|
|
19745
20677
|
"SubagentStop",
|
|
19746
20678
|
"Background"
|
|
19747
20679
|
]);
|
|
19748
|
-
var HookActionSchema =
|
|
19749
|
-
|
|
19750
|
-
type:
|
|
19751
|
-
command:
|
|
19752
|
-
timeout_ms:
|
|
19753
|
-
blocking:
|
|
20680
|
+
var HookActionSchema = z31.union([
|
|
20681
|
+
z31.object({
|
|
20682
|
+
type: z31.literal("command"),
|
|
20683
|
+
command: z31.string().min(1),
|
|
20684
|
+
timeout_ms: z31.number().int().positive().max(6e5).optional(),
|
|
20685
|
+
blocking: z31.boolean().default(false)
|
|
19754
20686
|
}),
|
|
19755
|
-
|
|
19756
|
-
type:
|
|
19757
|
-
url:
|
|
19758
|
-
method:
|
|
19759
|
-
headers:
|
|
19760
|
-
timeout_ms:
|
|
19761
|
-
blocking:
|
|
20687
|
+
z31.object({
|
|
20688
|
+
type: z31.literal("http"),
|
|
20689
|
+
url: z31.string().url(),
|
|
20690
|
+
method: z31.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
|
|
20691
|
+
headers: z31.record(z31.string()).optional().default({}),
|
|
20692
|
+
timeout_ms: z31.number().int().positive().max(6e5).optional(),
|
|
20693
|
+
blocking: z31.boolean().default(false)
|
|
19762
20694
|
})
|
|
19763
20695
|
]);
|
|
19764
|
-
var HookBindingSchema =
|
|
20696
|
+
var HookBindingSchema = z31.object({
|
|
19765
20697
|
event: HookEventSchema,
|
|
19766
|
-
matcher:
|
|
20698
|
+
matcher: z31.string().optional(),
|
|
19767
20699
|
action: HookActionSchema
|
|
19768
20700
|
});
|
|
19769
|
-
var HookFrontmatterSchema =
|
|
19770
|
-
name:
|
|
19771
|
-
description:
|
|
19772
|
-
title:
|
|
20701
|
+
var HookFrontmatterSchema = z31.object({
|
|
20702
|
+
name: z31.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
|
|
20703
|
+
description: z31.string().min(1).max(2048),
|
|
20704
|
+
title: z31.string().min(1).max(200).optional(),
|
|
19773
20705
|
version: SemverSchema.optional(),
|
|
19774
20706
|
/** One or more hook bindings. */
|
|
19775
|
-
hooks:
|
|
20707
|
+
hooks: z31.array(HookBindingSchema).min(1),
|
|
19776
20708
|
/** Declared to surface in search filters — always "claude-code" for now. */
|
|
19777
|
-
tool_compat:
|
|
19778
|
-
license:
|
|
20709
|
+
tool_compat: z31.array(z31.enum(["claude-code"])).default(["claude-code"]),
|
|
20710
|
+
license: z31.string().max(64).optional()
|
|
19779
20711
|
}).strict();
|
|
19780
20712
|
|
|
19781
20713
|
// ../../packages/harness-schema/src/types/judgment-pattern.ts
|
|
19782
|
-
import { z as
|
|
19783
|
-
var FailureModeSchema =
|
|
19784
|
-
symptom:
|
|
19785
|
-
correction:
|
|
20714
|
+
import { z as z32 } from "zod";
|
|
20715
|
+
var FailureModeSchema = z32.object({
|
|
20716
|
+
symptom: z32.string().min(1).max(600).describe("the observable sign the judgment was ignored"),
|
|
20717
|
+
correction: z32.string().min(1).max(1200).describe("what to do once the symptom shows")
|
|
19786
20718
|
}).strict();
|
|
19787
|
-
var AdaptHintsSchema =
|
|
20719
|
+
var AdaptHintsSchema = z32.object({
|
|
19788
20720
|
/**
|
|
19789
20721
|
* Parts that transplant verbatim into ANY harness — the invariant core.
|
|
19790
20722
|
* Required, min 1: a judgment-pattern with no universal part has nothing
|
|
19791
20723
|
* to transplant, which makes it a skill/project-rules in disguise, not a
|
|
19792
20724
|
* judgment worth the dedicated type.
|
|
19793
20725
|
*/
|
|
19794
|
-
universal:
|
|
20726
|
+
universal: z32.array(z32.string().min(1).max(400)).min(1, "adapt_hints.universal must list at least one transplantable, harness-agnostic part").max(20),
|
|
19795
20727
|
/** Parts the adapting agent must re-express in the host's stack/conventions. */
|
|
19796
|
-
context_specific:
|
|
20728
|
+
context_specific: z32.array(z32.string().min(1).max(400)).max(20).optional()
|
|
19797
20729
|
}).strict();
|
|
19798
|
-
var JudgmentPatternFrontmatterSchema =
|
|
19799
|
-
name:
|
|
20730
|
+
var JudgmentPatternFrontmatterSchema = z32.object({
|
|
20731
|
+
name: z32.string().min(1).max(64).regex(
|
|
19800
20732
|
/^[a-z0-9]+(-[a-z0-9]+)*$/,
|
|
19801
20733
|
'name must be lowercase-with-hyphens, e.g. "additive-only-migrations"'
|
|
19802
20734
|
),
|
|
19803
|
-
description:
|
|
20735
|
+
description: z32.string().min(1).max(2048),
|
|
19804
20736
|
version: SemverSchema.optional(),
|
|
19805
|
-
title:
|
|
20737
|
+
title: z32.string().min(1).max(200).optional(),
|
|
19806
20738
|
/** The recurring situation / trap this judgment addresses. */
|
|
19807
|
-
problem:
|
|
20739
|
+
problem: z32.string().min(1).max(4e3),
|
|
19808
20740
|
/**
|
|
19809
20741
|
* The judgment itself — free-form Markdown. The agent is the interpreter,
|
|
19810
20742
|
* so this stays unstructured prose (decision tree, heuristics, rationale).
|
|
19811
20743
|
* This is the part that gets transplanted.
|
|
19812
20744
|
*/
|
|
19813
|
-
decision_logic:
|
|
20745
|
+
decision_logic: z32.string().min(1).max(2e4),
|
|
19814
20746
|
/** Triggers — when this judgment should fire. At least one. */
|
|
19815
|
-
when_to_apply:
|
|
20747
|
+
when_to_apply: z32.array(z32.string().min(1).max(400)).min(1).max(12),
|
|
19816
20748
|
/** Counter-triggers — when applying it would be wrong. */
|
|
19817
|
-
when_not_to_apply:
|
|
20749
|
+
when_not_to_apply: z32.array(z32.string().min(1).max(400)).max(12).optional(),
|
|
19818
20750
|
/** How it fails in practice, and how to recover — the hard-won part. */
|
|
19819
|
-
failure_modes:
|
|
20751
|
+
failure_modes: z32.array(FailureModeSchema).max(12).optional(),
|
|
19820
20752
|
/**
|
|
19821
20753
|
* Transplant steering — the field unique to judgment-pattern. Required:
|
|
19822
20754
|
* `adapt_hints.universal` (min 1) is what makes this a judgment-pattern
|
|
@@ -19824,13 +20756,13 @@ var JudgmentPatternFrontmatterSchema = z31.object({
|
|
|
19824
20756
|
*/
|
|
19825
20757
|
adapt_hints: AdaptHintsSchema,
|
|
19826
20758
|
/** Where this judgment came from (project, incident, paper). Provenance. */
|
|
19827
|
-
origin:
|
|
20759
|
+
origin: z32.string().min(1).max(600).optional(),
|
|
19828
20760
|
/** Proof it works (anti-spam lever for AI review). */
|
|
19829
|
-
evidence:
|
|
20761
|
+
evidence: z32.string().min(1).max(2e3).optional(),
|
|
19830
20762
|
// Kordis-forwarded cross-type fields (kept minimal, like skill).
|
|
19831
|
-
tags:
|
|
19832
|
-
tools:
|
|
19833
|
-
license:
|
|
20763
|
+
tags: z32.array(TagSchema).max(16).optional(),
|
|
20764
|
+
tools: z32.array(ToolSlugSchema).optional(),
|
|
20765
|
+
license: z32.string().max(64).optional()
|
|
19834
20766
|
}).strict();
|
|
19835
20767
|
|
|
19836
20768
|
// ../../packages/harness-schema/src/validator.ts
|
|
@@ -20149,14 +21081,14 @@ function isSameBrainNote(publishedOrigin, memoryId) {
|
|
|
20149
21081
|
}
|
|
20150
21082
|
|
|
20151
21083
|
// src/commands/registry/read.ts
|
|
20152
|
-
import { Command as
|
|
21084
|
+
import { Command as Command26 } from "commander";
|
|
20153
21085
|
init_errors();
|
|
20154
21086
|
init_credentials();
|
|
20155
21087
|
|
|
20156
21088
|
// src/lib/untrusted-fence.ts
|
|
20157
|
-
import { randomUUID as
|
|
21089
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
20158
21090
|
function makeUntrustedSentinel() {
|
|
20159
|
-
return `KORDIS-UNTRUSTED-${
|
|
21091
|
+
return `KORDIS-UNTRUSTED-${randomUUID4()}`;
|
|
20160
21092
|
}
|
|
20161
21093
|
function fenceUntrusted(sentinel, text) {
|
|
20162
21094
|
const neutralized = text.replace(/KORDIS-UNTRUSTED-/g, "KORDIS-UNTRUSTED\u200B-");
|
|
@@ -20172,7 +21104,7 @@ function untrustedFenceNotice(sentinel) {
|
|
|
20172
21104
|
init_ui();
|
|
20173
21105
|
init_version();
|
|
20174
21106
|
function readCommand() {
|
|
20175
|
-
return new
|
|
21107
|
+
return new Command26("read").description("read a judgment from the public layer into your context (nothing is installed)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was read").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
|
|
20176
21108
|
"after",
|
|
20177
21109
|
"\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
|
|
20178
21110
|
).action(async (rawSlug, opts) => {
|
|
@@ -20320,7 +21252,7 @@ async function recordUsage(verb, detail, where = {}) {
|
|
|
20320
21252
|
init_vault();
|
|
20321
21253
|
init_ui();
|
|
20322
21254
|
function publishCommand() {
|
|
20323
|
-
return new
|
|
21255
|
+
return new Command27("publish").description("publish one decision from this brain to the public layer").argument("<ref>", "handle, id, path, or exact title of the note to publish").option("--slug <name>", "the package name (default: derived from the note title)").option("--yes", "actually send it (without this, nothing leaves this machine)").option("--reason <text>", "why this is being published \u2014 the user's own words").option("--no-lineage", "do not propose lineage from what this machine has adapted").option(
|
|
20324
21256
|
"--bump <kind>",
|
|
20325
21257
|
"patch | minor | major \u2014 publish the next version of a package this note already produced"
|
|
20326
21258
|
).option("--wait <ms>", "how long to wait for the AI review before answering (default 3000)").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").addHelpText(
|
|
@@ -20461,7 +21393,7 @@ async function hostedAssertion(target, ref) {
|
|
|
20461
21393
|
const cloud = await openCloudBrain(target);
|
|
20462
21394
|
const found = await cloudOpen(cloud, ref, await projectAskedFrom());
|
|
20463
21395
|
const memory = MemorySchema.safeParse(found.memory);
|
|
20464
|
-
const claims =
|
|
21396
|
+
const claims = z33.array(ClaimSchema).safeParse(found.claims);
|
|
20465
21397
|
if (!memory.success || !claims.success) {
|
|
20466
21398
|
throw memgError(
|
|
20467
21399
|
"server_refused",
|
|
@@ -20646,13 +21578,13 @@ function parseWait(raw) {
|
|
|
20646
21578
|
}
|
|
20647
21579
|
|
|
20648
21580
|
// src/commands/registry/adapt.ts
|
|
20649
|
-
import { Command as
|
|
21581
|
+
import { Command as Command28 } from "commander";
|
|
20650
21582
|
init_errors();
|
|
20651
21583
|
|
|
20652
21584
|
// src/lib/harness-context.ts
|
|
20653
21585
|
init_brand();
|
|
20654
|
-
import { access, readFile as
|
|
20655
|
-
import { join as
|
|
21586
|
+
import { access, readFile as readFile23 } from "fs/promises";
|
|
21587
|
+
import { join as join23 } from "path";
|
|
20656
21588
|
var PER_FILE_CAP = 16 * 1024;
|
|
20657
21589
|
var TOTAL_CAP = 48 * 1024;
|
|
20658
21590
|
var TRUNCATION_RESERVE = 64;
|
|
@@ -20697,13 +21629,13 @@ function extractHeadings(content) {
|
|
|
20697
21629
|
}
|
|
20698
21630
|
async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
20699
21631
|
const candidates = [
|
|
20700
|
-
{ path:
|
|
20701
|
-
{ path:
|
|
20702
|
-
{ path:
|
|
20703
|
-
{ path:
|
|
20704
|
-
{ path:
|
|
20705
|
-
{ path:
|
|
20706
|
-
{ path:
|
|
21632
|
+
{ path: join23(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
|
|
21633
|
+
{ path: join23(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
|
|
21634
|
+
{ path: join23(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
|
|
21635
|
+
{ path: join23(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
|
|
21636
|
+
{ path: join23(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
|
|
21637
|
+
{ path: join23(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
|
|
21638
|
+
{ path: join23(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
|
|
20707
21639
|
];
|
|
20708
21640
|
const files2 = [];
|
|
20709
21641
|
let totalBytes = 0;
|
|
@@ -20711,7 +21643,7 @@ async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
|
20711
21643
|
if (!await pathExists(cand.path)) continue;
|
|
20712
21644
|
let raw;
|
|
20713
21645
|
try {
|
|
20714
|
-
raw = await
|
|
21646
|
+
raw = await readFile23(cand.path, "utf8");
|
|
20715
21647
|
} catch {
|
|
20716
21648
|
continue;
|
|
20717
21649
|
}
|
|
@@ -20782,7 +21714,7 @@ function renderBrief(res, harness) {
|
|
|
20782
21714
|
return out.join("\n");
|
|
20783
21715
|
}
|
|
20784
21716
|
function adaptCommand() {
|
|
20785
|
-
return new
|
|
21717
|
+
return new Command28("adapt").description("fit a public judgment to your existing harness (prints a brief; writes nothing)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was adapted").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
|
|
20786
21718
|
"after",
|
|
20787
21719
|
"\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
|
|
20788
21720
|
).action(async (rawSlug, opts) => {
|
|
@@ -20830,10 +21762,10 @@ function adaptCommand() {
|
|
|
20830
21762
|
}
|
|
20831
21763
|
|
|
20832
21764
|
// src/commands/registry/info.ts
|
|
20833
|
-
import { Command as
|
|
21765
|
+
import { Command as Command29 } from "commander";
|
|
20834
21766
|
init_ui();
|
|
20835
21767
|
function infoCommand() {
|
|
20836
|
-
return new
|
|
21768
|
+
return new Command29("info").description("what a package in the public layer is, without reading it").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").addHelpText(
|
|
20837
21769
|
"after",
|
|
20838
21770
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
20839
21771
|
).action(async (rawSlug) => {
|
|
@@ -20919,7 +21851,7 @@ function renderCanonical(component) {
|
|
|
20919
21851
|
}
|
|
20920
21852
|
|
|
20921
21853
|
// src/commands/registry/search.ts
|
|
20922
|
-
import { Command as
|
|
21854
|
+
import { Command as Command30 } from "commander";
|
|
20923
21855
|
init_errors();
|
|
20924
21856
|
init_ui();
|
|
20925
21857
|
var SEARCHABLE_TYPES = PHASE_1_TYPES;
|
|
@@ -20927,7 +21859,7 @@ var LIMIT_MIN = 1;
|
|
|
20927
21859
|
var LIMIT_MAX = 100;
|
|
20928
21860
|
var LIMIT_DEFAULT = 20;
|
|
20929
21861
|
function searchCommand() {
|
|
20930
|
-
return new
|
|
21862
|
+
return new Command30("search").description("find a judgment in the public layer (titles only; nothing is read or installed)").argument("[query...]", "words to match against title and description").option("--type <type>", `narrow by kind: ${SEARCHABLE_TYPES.join("|")}`).option("--limit <n>", `how many results (${LIMIT_MIN}-${LIMIT_MAX})`, String(LIMIT_DEFAULT)).addHelpText(
|
|
20931
21863
|
"after",
|
|
20932
21864
|
"\nEnvironment:\n KORDIS_API_URL=<url> search this registry instead of the linked one\n"
|
|
20933
21865
|
).action(async (query, opts) => {
|
|
@@ -21105,11 +22037,11 @@ function parseLimit2(raw) {
|
|
|
21105
22037
|
}
|
|
21106
22038
|
|
|
21107
22039
|
// src/commands/registry/pull.ts
|
|
21108
|
-
import { Command as
|
|
21109
|
-
import
|
|
22040
|
+
import { Command as Command31 } from "commander";
|
|
22041
|
+
import prompts4 from "prompts";
|
|
21110
22042
|
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
21111
|
-
import { mkdir as
|
|
21112
|
-
import { dirname as
|
|
22043
|
+
import { mkdir as mkdir16, readFile as readFile25, stat as stat8, writeFile as writeFile13 } from "fs/promises";
|
|
22044
|
+
import { dirname as dirname8, join as join25, resolve as resolve12, sep as sep3 } from "path";
|
|
21113
22045
|
|
|
21114
22046
|
// ../../packages/translators/src/skill/identity.ts
|
|
21115
22047
|
function buildSkillFile(input, outputPath) {
|
|
@@ -21684,80 +22616,31 @@ function getTranslator(type, format) {
|
|
|
21684
22616
|
return fn;
|
|
21685
22617
|
}
|
|
21686
22618
|
|
|
21687
|
-
//
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
|
|
21695
|
-
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
}
|
|
21699
|
-
function lockfilePath() {
|
|
21700
|
-
return join22(brandHome(), "installed.json");
|
|
21701
|
-
}
|
|
21702
|
-
function kordisLockfilePath() {
|
|
21703
|
-
return join22(process.env["KORDIS_HOME"] ?? join22(homedir4(), ".kordis"), "installed.json");
|
|
21704
|
-
}
|
|
21705
|
-
function lockfileDisplayPath() {
|
|
21706
|
-
const path = lockfilePath();
|
|
21707
|
-
const home = process.env["HOME"];
|
|
21708
|
-
return home !== void 0 && home !== "" && path.startsWith(home) ? "~" + path.slice(home.length) : path;
|
|
21709
|
-
}
|
|
21710
|
-
async function migrateFromKordis() {
|
|
21711
|
-
const source = kordisLockfilePath();
|
|
21712
|
-
const destination = lockfilePath();
|
|
21713
|
-
try {
|
|
21714
|
-
await mkdir14(brandHome(), { recursive: true });
|
|
21715
|
-
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
21716
|
-
} catch {
|
|
21717
|
-
return;
|
|
21718
|
-
}
|
|
21719
|
-
try {
|
|
21720
|
-
const raw = await readFile22(destination, "utf8");
|
|
21721
|
-
const parsed = JSON.parse(raw);
|
|
21722
|
-
if (parsed.migratedFrom === void 0) {
|
|
21723
|
-
parsed.migratedFrom = source;
|
|
21724
|
-
await writeLockfile(parsed);
|
|
21725
|
-
}
|
|
21726
|
-
} catch {
|
|
21727
|
-
}
|
|
21728
|
-
}
|
|
21729
|
-
async function readLockfile() {
|
|
21730
|
-
try {
|
|
21731
|
-
return JSON.parse(await readFile22(lockfilePath(), "utf8"));
|
|
21732
|
-
} catch {
|
|
21733
|
-
await migrateFromKordis();
|
|
22619
|
+
// ../../packages/translators/src/merge-emitted-file.ts
|
|
22620
|
+
import tomlLib2 from "@iarna/toml";
|
|
22621
|
+
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
22622
|
+
var MergeRefusal = class extends Error {
|
|
22623
|
+
code;
|
|
22624
|
+
reason;
|
|
22625
|
+
constructor(reason) {
|
|
22626
|
+
super(refusalMessage(reason));
|
|
22627
|
+
this.name = "MergeRefusal";
|
|
22628
|
+
this.code = reason.kind === "prototype_key" ? "unsafe_package_config" : "merge_refused";
|
|
22629
|
+
this.reason = reason;
|
|
21734
22630
|
}
|
|
21735
|
-
|
|
21736
|
-
|
|
21737
|
-
|
|
21738
|
-
|
|
22631
|
+
};
|
|
22632
|
+
function refusalMessage(reason) {
|
|
22633
|
+
switch (reason.kind) {
|
|
22634
|
+
case "prototype_key":
|
|
22635
|
+
return `refusing to merge config fragment \u2014 prototype-polluting key "${reason.key}" at ${reason.path}`;
|
|
22636
|
+
case "malformed_file":
|
|
22637
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing file is not valid ${reason.format.toUpperCase()}: ${reason.parseError}`;
|
|
22638
|
+
case "non_object_root":
|
|
22639
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing ${reason.format.toUpperCase()} root is not an object`;
|
|
22640
|
+
case "dangling_marker":
|
|
22641
|
+
return `refusing to merge \u2014 found a "kordis:${reason.fullSlug}" start marker with no matching end marker`;
|
|
21739
22642
|
}
|
|
21740
22643
|
}
|
|
21741
|
-
async function writeLockfile(lf) {
|
|
21742
|
-
await mkdir14(brandHome(), { recursive: true });
|
|
21743
|
-
await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
21744
|
-
encoding: "utf8",
|
|
21745
|
-
mode: 384
|
|
21746
|
-
});
|
|
21747
|
-
}
|
|
21748
|
-
async function recordInstalled(fullSlug, entry) {
|
|
21749
|
-
const home = brandHome();
|
|
21750
|
-
await withFileLock(join22(home, "installed.lock"), home, async () => {
|
|
21751
|
-
const lf = await readLockfile();
|
|
21752
|
-
lf.packages[fullSlug] = entry;
|
|
21753
|
-
await writeLockfile(lf);
|
|
21754
|
-
});
|
|
21755
|
-
}
|
|
21756
|
-
|
|
21757
|
-
// src/lib/merge-emitted-file.ts
|
|
21758
|
-
init_errors();
|
|
21759
|
-
import tomlLib2 from "@iarna/toml";
|
|
21760
|
-
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
21761
22644
|
function isPlainObject(v) {
|
|
21762
22645
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21763
22646
|
}
|
|
@@ -21782,11 +22665,7 @@ function assertNoProtoKeys(value, path) {
|
|
|
21782
22665
|
if (!isPlainObject(value)) return;
|
|
21783
22666
|
for (const [k, v] of Object.entries(value)) {
|
|
21784
22667
|
if (PROTO_KEYS.has(k)) {
|
|
21785
|
-
throw
|
|
21786
|
-
"unsafe_package_config",
|
|
21787
|
-
`refusing to merge config fragment \u2014 prototype-polluting key "${k}" at ${[...path, k].join(".")}`,
|
|
21788
|
-
"Nothing was written. The package emits a config key named __proto__, constructor or prototype, which no legitimate tool config needs. Do not install it, and tell the author if you did not publish it."
|
|
21789
|
-
);
|
|
22668
|
+
throw new MergeRefusal({ kind: "prototype_key", key: k, path: [...path, k].join(".") });
|
|
21790
22669
|
}
|
|
21791
22670
|
assertNoProtoKeys(v, [...path, k]);
|
|
21792
22671
|
}
|
|
@@ -21795,12 +22674,15 @@ function isOwnedServerLeaf(path) {
|
|
|
21795
22674
|
return path.length === 2 && (path[0] === "mcpServers" || path[0] === "mcp_servers");
|
|
21796
22675
|
}
|
|
21797
22676
|
function deepMergeFragment(existing, fragment, path = []) {
|
|
21798
|
-
|
|
22677
|
+
assertNoProtoKeys(fragment, path);
|
|
22678
|
+
return mergeAt(existing, fragment, path);
|
|
22679
|
+
}
|
|
22680
|
+
function mergeAt(existing, fragment, path) {
|
|
21799
22681
|
if (isOwnedServerLeaf(path)) return fragment;
|
|
21800
22682
|
if (isPlainObject(existing) && isPlainObject(fragment)) {
|
|
21801
22683
|
const out = { ...existing };
|
|
21802
22684
|
for (const [k, v] of Object.entries(fragment)) {
|
|
21803
|
-
out[k] =
|
|
22685
|
+
out[k] = mergeAt(existing[k], v, [...path, k]);
|
|
21804
22686
|
}
|
|
21805
22687
|
return out;
|
|
21806
22688
|
}
|
|
@@ -21843,18 +22725,15 @@ function mergeStructuredFile(existingRaw, fragmentRaw, format, targetPath) {
|
|
|
21843
22725
|
try {
|
|
21844
22726
|
existing = parseStructured(existingRaw, format);
|
|
21845
22727
|
} catch (err) {
|
|
21846
|
-
throw
|
|
21847
|
-
"
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
22728
|
+
throw new MergeRefusal({
|
|
22729
|
+
kind: "malformed_file",
|
|
22730
|
+
targetPath,
|
|
22731
|
+
format,
|
|
22732
|
+
parseError: err.message
|
|
22733
|
+
});
|
|
21851
22734
|
}
|
|
21852
22735
|
if (!isPlainObject(existing) && isPlainObject(fragment)) {
|
|
21853
|
-
throw
|
|
21854
|
-
"merge_refused",
|
|
21855
|
-
`refusing to merge into "${targetPath}" \u2014 existing ${format.toUpperCase()} root is not an object`,
|
|
21856
|
-
`Nothing was written. ${targetPath} parses, but its top level is not an object, so a config fragment cannot be merged into it. Fix its shape or move it aside, then run the install again.`
|
|
21857
|
-
);
|
|
22736
|
+
throw new MergeRefusal({ kind: "non_object_root", targetPath, format });
|
|
21858
22737
|
}
|
|
21859
22738
|
const merged = deepMergeFragment(existing, fragment);
|
|
21860
22739
|
return stringifyStructured(merged, format);
|
|
@@ -21876,15 +22755,100 @@ function mergeFencedFile(existingRaw, fullSlug, block) {
|
|
|
21876
22755
|
return existingRaw.slice(0, startMatch.index) + trimmedBlock + existingRaw.slice(spanEnd);
|
|
21877
22756
|
}
|
|
21878
22757
|
if (startMatch && endIdx === -1) {
|
|
21879
|
-
throw
|
|
21880
|
-
"merge_refused",
|
|
21881
|
-
`refusing to merge \u2014 found a "kordis:${fullSlug}" start marker with no matching end marker`,
|
|
21882
|
-
`Nothing was written. The file has a dangling <!-- kordis:${fullSlug}:\u2026:start --> marker. Delete that line, or restore its <!-- kordis:${fullSlug}:end --> line, then run the install again.`
|
|
21883
|
-
);
|
|
22758
|
+
throw new MergeRefusal({ kind: "dangling_marker", fullSlug });
|
|
21884
22759
|
}
|
|
21885
22760
|
return existingRaw.trimEnd() + "\n\n" + trimmedBlock + "\n";
|
|
21886
22761
|
}
|
|
21887
22762
|
|
|
22763
|
+
// src/commands/registry/pull.ts
|
|
22764
|
+
init_errors();
|
|
22765
|
+
init_ui();
|
|
22766
|
+
|
|
22767
|
+
// src/lib/lockfile.ts
|
|
22768
|
+
init_brand();
|
|
22769
|
+
import { copyFile, mkdir as mkdir15, readFile as readFile24, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
|
|
22770
|
+
import { homedir as homedir4 } from "os";
|
|
22771
|
+
import { join as join24 } from "path";
|
|
22772
|
+
function effectiveRange(entry) {
|
|
22773
|
+
return entry.requestedRange ?? `^${entry.version}`;
|
|
22774
|
+
}
|
|
22775
|
+
function lockfilePath() {
|
|
22776
|
+
return join24(brandHome(), "installed.json");
|
|
22777
|
+
}
|
|
22778
|
+
function kordisLockfilePath() {
|
|
22779
|
+
return join24(process.env["KORDIS_HOME"] ?? join24(homedir4(), ".kordis"), "installed.json");
|
|
22780
|
+
}
|
|
22781
|
+
function lockfileDisplayPath() {
|
|
22782
|
+
const path = lockfilePath();
|
|
22783
|
+
const home = process.env["HOME"];
|
|
22784
|
+
return home !== void 0 && home !== "" && path.startsWith(home) ? "~" + path.slice(home.length) : path;
|
|
22785
|
+
}
|
|
22786
|
+
async function migrateFromKordis() {
|
|
22787
|
+
const source = kordisLockfilePath();
|
|
22788
|
+
const destination = lockfilePath();
|
|
22789
|
+
try {
|
|
22790
|
+
await mkdir15(brandHome(), { recursive: true });
|
|
22791
|
+
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
22792
|
+
} catch {
|
|
22793
|
+
return;
|
|
22794
|
+
}
|
|
22795
|
+
try {
|
|
22796
|
+
const raw = await readFile24(destination, "utf8");
|
|
22797
|
+
const parsed = JSON.parse(raw);
|
|
22798
|
+
if (parsed.migratedFrom === void 0) {
|
|
22799
|
+
parsed.migratedFrom = source;
|
|
22800
|
+
await writeLockfile(parsed);
|
|
22801
|
+
}
|
|
22802
|
+
} catch {
|
|
22803
|
+
}
|
|
22804
|
+
}
|
|
22805
|
+
async function readLockfile() {
|
|
22806
|
+
try {
|
|
22807
|
+
return JSON.parse(await readFile24(lockfilePath(), "utf8"));
|
|
22808
|
+
} catch {
|
|
22809
|
+
await migrateFromKordis();
|
|
22810
|
+
}
|
|
22811
|
+
try {
|
|
22812
|
+
return JSON.parse(await readFile24(lockfilePath(), "utf8"));
|
|
22813
|
+
} catch {
|
|
22814
|
+
return { packages: {} };
|
|
22815
|
+
}
|
|
22816
|
+
}
|
|
22817
|
+
async function writeLockfile(lf) {
|
|
22818
|
+
await mkdir15(brandHome(), { recursive: true });
|
|
22819
|
+
await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
22820
|
+
encoding: "utf8",
|
|
22821
|
+
mode: 384
|
|
22822
|
+
});
|
|
22823
|
+
}
|
|
22824
|
+
async function recordInstalled(fullSlug, entry) {
|
|
22825
|
+
const home = brandHome();
|
|
22826
|
+
await withFileLock(join24(home, "installed.lock"), home, async () => {
|
|
22827
|
+
const lf = await readLockfile();
|
|
22828
|
+
lf.packages[fullSlug] = entry;
|
|
22829
|
+
await writeLockfile(lf);
|
|
22830
|
+
});
|
|
22831
|
+
}
|
|
22832
|
+
|
|
22833
|
+
// src/lib/merge-refusal.ts
|
|
22834
|
+
init_errors();
|
|
22835
|
+
function adaptMergeRefusal(err) {
|
|
22836
|
+
if (!(err instanceof MergeRefusal)) return null;
|
|
22837
|
+
return memgError(err.code, err.message, mergeRefusalHint(err.reason));
|
|
22838
|
+
}
|
|
22839
|
+
function mergeRefusalHint(reason) {
|
|
22840
|
+
switch (reason.kind) {
|
|
22841
|
+
case "prototype_key":
|
|
22842
|
+
return "Nothing was written. The package emits a config key named __proto__, constructor or prototype, which no legitimate tool config needs. Do not install it, and tell the author if you did not publish it.";
|
|
22843
|
+
case "malformed_file":
|
|
22844
|
+
return `Nothing was written. Fix or move aside the malformed ${reason.targetPath}, then run the install again. It is not overwritten blind because it may hold configuration from other tools and other packages.`;
|
|
22845
|
+
case "non_object_root":
|
|
22846
|
+
return `Nothing was written. ${reason.targetPath} parses, but its top level is not an object, so a config fragment cannot be merged into it. Fix its shape or move it aside, then run the install again.`;
|
|
22847
|
+
case "dangling_marker":
|
|
22848
|
+
return `Nothing was written. The file has a dangling <!-- kordis:${reason.fullSlug}:\u2026:start --> marker. Delete that line, or restore its <!-- kordis:${reason.fullSlug}:end --> line, then run the install again.`;
|
|
22849
|
+
}
|
|
22850
|
+
}
|
|
22851
|
+
|
|
21888
22852
|
// src/commands/registry/pull.ts
|
|
21889
22853
|
async function requireExecutableApproval(pkg, fullSlug, files2, approve2) {
|
|
21890
22854
|
if (!pkg.capabilities.executable) return;
|
|
@@ -21922,7 +22886,7 @@ async function promptForExecutableApproval(review) {
|
|
|
21922
22886
|
);
|
|
21923
22887
|
printHuman(c.gray(" \u2500\u2500 END EXECUTABLE CONFIG \u2500\u2500"));
|
|
21924
22888
|
}
|
|
21925
|
-
const { confirmed } = await
|
|
22889
|
+
const { confirmed } = await prompts4({
|
|
21926
22890
|
type: "confirm",
|
|
21927
22891
|
name: "confirmed",
|
|
21928
22892
|
message: "Install and activate these exact executable settings?",
|
|
@@ -22015,7 +22979,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
22015
22979
|
);
|
|
22016
22980
|
for (const f of translated.files) {
|
|
22017
22981
|
const abs = safeInstallPath(installRoot, f.path);
|
|
22018
|
-
await
|
|
22982
|
+
await mkdir16(dirname8(abs), { recursive: true });
|
|
22019
22983
|
await applyEmittedFile(abs, f, fullSlug, notes);
|
|
22020
22984
|
written.push(abs);
|
|
22021
22985
|
}
|
|
@@ -22044,7 +23008,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
22044
23008
|
};
|
|
22045
23009
|
}
|
|
22046
23010
|
function registryPullCommand() {
|
|
22047
|
-
return new
|
|
23011
|
+
return new Command31("pull").description("install a package from the public layer into this project").argument(
|
|
22048
23012
|
"<full_slug>",
|
|
22049
23013
|
"package identifier {username}/{slug}, optionally @{version} or @{range}"
|
|
22050
23014
|
).option("--tool <name>", "target tool format (default: work it out from this directory)").option("--target <path>", "install into this directory instead of the current one").option("--with-recommends", "also install recommended (depth-0) dependencies", false).option(
|
|
@@ -22171,7 +23135,7 @@ var TOOL_MARKERS = [
|
|
|
22171
23135
|
async function detectTool(root) {
|
|
22172
23136
|
for (const [path, tool] of TOOL_MARKERS) {
|
|
22173
23137
|
try {
|
|
22174
|
-
await
|
|
23138
|
+
await stat8(join25(root, path));
|
|
22175
23139
|
return tool;
|
|
22176
23140
|
} catch {
|
|
22177
23141
|
}
|
|
@@ -22187,7 +23151,7 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
22187
23151
|
"Nothing was written. The package emits an absolute file path. Tell the author to publish it with relative paths only; if you did not publish it, treat it as an attempt to write outside your project."
|
|
22188
23152
|
);
|
|
22189
23153
|
}
|
|
22190
|
-
const abs =
|
|
23154
|
+
const abs = join25(installRoot, relPath);
|
|
22191
23155
|
const resolvedRoot = resolve12(installRoot);
|
|
22192
23156
|
const resolvedAbs = resolve12(abs);
|
|
22193
23157
|
if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep3)) {
|
|
@@ -22203,9 +23167,9 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
22203
23167
|
} catch {
|
|
22204
23168
|
return abs;
|
|
22205
23169
|
}
|
|
22206
|
-
let ancestor =
|
|
23170
|
+
let ancestor = dirname8(resolvedAbs);
|
|
22207
23171
|
while (!existsSync(ancestor)) {
|
|
22208
|
-
const parent =
|
|
23172
|
+
const parent = dirname8(ancestor);
|
|
22209
23173
|
if (parent === ancestor) break;
|
|
22210
23174
|
ancestor = parent;
|
|
22211
23175
|
}
|
|
@@ -22253,12 +23217,17 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
22253
23217
|
}
|
|
22254
23218
|
let existingRaw = null;
|
|
22255
23219
|
try {
|
|
22256
|
-
existingRaw = await
|
|
23220
|
+
existingRaw = await readFile25(abs, "utf8");
|
|
22257
23221
|
} catch (err) {
|
|
22258
23222
|
if (err.code !== "ENOENT") throw err;
|
|
22259
23223
|
}
|
|
22260
23224
|
if (f.format === "json" || f.format === "yaml" || f.format === "toml") {
|
|
22261
|
-
|
|
23225
|
+
let merged;
|
|
23226
|
+
try {
|
|
23227
|
+
merged = mergeStructuredFile(existingRaw, f.content, f.format, abs);
|
|
23228
|
+
} catch (err) {
|
|
23229
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
23230
|
+
}
|
|
22262
23231
|
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
22263
23232
|
notes?.push(
|
|
22264
23233
|
`merged into ${f.path} \u2014 comments/formatting in the existing file are not preserved`
|
|
@@ -22267,16 +23236,22 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
22267
23236
|
await writeFile13(abs, merged, "utf8");
|
|
22268
23237
|
return;
|
|
22269
23238
|
}
|
|
22270
|
-
|
|
23239
|
+
let fenced;
|
|
23240
|
+
try {
|
|
23241
|
+
fenced = mergeFencedFile(existingRaw, fullSlug, f.content);
|
|
23242
|
+
} catch (err) {
|
|
23243
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
23244
|
+
}
|
|
23245
|
+
await writeFile13(abs, fenced, "utf8");
|
|
22271
23246
|
}
|
|
22272
23247
|
|
|
22273
23248
|
// src/commands/registry/update.ts
|
|
22274
23249
|
init_errors();
|
|
22275
23250
|
init_ui();
|
|
22276
|
-
import { Command as
|
|
23251
|
+
import { Command as Command32 } from "commander";
|
|
22277
23252
|
import semver2 from "semver";
|
|
22278
23253
|
function registryUpdateCommand() {
|
|
22279
|
-
return new
|
|
23254
|
+
return new Command32("update").description("update installed packages to their latest in-range versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).addHelpText(
|
|
22280
23255
|
"after",
|
|
22281
23256
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
22282
23257
|
).action(async (fullSlugArg, opts) => {
|
|
@@ -22410,10 +23385,10 @@ function registryUpdateCommand() {
|
|
|
22410
23385
|
|
|
22411
23386
|
// src/commands/registry/outdated.ts
|
|
22412
23387
|
init_ui();
|
|
22413
|
-
import { Command as
|
|
23388
|
+
import { Command as Command33 } from "commander";
|
|
22414
23389
|
import semver3 from "semver";
|
|
22415
23390
|
function outdatedCommand() {
|
|
22416
|
-
return new
|
|
23391
|
+
return new Command33("outdated").description("check installed packages for available updates").addHelpText(
|
|
22417
23392
|
"after",
|
|
22418
23393
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
22419
23394
|
).action(async () => {
|
|
@@ -22505,9 +23480,9 @@ function outdatedCommand() {
|
|
|
22505
23480
|
// src/commands/registry/versions.ts
|
|
22506
23481
|
init_errors();
|
|
22507
23482
|
init_ui();
|
|
22508
|
-
import { Command as
|
|
23483
|
+
import { Command as Command34 } from "commander";
|
|
22509
23484
|
function versionsCommand() {
|
|
22510
|
-
return new
|
|
23485
|
+
return new Command34("versions").description("list every published version of a package").argument("<full_slug>", "package identifier {username}/{slug}").addHelpText(
|
|
22511
23486
|
"after",
|
|
22512
23487
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
22513
23488
|
).action(async (fullSlug) => {
|
|
@@ -22571,9 +23546,9 @@ function relativeTime(isoString) {
|
|
|
22571
23546
|
// src/commands/registry/diff.ts
|
|
22572
23547
|
init_errors();
|
|
22573
23548
|
init_ui();
|
|
22574
|
-
import { Command as
|
|
23549
|
+
import { Command as Command35 } from "commander";
|
|
22575
23550
|
function diffCommand() {
|
|
22576
|
-
return new
|
|
23551
|
+
return new Command35("diff").description("show what changed between two versions of a package").argument(
|
|
22577
23552
|
"<full_slug>",
|
|
22578
23553
|
"package identifier {username}/{slug}, or {username}/{slug}@{from}..{to}"
|
|
22579
23554
|
).argument("[from]", "older version (omit to use the second-latest)").argument("[to]", "newer version (omit to use the latest)").addHelpText(
|
|
@@ -22650,7 +23625,7 @@ function diffCommand() {
|
|
|
22650
23625
|
}
|
|
22651
23626
|
|
|
22652
23627
|
// src/commands/registry/list.ts
|
|
22653
|
-
import { Command as
|
|
23628
|
+
import { Command as Command36 } from "commander";
|
|
22654
23629
|
init_errors();
|
|
22655
23630
|
init_ui();
|
|
22656
23631
|
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
@@ -22673,7 +23648,7 @@ function parseUsername(raw) {
|
|
|
22673
23648
|
return parsed.data;
|
|
22674
23649
|
}
|
|
22675
23650
|
function listCommand() {
|
|
22676
|
-
return new
|
|
23651
|
+
return new Command36("list").description("list packages \u2014 your own with no argument, or a public user\u2019s").argument("[username]", "whose packages to list (omit for your own)").option("--limit <n>", "maximum results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number for your own packages (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "continue a public listing from a previous result").option(
|
|
22677
23652
|
"--sort <mode>",
|
|
22678
23653
|
"latest|popular for your own; stars|downloads|recent|updated for a public listing",
|
|
22679
23654
|
"latest"
|
|
@@ -22770,16 +23745,18 @@ init_push();
|
|
|
22770
23745
|
|
|
22771
23746
|
// src/commands/recall.ts
|
|
22772
23747
|
init_src();
|
|
23748
|
+
init_exclude();
|
|
22773
23749
|
init_config();
|
|
22774
|
-
import { Command as
|
|
22775
|
-
import { basename as
|
|
23750
|
+
import { Command as Command38 } from "commander";
|
|
23751
|
+
import { basename as basename3, resolve as resolve13 } from "path";
|
|
22776
23752
|
init_index_build();
|
|
22777
23753
|
init_cloud_brain();
|
|
22778
23754
|
init_errors();
|
|
23755
|
+
init_held_back();
|
|
22779
23756
|
init_vault();
|
|
22780
23757
|
init_ui();
|
|
22781
23758
|
function recallCommand() {
|
|
22782
|
-
return new
|
|
23759
|
+
return new Command38("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
|
|
22783
23760
|
"--brain <name>",
|
|
22784
23761
|
"search one existing hosted brain by name or id, without changing the pointer"
|
|
22785
23762
|
).option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option(
|
|
@@ -22800,6 +23777,10 @@ function recallCommand() {
|
|
|
22800
23777
|
let unsearchedBrains = [];
|
|
22801
23778
|
let unsearchedLocal = [];
|
|
22802
23779
|
let searchedLocalName = null;
|
|
23780
|
+
let nothingToSearch = null;
|
|
23781
|
+
let alsoLeftOut = null;
|
|
23782
|
+
let namedIsPointer = false;
|
|
23783
|
+
let serverHint;
|
|
22803
23784
|
let isScaffolding;
|
|
22804
23785
|
let templatesWithheld = 0;
|
|
22805
23786
|
let spread = null;
|
|
@@ -22846,8 +23827,19 @@ function recallCommand() {
|
|
|
22846
23827
|
}));
|
|
22847
23828
|
spread = isScopeSpread(answered.scope_spread) ? answered.scope_spread : null;
|
|
22848
23829
|
templatesWithheld = Number.isInteger(answered.templates_withheld) && (answered.templates_withheld ?? 0) > 0 ? answered.templates_withheld : 0;
|
|
22849
|
-
if (rows.
|
|
22850
|
-
|
|
23830
|
+
if (rows.every(({ card }) => unfilledIds.has(card.id))) {
|
|
23831
|
+
const listed = await listCloudBrains(cloud.token, cloud.apiUrl, { retries: 0 }).catch(
|
|
23832
|
+
() => []
|
|
23833
|
+
);
|
|
23834
|
+
unsearchedBrains = listed.filter((b) => b.id !== cloud.brainId);
|
|
23835
|
+
if (listed.find((b) => b.id === cloud.brainId)?.note_count === 0) {
|
|
23836
|
+
nothingToSearch = { kind: "no_notes" };
|
|
23837
|
+
}
|
|
23838
|
+
if (opts.brain !== void 0) {
|
|
23839
|
+
const pointer = await hostedPointer().catch(() => null);
|
|
23840
|
+
namedIsPointer = pointer !== null && pointer.id === cloud.brainId && (pointer.signedIn || pointer.signInPending);
|
|
23841
|
+
}
|
|
23842
|
+
serverHint = typeof answered.hint === "string" && answered.hint.trim() !== "" ? answered.hint : void 0;
|
|
22851
23843
|
}
|
|
22852
23844
|
isScaffolding = (card) => unfilledIds.has(card.id);
|
|
22853
23845
|
reasons = /* @__PURE__ */ new Map();
|
|
@@ -22889,7 +23881,11 @@ function recallCommand() {
|
|
|
22889
23881
|
);
|
|
22890
23882
|
isScaffolding = (card) => scaffolding.has(byId.get(card.id)?.path ?? "");
|
|
22891
23883
|
const sides = new Set(local.conflicts.flatMap((conflict) => conflict.between));
|
|
22892
|
-
const capped2 = capTemplates(
|
|
23884
|
+
const capped2 = capTemplates(
|
|
23885
|
+
local.memory_cards,
|
|
23886
|
+
isScaffolding,
|
|
23887
|
+
(card) => sides.has(card.id)
|
|
23888
|
+
);
|
|
22893
23889
|
templatesWithheld = capped2.withheld.length;
|
|
22894
23890
|
result = { ...local, memory_cards: capped2.kept };
|
|
22895
23891
|
entries = built.entries;
|
|
@@ -22902,8 +23898,16 @@ function recallCommand() {
|
|
|
22902
23898
|
built.entries.map((e) => declaredScope(e.memory))
|
|
22903
23899
|
);
|
|
22904
23900
|
}
|
|
22905
|
-
if (result.memory_cards.
|
|
22906
|
-
|
|
23901
|
+
if (result.memory_cards.every((card) => isScaffolding(card))) {
|
|
23902
|
+
const leftOut = await adapter.scan().then((scan) => leftOutOf(heldBack(scan), built.denied)).catch(() => void 0);
|
|
23903
|
+
if (built.entries.length === 0) {
|
|
23904
|
+
if (leftOut !== void 0) {
|
|
23905
|
+
nothingToSearch = leftOut === null ? { kind: "no_notes" } : { kind: "all_left_out", leftOut };
|
|
23906
|
+
}
|
|
23907
|
+
} else {
|
|
23908
|
+
alsoLeftOut = leftOut ?? null;
|
|
23909
|
+
}
|
|
23910
|
+
searchedLocalName = basename3(resolve13(brain.root));
|
|
22907
23911
|
unsearchedLocal = await loadConfig().then(
|
|
22908
23912
|
(cfg) => cfg.brain.brains.filter((other) => resolve13(other.root) !== resolve13(brain.root)).map((other) => ({ root: other.root }))
|
|
22909
23913
|
).catch(() => []);
|
|
@@ -22931,6 +23935,32 @@ function recallCommand() {
|
|
|
22931
23935
|
);
|
|
22932
23936
|
reasons = result.conflicts.length > 0 ? await conflictReasons(brain.root, result.conflicts, byId) : /* @__PURE__ */ new Map();
|
|
22933
23937
|
}
|
|
23938
|
+
const reachedSomethingWritten = rows.some(({ card }) => !isScaffolding(card));
|
|
23939
|
+
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
23940
|
+
const advice = !reachedSomethingWritten ? emptyRecallAdvice({
|
|
23941
|
+
query,
|
|
23942
|
+
depth,
|
|
23943
|
+
scoped: Boolean(opts.scope),
|
|
23944
|
+
chosenWith: {
|
|
23945
|
+
...opts.brain === void 0 || namedIsPointer ? {} : { brain: answeredBy ?? opts.brain },
|
|
23946
|
+
...opts.local === true ? { local: true } : {},
|
|
23947
|
+
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
23948
|
+
},
|
|
23949
|
+
nothingToSearch,
|
|
23950
|
+
alsoLeftOut,
|
|
23951
|
+
otherScript,
|
|
23952
|
+
unsearched: unsearchedBrains.length > 0 ? {
|
|
23953
|
+
searched: answeredBy ?? "",
|
|
23954
|
+
flag: "--brain",
|
|
23955
|
+
others: unsearchedBrains.map((other) => other.name)
|
|
23956
|
+
} : unsearchedLocal.length > 0 ? {
|
|
23957
|
+
searched: searchedLocalName ?? "",
|
|
23958
|
+
flag: "--vault",
|
|
23959
|
+
others: unsearchedLocal.map((other) => other.root)
|
|
23960
|
+
} : null,
|
|
23961
|
+
semanticHint: semantic.state === "skipped" ? semantic.hint : void 0,
|
|
23962
|
+
serverHint
|
|
23963
|
+
}) : null;
|
|
22934
23964
|
printDual({
|
|
22935
23965
|
json: {
|
|
22936
23966
|
query,
|
|
@@ -22969,20 +23999,29 @@ function recallCommand() {
|
|
|
22969
23999
|
// Counted, never silent: these matched and were left off because
|
|
22970
24000
|
// they are still exactly what `init` wrote.
|
|
22971
24001
|
...templatesWithheld > 0 ? { templates_withheld: templatesWithheld } : {},
|
|
22972
|
-
...spread === null ? {} : { scope_spread: spread }
|
|
24002
|
+
...spread === null ? {} : { scope_spread: spread },
|
|
24003
|
+
// Only when nothing the person wrote came back — no card, or only
|
|
24004
|
+
// untouched templates: the one next step the footer would have led
|
|
24005
|
+
// with. An agent reading `cards: []` alone cannot tell an empty
|
|
24006
|
+
// brain from a missed word from an unread brain, and one reading
|
|
24007
|
+
// template cards alone took them for the whole brain.
|
|
24008
|
+
...advice === null ? {} : { hint: advice.hint }
|
|
22973
24009
|
},
|
|
22974
24010
|
human: () => {
|
|
22975
24011
|
const brainHeading = answeredBy === null ? "" : ` \u2014 ${oneLine(answeredBy, WIDTH.title)}`;
|
|
22976
|
-
const
|
|
22977
|
-
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
22978
|
-
const sayWhy = () => {
|
|
24012
|
+
const sayWhy = (notice2) => {
|
|
22979
24013
|
printHuman(
|
|
22980
|
-
c.yellow(
|
|
22981
|
-
|
|
22982
|
-
|
|
24014
|
+
c.yellow(`\u26A0 ${notice2.headline}`) + c.gray(`
|
|
24015
|
+
${notice2.lines.map((line) => ` ${line}`).join("\n")}
|
|
24016
|
+
`)
|
|
24017
|
+
);
|
|
24018
|
+
};
|
|
24019
|
+
const sayOtherBrains = (otherBrains) => {
|
|
24020
|
+
printHuman(
|
|
24021
|
+
c.yellow(`\u26A0 ${otherBrains.headline}`) + c.gray(
|
|
22983
24022
|
`
|
|
22984
|
-
|
|
22985
|
-
|
|
24023
|
+
${otherBrains.commands.map((cmd) => ` ${cmd}`).join("\n")}
|
|
24024
|
+
${otherBrains.safe}
|
|
22986
24025
|
`
|
|
22987
24026
|
)
|
|
22988
24027
|
);
|
|
@@ -22995,60 +24034,18 @@ function recallCommand() {
|
|
|
22995
24034
|
`)
|
|
22996
24035
|
);
|
|
22997
24036
|
};
|
|
22998
|
-
if (rows.length === 0) {
|
|
24037
|
+
if (advice !== null && rows.length === 0) {
|
|
22999
24038
|
printHuman(`## recall: ${query}${brainHeading}
|
|
23000
24039
|
`);
|
|
23001
24040
|
printHuman("_Nothing matched._\n");
|
|
23002
24041
|
sayDegraded();
|
|
23003
|
-
if (
|
|
23004
|
-
|
|
24042
|
+
if (advice.leftOut !== null) sayWhy(advice.leftOut);
|
|
24043
|
+
if (advice.otherScript !== null) {
|
|
24044
|
+
sayWhy(advice.otherScript);
|
|
23005
24045
|
return;
|
|
23006
24046
|
}
|
|
23007
|
-
if (
|
|
23008
|
-
|
|
23009
|
-
c.yellow(
|
|
23010
|
-
`\u26A0 Only \`${oneLine(answeredBy ?? "", WIDTH.title)}\` was searched \u2014 this account has ${unsearchedBrains.length + 1} brains.`
|
|
23011
|
-
) + c.gray(
|
|
23012
|
-
`
|
|
23013
|
-
${unsearchedBrains.map(
|
|
23014
|
-
(brain) => ` memgineering recall ${JSON.stringify(query)} --brain ${JSON.stringify(brain.name)}`
|
|
23015
|
-
).join(
|
|
23016
|
-
"\n"
|
|
23017
|
-
)}
|
|
23018
|
-
Safe to run \u2014 reading another brain does not change which one this machine points at.
|
|
23019
|
-
`
|
|
23020
|
-
)
|
|
23021
|
-
);
|
|
23022
|
-
}
|
|
23023
|
-
if (unsearchedLocal.length > 0) {
|
|
23024
|
-
printHuman(
|
|
23025
|
-
c.yellow(
|
|
23026
|
-
`\u26A0 Only \`${oneLine(searchedLocalName ?? "", WIDTH.title)}\` was searched \u2014 ${unsearchedLocal.length + 1} brains are linked on this machine.`
|
|
23027
|
-
) + c.gray(
|
|
23028
|
-
`
|
|
23029
|
-
${unsearchedLocal.map(
|
|
23030
|
-
(other) => ` memgineering recall ${JSON.stringify(query)} --vault ${JSON.stringify(other.root)}`
|
|
23031
|
-
).join(
|
|
23032
|
-
"\n"
|
|
23033
|
-
)}
|
|
23034
|
-
Safe to run \u2014 reading another brain does not change which one this folder points at.
|
|
23035
|
-
`
|
|
23036
|
-
)
|
|
23037
|
-
);
|
|
23038
|
-
}
|
|
23039
|
-
printHuman(
|
|
23040
|
-
c.gray(
|
|
23041
|
-
// "Within this brain" is load-bearing when the warning above
|
|
23042
|
-
// has just said another brain went unsearched. The enumeration
|
|
23043
|
-
// reads as the whole surface — a recheck tester called this
|
|
23044
|
-
// the last line on screen and a false all-clear, sitting below
|
|
23045
|
-
// the one line that says otherwise.
|
|
23046
|
-
(depth === "exact" ? "Within this brain, `--depth exact` searches the card only \u2014 titles, aliases, entities, summaries and scope \u2014 try `--depth standard` to search note bodies too, or fewer words" : "Within this brain, titles, aliases, summaries and note bodies are all searched \u2014 try fewer or broader words") + // Only when the reader actually passed one. Telling somebody
|
|
23047
|
-
// to drop an argument they never supplied cost one tester a
|
|
23048
|
-
// whole command to `--help` to find out it was not their bug.
|
|
23049
|
-
(opts.scope ? ", or drop --scope." : ".")
|
|
23050
|
-
)
|
|
23051
|
-
);
|
|
24047
|
+
if (advice.otherBrains !== null) sayOtherBrains(advice.otherBrains);
|
|
24048
|
+
if (advice.withinThisBrain !== null) printHuman(c.gray(advice.withinThisBrain));
|
|
23052
24049
|
return;
|
|
23053
24050
|
}
|
|
23054
24051
|
printHuman(`## recall: ${query}${brainHeading} (${rows.length} cards)
|
|
@@ -23060,6 +24057,10 @@ ${unsearchedLocal.map(
|
|
|
23060
24057
|
"\n Every card below is still the template `init` wrote \u2014 nobody has answered\n these yet, so none of them is evidence of anything.\n `memgineering onboard` prints what to ask to fill them in.\n"
|
|
23061
24058
|
)
|
|
23062
24059
|
);
|
|
24060
|
+
if (advice !== null && advice.leftOut !== null) sayWhy(advice.leftOut);
|
|
24061
|
+
if (advice !== null && advice.otherScript === null && advice.otherBrains !== null) {
|
|
24062
|
+
sayOtherBrains(advice.otherBrains);
|
|
24063
|
+
}
|
|
23063
24064
|
}
|
|
23064
24065
|
const templateCount = rows.filter(({ card }) => isScaffolding(card)).length;
|
|
23065
24066
|
if (reachedSomethingWritten && templateCount > 0) {
|
|
@@ -23089,7 +24090,7 @@ ${unsearchedLocal.map(
|
|
|
23089
24090
|
)
|
|
23090
24091
|
);
|
|
23091
24092
|
}
|
|
23092
|
-
if (otherScript) sayWhy();
|
|
24093
|
+
if (otherScript) sayWhy(otherScriptNotice(otherScript));
|
|
23093
24094
|
const glossOn = rows.findIndex(
|
|
23094
24095
|
({ card }) => card.confidence === "stated" && !isScaffolding(card)
|
|
23095
24096
|
);
|
|
@@ -23257,6 +24258,114 @@ function ago2(days) {
|
|
|
23257
24258
|
if (days < 365) return `${Math.floor(days / 30)} months ago`;
|
|
23258
24259
|
return `${Math.floor(days / 365)} years ago`;
|
|
23259
24260
|
}
|
|
24261
|
+
function emptyRecallAdvice(facts) {
|
|
24262
|
+
const otherScript = facts.otherScript === null ? null : otherScriptNotice(facts.otherScript);
|
|
24263
|
+
const otherBrains = facts.unsearched === null || facts.unsearched.others.length === 0 ? null : unsearchedNotice(facts.query, facts.unsearched);
|
|
24264
|
+
const nothing = facts.nothingToSearch;
|
|
24265
|
+
const leftOut = nothing?.kind === "all_left_out" ? leftOutNotice(nothing.leftOut, facts.chosenWith, "all") : facts.alsoLeftOut === null || notesIn(facts.alsoLeftOut) === 0 ? null : leftOutNotice(facts.alsoLeftOut, facts.chosenWith, "some");
|
|
24266
|
+
const within = (
|
|
24267
|
+
// "Within this brain" is load-bearing when the warning above it has just
|
|
24268
|
+
// said another brain went unsearched. The enumeration reads as the whole
|
|
24269
|
+
// surface — a recheck tester called this the last line on screen and a
|
|
24270
|
+
// false all-clear, sitting below the one line that says otherwise. Below
|
|
24271
|
+
// a count of held-back notes it would be the same false all-clear.
|
|
24272
|
+
(leftOut === null ? "Within this brain" : "Within the rest of this brain") + (facts.depth === "exact" ? ", `--depth exact` searches the card only \u2014 titles, aliases, entities, summaries and scope \u2014 try `--depth standard` to search note bodies too, or fewer words" : ", titles, aliases, summaries and note bodies are all searched \u2014 try fewer or broader words") + // Only when the reader actually passed one. Telling somebody to drop an
|
|
24273
|
+
// argument they never supplied cost one tester a whole command to `--help`
|
|
24274
|
+
// to find out it was not their bug.
|
|
24275
|
+
(facts.scoped ? ", or drop --scope." : ".")
|
|
24276
|
+
);
|
|
24277
|
+
const hint = (() => {
|
|
24278
|
+
if (nothing !== null) return nothingToSearchHint(nothing, facts.chosenWith);
|
|
24279
|
+
const next = (() => {
|
|
24280
|
+
if (otherScript !== null) return [otherScript.headline, ...otherScript.lines].join(" ");
|
|
24281
|
+
if (otherBrains !== null) {
|
|
24282
|
+
const runnable = otherBrains.commands.map((cmd) => `\`${cmd}\``).join(", ");
|
|
24283
|
+
return `${otherBrains.headline} To search ${otherBrains.commands.length === 1 ? "the other one" : "the others"}: ${runnable}. ${otherBrains.safe}`;
|
|
24284
|
+
}
|
|
24285
|
+
if (facts.semanticHint !== void 0) {
|
|
24286
|
+
return `Meaning was not searched \u2014 words only: ${facts.semanticHint}`;
|
|
24287
|
+
}
|
|
24288
|
+
if (facts.serverHint !== void 0) return facts.serverHint;
|
|
24289
|
+
return within;
|
|
24290
|
+
})();
|
|
24291
|
+
return leftOut === null ? next : `${noticeText(leftOut)} ${next}`;
|
|
24292
|
+
})();
|
|
24293
|
+
return {
|
|
24294
|
+
hint,
|
|
24295
|
+
leftOut,
|
|
24296
|
+
otherScript,
|
|
24297
|
+
otherBrains,
|
|
24298
|
+
// Nothing was searched, so no wording reaches anything.
|
|
24299
|
+
withinThisBrain: nothing?.kind === "all_left_out" ? null : within
|
|
24300
|
+
};
|
|
24301
|
+
}
|
|
24302
|
+
function noticeText(notice2) {
|
|
24303
|
+
return [notice2.headline, ...notice2.lines].join(" ");
|
|
24304
|
+
}
|
|
24305
|
+
function otherScriptNotice(script) {
|
|
24306
|
+
return {
|
|
24307
|
+
headline: `This brain's notes are mostly written in ${script}, and this query is not.`,
|
|
24308
|
+
lines: [
|
|
24309
|
+
"Search matches text, not meaning, so no rewording in another language will reach them.",
|
|
24310
|
+
`Ask again in ${script}, or run \`memgineering resurface\` to see what is here.`
|
|
24311
|
+
]
|
|
24312
|
+
};
|
|
24313
|
+
}
|
|
24314
|
+
function unsearchedNotice(query, unsearched) {
|
|
24315
|
+
const total = unsearched.others.length + 1;
|
|
24316
|
+
const hosted = unsearched.flag === "--brain";
|
|
24317
|
+
return {
|
|
24318
|
+
headline: `Only \`${oneLine(unsearched.searched, WIDTH.title)}\` was searched \u2014 ${hosted ? `this account has ${total} brains` : `${total} brains are linked on this machine`}.`,
|
|
24319
|
+
commands: unsearched.others.map(
|
|
24320
|
+
(other) => `memgineering recall ${JSON.stringify(query)} ${unsearched.flag} ${JSON.stringify(other)}`
|
|
24321
|
+
),
|
|
24322
|
+
safe: `Safe to run \u2014 reading another brain does not change which one this ${hosted ? "machine" : "folder"} points at.`
|
|
24323
|
+
};
|
|
24324
|
+
}
|
|
24325
|
+
function nothingToSearchHint(nothing, chosenWith) {
|
|
24326
|
+
return nothing.kind === "no_notes" ? emptyBrainHint(chosenWith) : noticeText(leftOutNotice(nothing.leftOut, chosenWith, "all"));
|
|
24327
|
+
}
|
|
24328
|
+
function folderFlags(chosenWith) {
|
|
24329
|
+
return (chosenWith.local === true ? " --local" : "") + (chosenWith.vault === void 0 ? "" : ` --vault ${JSON.stringify(chosenWith.vault)}`);
|
|
24330
|
+
}
|
|
24331
|
+
function emptyBrainHint(chosenWith) {
|
|
24332
|
+
if (chosenWith.brain !== void 0) {
|
|
24333
|
+
return `This brain has no notes yet. \`memgineering remember\` writes to the brain this machine points at, not to this one \u2014 \`memgineering use --brain ${JSON.stringify(chosenWith.brain)}\` points it here first, if this is where the user wants the note.`;
|
|
24334
|
+
}
|
|
24335
|
+
return `This brain has no notes yet; \`memgineering remember "<what to keep>" --reason "<why>"${folderFlags(chosenWith)}\` writes the first one.`;
|
|
24336
|
+
}
|
|
24337
|
+
function leftOutNotice(leftOut, chosenWith, share) {
|
|
24338
|
+
const notes = (n) => `${n} note${n === 1 ? "" : "s"}`;
|
|
24339
|
+
const parts = [
|
|
24340
|
+
leftOut.byRule > 0 ? `${notes(leftOut.byRule)} excluded by your rules` : null,
|
|
24341
|
+
leftOut.listed > 0 ? `${notes(leftOut.listed)} listed in .memgdeny` : null,
|
|
24342
|
+
leftOut.byName > 0 ? `${notes(leftOut.byName)} held back because the name looks private` : null,
|
|
24343
|
+
leftOut.byContent > 0 ? `${notes(leftOut.byContent)} refused because the content looks like a credential` : null,
|
|
24344
|
+
leftOut.symlinked > 0 ? `${notes(leftOut.symlinked)} behind a symlink, which is never followed` : null,
|
|
24345
|
+
leftOut.otherSymlinks > 0 ? `${leftOut.otherSymlinks} other symlink${leftOut.otherSymlinks === 1 ? "" : "s"}, never followed, so a folder behind one was not looked inside` : null,
|
|
24346
|
+
leftOut.folders > 0 ? `${leftOut.folders} excluded folder${leftOut.folders === 1 ? "" : "s"}, not looked inside` : null
|
|
24347
|
+
].filter((part) => part !== null);
|
|
24348
|
+
return {
|
|
24349
|
+
headline: `${share === "all" ? "Nothing in this brain could be searched" : "Not every note in this brain was searched"}: ${parts.join(", ")}.`,
|
|
24350
|
+
lines: [`\`memgineering reindex${folderFlags(chosenWith)}\` names each one and why.`]
|
|
24351
|
+
};
|
|
24352
|
+
}
|
|
24353
|
+
function notesIn(leftOut) {
|
|
24354
|
+
return leftOut.byRule + leftOut.listed + leftOut.byName + leftOut.byContent + leftOut.symlinked;
|
|
24355
|
+
}
|
|
24356
|
+
function leftOutOf(held, denied) {
|
|
24357
|
+
const refused = (reason) => denied.filter((d) => d.reason === reason).length;
|
|
24358
|
+
const leftOut = {
|
|
24359
|
+
byRule: held.byRule.filter((path) => !isExcluded(path, false)).length + refused("excluded"),
|
|
24360
|
+
listed: held.explicitly.length + refused("explicit"),
|
|
24361
|
+
byName: held.byName.length + refused("name"),
|
|
24362
|
+
byContent: refused("content"),
|
|
24363
|
+
symlinked: held.symlinked.length,
|
|
24364
|
+
otherSymlinks: held.otherSymlinks.filter((path) => !isExcluded(path, false)).length,
|
|
24365
|
+
folders: held.folders.filter((dir) => !isExcluded(dir, true)).length
|
|
24366
|
+
};
|
|
24367
|
+
return Object.values(leftOut).some((n) => n > 0) ? leftOut : null;
|
|
24368
|
+
}
|
|
23260
24369
|
function unreachableScript(query, entries) {
|
|
23261
24370
|
const queryScripts = scriptsIn(query);
|
|
23262
24371
|
if (queryScripts.size === 0) return null;
|
|
@@ -23322,13 +24431,13 @@ init_setup();
|
|
|
23322
24431
|
init_ui();
|
|
23323
24432
|
init_deadline();
|
|
23324
24433
|
init_src();
|
|
23325
|
-
import { Command as
|
|
24434
|
+
import { Command as Command40 } from "commander";
|
|
23326
24435
|
init_index_build();
|
|
23327
24436
|
init_cloud_brain();
|
|
23328
24437
|
init_vault();
|
|
23329
24438
|
init_ui();
|
|
23330
24439
|
function resurfaceCommand() {
|
|
23331
|
-
return new
|
|
24440
|
+
return new Command40("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
23332
24441
|
"--brain <name>",
|
|
23333
24442
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
23334
24443
|
).option(
|
|
@@ -23555,11 +24664,11 @@ rank by project yet, so that is not one of the signals above.`
|
|
|
23555
24664
|
}
|
|
23556
24665
|
|
|
23557
24666
|
// src/commands/rules.ts
|
|
23558
|
-
import { Command as
|
|
24667
|
+
import { Command as Command41 } from "commander";
|
|
23559
24668
|
init_cloud_brain();
|
|
23560
24669
|
init_ui();
|
|
23561
24670
|
function rulesCommand() {
|
|
23562
|
-
return new
|
|
24671
|
+
return new Command41("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
23563
24672
|
"--brain <name>",
|
|
23564
24673
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
23565
24674
|
).action(async (opts) => {
|
|
@@ -23726,24 +24835,24 @@ init_next_step();
|
|
|
23726
24835
|
init_provenance();
|
|
23727
24836
|
import { spawn as spawn4 } from "child_process";
|
|
23728
24837
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23729
|
-
import { mkdir as
|
|
23730
|
-
import { join as
|
|
23731
|
-
import { Command as
|
|
24838
|
+
import { mkdir as mkdir20, open, readFile as readFile30, rename as rename5, rm as rm8, stat as stat10, unlink as unlink7, writeFile as writeFile16 } from "fs/promises";
|
|
24839
|
+
import { join as join32 } from "path";
|
|
24840
|
+
import { Command as Command42 } from "commander";
|
|
23732
24841
|
|
|
23733
24842
|
// src/lib/update-check.ts
|
|
23734
24843
|
init_atomic();
|
|
23735
24844
|
init_brand();
|
|
23736
24845
|
import { execFile as execFile2 } from "child_process";
|
|
23737
|
-
import { mkdir as
|
|
23738
|
-
import { join as
|
|
24846
|
+
import { mkdir as mkdir19, readFile as readFile29, writeFile as writeFile15 } from "fs/promises";
|
|
24847
|
+
import { join as join31 } from "path";
|
|
23739
24848
|
import { promisify as promisify2 } from "util";
|
|
23740
24849
|
import semver4 from "semver";
|
|
23741
|
-
import { z as
|
|
24850
|
+
import { z as z34 } from "zod";
|
|
23742
24851
|
var run2 = promisify2(execFile2);
|
|
23743
|
-
var PolicySchema =
|
|
23744
|
-
latest:
|
|
23745
|
-
minimum_supported:
|
|
23746
|
-
notice:
|
|
24852
|
+
var PolicySchema = z34.object({
|
|
24853
|
+
latest: z34.string().optional(),
|
|
24854
|
+
minimum_supported: z34.string().optional(),
|
|
24855
|
+
notice: z34.string().optional()
|
|
23747
24856
|
});
|
|
23748
24857
|
var POLICY_URL = "https://memgineering.com/version-policy.json";
|
|
23749
24858
|
var NOTICE_MAX = 300;
|
|
@@ -23808,19 +24917,19 @@ function shouldCheck(lastCheck, now) {
|
|
|
23808
24917
|
if (!Number.isFinite(last)) return true;
|
|
23809
24918
|
return now.getTime() - last > 864e5;
|
|
23810
24919
|
}
|
|
23811
|
-
var UpdateStateSchema =
|
|
23812
|
-
at:
|
|
23813
|
-
ok:
|
|
23814
|
-
from:
|
|
23815
|
-
to:
|
|
23816
|
-
message:
|
|
24920
|
+
var UpdateStateSchema = z34.object({
|
|
24921
|
+
at: z34.string(),
|
|
24922
|
+
ok: z34.boolean(),
|
|
24923
|
+
from: z34.string(),
|
|
24924
|
+
to: z34.string().nullable(),
|
|
24925
|
+
message: z34.string().nullable()
|
|
23817
24926
|
});
|
|
23818
24927
|
function statePath() {
|
|
23819
|
-
return
|
|
24928
|
+
return join31(brandHome(), "update-state.json");
|
|
23820
24929
|
}
|
|
23821
24930
|
async function readUpdateState() {
|
|
23822
24931
|
try {
|
|
23823
|
-
const parsed = UpdateStateSchema.safeParse(JSON.parse(await
|
|
24932
|
+
const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile29(statePath(), "utf8")));
|
|
23824
24933
|
return parsed.success ? parsed.data : null;
|
|
23825
24934
|
} catch {
|
|
23826
24935
|
return null;
|
|
@@ -23828,7 +24937,7 @@ async function readUpdateState() {
|
|
|
23828
24937
|
}
|
|
23829
24938
|
async function writeUpdateState(state) {
|
|
23830
24939
|
try {
|
|
23831
|
-
await
|
|
24940
|
+
await mkdir19(brandHome(), { recursive: true });
|
|
23832
24941
|
await writeThenRename(statePath(), `${JSON.stringify(state, null, 2)}
|
|
23833
24942
|
`);
|
|
23834
24943
|
} catch {
|
|
@@ -23884,7 +24993,7 @@ function shortMessage(err) {
|
|
|
23884
24993
|
// src/commands/update.ts
|
|
23885
24994
|
init_ui();
|
|
23886
24995
|
function updateCommand() {
|
|
23887
|
-
return new
|
|
24996
|
+
return new Command42("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
|
|
23888
24997
|
const inherited = process.env[UPDATE_CLAIM_ENV] || void 0;
|
|
23889
24998
|
try {
|
|
23890
24999
|
await runUpdate(opts, inherited);
|
|
@@ -23940,7 +25049,7 @@ async function runUpdate(opts, inherited) {
|
|
|
23940
25049
|
}
|
|
23941
25050
|
const held = await holdUpdateSlot(inherited);
|
|
23942
25051
|
if (held === null && await blockedBy() === "stuck") {
|
|
23943
|
-
const lock =
|
|
25052
|
+
const lock = join32(brandHome(), RECLAIM_LOCK);
|
|
23944
25053
|
const hint = `A previous update did not finish, and clearing what it left is blocked by ${lock}. If no memgineering update is running, delete that folder and run \`memgineering update\` again.`;
|
|
23945
25054
|
printDual({
|
|
23946
25055
|
json: { ...status, provenance: provenance.kind, updated: false, reason: "update_claim_stuck", hint },
|
|
@@ -24055,14 +25164,14 @@ function newClaimToken() {
|
|
|
24055
25164
|
}
|
|
24056
25165
|
async function claimOwner() {
|
|
24057
25166
|
try {
|
|
24058
|
-
return (await
|
|
25167
|
+
return (await readFile30(join32(brandHome(), "update-running"), "utf8")).trim() || null;
|
|
24059
25168
|
} catch {
|
|
24060
25169
|
return null;
|
|
24061
25170
|
}
|
|
24062
25171
|
}
|
|
24063
25172
|
async function releaseUpdateSlot(token) {
|
|
24064
25173
|
if (await claimOwner() !== token) return;
|
|
24065
|
-
await
|
|
25174
|
+
await unlink7(join32(brandHome(), "update-running")).catch(() => void 0);
|
|
24066
25175
|
}
|
|
24067
25176
|
async function holdUpdateSlot(inherited) {
|
|
24068
25177
|
if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
|
|
@@ -24074,9 +25183,9 @@ async function holdUpdateSlot(inherited) {
|
|
|
24074
25183
|
var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
|
|
24075
25184
|
var UPDATE_CLAIM_MS = 15 * 6e4;
|
|
24076
25185
|
async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
|
|
24077
|
-
const path =
|
|
25186
|
+
const path = join32(brandHome(), "update-running");
|
|
24078
25187
|
try {
|
|
24079
|
-
await
|
|
25188
|
+
await mkdir20(brandHome(), { recursive: true });
|
|
24080
25189
|
const handle = await open(path, "wx");
|
|
24081
25190
|
await handle.writeFile(`${token}
|
|
24082
25191
|
`);
|
|
@@ -24086,12 +25195,12 @@ async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
|
|
|
24086
25195
|
if (err?.code !== "EEXIST") return false;
|
|
24087
25196
|
}
|
|
24088
25197
|
try {
|
|
24089
|
-
if (Date.now() - (await
|
|
25198
|
+
if (Date.now() - (await stat10(path)).mtimeMs >= UPDATE_CLAIM_MS) {
|
|
24090
25199
|
await hooks.afterObserve?.();
|
|
24091
25200
|
await withReclaimLock(async () => {
|
|
24092
|
-
if (Date.now() - (await
|
|
25201
|
+
if (Date.now() - (await stat10(path)).mtimeMs < UPDATE_CLAIM_MS) return;
|
|
24093
25202
|
await hooks.afterRecheck?.();
|
|
24094
|
-
await
|
|
25203
|
+
await unlink7(path);
|
|
24095
25204
|
});
|
|
24096
25205
|
}
|
|
24097
25206
|
} catch {
|
|
@@ -24102,10 +25211,10 @@ var RECLAIM_LOCK = "update-running.reclaim";
|
|
|
24102
25211
|
var RECLAIM_LOCK_MAX_MS = 10 * 6e4;
|
|
24103
25212
|
async function withReclaimLock(fn) {
|
|
24104
25213
|
const home = brandHome();
|
|
24105
|
-
const lock =
|
|
24106
|
-
const staging =
|
|
24107
|
-
await
|
|
24108
|
-
await
|
|
25214
|
+
const lock = join32(home, RECLAIM_LOCK);
|
|
25215
|
+
const staging = join32(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
|
|
25216
|
+
await mkdir20(staging, { recursive: true });
|
|
25217
|
+
await writeFile16(join32(staging, "owner"), `${process.pid} ${Date.now()}
|
|
24109
25218
|
`);
|
|
24110
25219
|
try {
|
|
24111
25220
|
await rename5(staging, lock);
|
|
@@ -24123,11 +25232,11 @@ async function withReclaimLock(fn) {
|
|
|
24123
25232
|
}
|
|
24124
25233
|
async function breakAbandonedLock(lock) {
|
|
24125
25234
|
try {
|
|
24126
|
-
const seen = (await
|
|
25235
|
+
const seen = (await readFile30(join32(lock, "owner"), "utf8")).trim();
|
|
24127
25236
|
if (!lockAbandoned(seen)) return;
|
|
24128
25237
|
const aside = `${lock}.dead.${randomBytes2(4).toString("hex")}`;
|
|
24129
25238
|
await rename5(lock, aside);
|
|
24130
|
-
const moved = (await
|
|
25239
|
+
const moved = (await readFile30(join32(aside, "owner"), "utf8").catch(() => "")).trim();
|
|
24131
25240
|
if (moved !== seen) {
|
|
24132
25241
|
await rename5(aside, lock).catch(() => void 0);
|
|
24133
25242
|
return;
|
|
@@ -24151,9 +25260,9 @@ function lockAbandoned(owner, now = Date.now()) {
|
|
|
24151
25260
|
}
|
|
24152
25261
|
async function blockedBy() {
|
|
24153
25262
|
try {
|
|
24154
|
-
const old = Date.now() - (await
|
|
25263
|
+
const old = Date.now() - (await stat10(join32(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
|
|
24155
25264
|
if (old) {
|
|
24156
|
-
await
|
|
25265
|
+
await stat10(join32(brandHome(), RECLAIM_LOCK));
|
|
24157
25266
|
return "stuck";
|
|
24158
25267
|
}
|
|
24159
25268
|
} catch {
|
|
@@ -24185,16 +25294,16 @@ function reportBelowMinimum(below) {
|
|
|
24185
25294
|
|
|
24186
25295
|
// src/commands/undo.ts
|
|
24187
25296
|
init_src();
|
|
24188
|
-
import { readFile as
|
|
24189
|
-
import { join as
|
|
24190
|
-
import { Command as
|
|
25297
|
+
import { readFile as readFile31, rm as rm9 } from "fs/promises";
|
|
25298
|
+
import { join as join33 } from "path";
|
|
25299
|
+
import { Command as Command43 } from "commander";
|
|
24191
25300
|
init_atomic();
|
|
24192
25301
|
init_errors();
|
|
24193
25302
|
init_cloud_brain();
|
|
24194
25303
|
init_vault();
|
|
24195
25304
|
init_ui();
|
|
24196
25305
|
function undoCommand() {
|
|
24197
|
-
return new
|
|
25306
|
+
return new Command43("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--reason <text>", "why it is being taken back \u2014 kept in the ledger").option("--vault <path>", "which brain to act on").option("--local", "act on the brain on this machine, even when signed in to a hosted one").action(
|
|
24198
25307
|
async (opId, opts) => {
|
|
24199
25308
|
const rationale = sanitizeRationale(opts.reason);
|
|
24200
25309
|
const where = await resolveTarget({
|
|
@@ -24283,8 +25392,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
24283
25392
|
);
|
|
24284
25393
|
}
|
|
24285
25394
|
const record = await withWriteLock(brain.root, async () => {
|
|
24286
|
-
const absolute =
|
|
24287
|
-
const current = await
|
|
25395
|
+
const absolute = join33(brain.root, target.target);
|
|
25396
|
+
const current = await readFile31(absolute, "utf8").catch(() => null);
|
|
24288
25397
|
const actualHash = current === null ? null : hashContent(current);
|
|
24289
25398
|
if (actualHash !== target.hash_after) {
|
|
24290
25399
|
throw memgError(
|
|
@@ -24346,7 +25455,7 @@ function minutesSince(iso) {
|
|
|
24346
25455
|
return Math.max(0, Math.floor((Date.now() - at) / 6e4));
|
|
24347
25456
|
}
|
|
24348
25457
|
async function reverse(brain, op, current) {
|
|
24349
|
-
const absolute =
|
|
25458
|
+
const absolute = join33(brain.root, op.target);
|
|
24350
25459
|
if (op.verb === "remember") {
|
|
24351
25460
|
if (current === null) {
|
|
24352
25461
|
throw memgError(
|
|
@@ -24403,7 +25512,7 @@ async function conflictHint(vaultRoot, op) {
|
|
|
24403
25512
|
Read what it says now, then decide what should stay.`;
|
|
24404
25513
|
const snapshot = await readSnapshot(vaultRoot, op.op_id);
|
|
24405
25514
|
return snapshot === null ? base2 : `${base2}
|
|
24406
|
-
The text from before that change is still on this machine: ${
|
|
25515
|
+
The text from before that change is still on this machine: ${join33(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
24407
25516
|
}
|
|
24408
25517
|
|
|
24409
25518
|
// src/commands/brains.ts
|
|
@@ -24412,11 +25521,11 @@ init_api_client();
|
|
|
24412
25521
|
init_cloud_brain();
|
|
24413
25522
|
init_config();
|
|
24414
25523
|
init_ui();
|
|
24415
|
-
import { Command as
|
|
25524
|
+
import { Command as Command44 } from "commander";
|
|
24416
25525
|
import stringWidth from "string-width";
|
|
24417
25526
|
var NAME_WIDTH2 = 48;
|
|
24418
25527
|
function brainsCommand() {
|
|
24419
|
-
return new
|
|
25528
|
+
return new Command44("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
|
|
24420
25529
|
"after",
|
|
24421
25530
|
"\nReads only. `memgineering use --brain <name>` points this machine at one of\nthem. `memgineering create <name>` makes a new empty one, and\n`push --brain <name>` makes one from a folder on this machine."
|
|
24422
25531
|
).action(async () => {
|
|
@@ -24496,7 +25605,7 @@ init_credentials();
|
|
|
24496
25605
|
init_cloud_brain();
|
|
24497
25606
|
init_config();
|
|
24498
25607
|
import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve16 } from "path";
|
|
24499
|
-
import { Command as
|
|
25608
|
+
import { Command as Command45 } from "commander";
|
|
24500
25609
|
init_vault();
|
|
24501
25610
|
init_ui();
|
|
24502
25611
|
function lostDefaultLines(missing) {
|
|
@@ -24515,7 +25624,7 @@ var VIA_MEANS = {
|
|
|
24515
25624
|
default: "because it is your default brain (`memgineering use --default <path>` changes it)"
|
|
24516
25625
|
};
|
|
24517
25626
|
function useCommand() {
|
|
24518
|
-
return new
|
|
25627
|
+
return new Command45("use").description(
|
|
24519
25628
|
"say which brain answers here \u2014 `--brain <name>` for the whole machine, or a path to bind this folder"
|
|
24520
25629
|
).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(
|
|
24521
25630
|
"--default",
|
|
@@ -24823,9 +25932,9 @@ async function pointAtHosted(named, opts, path) {
|
|
|
24823
25932
|
// src/commands/whoami.ts
|
|
24824
25933
|
init_api_client();
|
|
24825
25934
|
init_ui();
|
|
24826
|
-
import { Command as
|
|
25935
|
+
import { Command as Command46 } from "commander";
|
|
24827
25936
|
function whoamiCommand() {
|
|
24828
|
-
return new
|
|
25937
|
+
return new Command46("whoami").description("show which account this machine is signed in as").action(async () => {
|
|
24829
25938
|
const base2 = apiUrl();
|
|
24830
25939
|
const credentials = await requireCredentials(base2);
|
|
24831
25940
|
const res = await apiRequest({
|
|
@@ -24900,7 +26009,7 @@ var NO_FLAT_ALIAS = new Set(HOMONYM_VERBS);
|
|
|
24900
26009
|
// src/program.ts
|
|
24901
26010
|
init_version();
|
|
24902
26011
|
function buildProgram() {
|
|
24903
|
-
const program = new
|
|
26012
|
+
const program = new Command47("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) => {
|
|
24904
26013
|
const opts = thisCommand.optsWithGlobals();
|
|
24905
26014
|
if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
|
|
24906
26015
|
await announceNextStep(commandPath(actionCommand), "verb");
|
|
@@ -24929,7 +26038,7 @@ function buildProgram() {
|
|
|
24929
26038
|
diffCommand,
|
|
24930
26039
|
listCommand
|
|
24931
26040
|
];
|
|
24932
|
-
const registry = new
|
|
26041
|
+
const registry = new Command47("registry").description(
|
|
24933
26042
|
"the public layer: find, read, adapt, publish, install and keep packages current"
|
|
24934
26043
|
);
|
|
24935
26044
|
for (const factory of publicLayer) registry.addCommand(factory());
|
|
@@ -24967,6 +26076,7 @@ function buildProgram() {
|
|
|
24967
26076
|
program.addCommand(setupCommand());
|
|
24968
26077
|
program.addCommand(loginCommand());
|
|
24969
26078
|
program.addCommand(logoutCommand());
|
|
26079
|
+
program.addCommand(deleteAccountCommand());
|
|
24970
26080
|
program.addCommand(whoamiCommand());
|
|
24971
26081
|
program.addCommand(updateCommand());
|
|
24972
26082
|
makeParseErrorsActionable(program);
|
|
@@ -24986,6 +26096,7 @@ var WITHOUT_NEXT_STEP = /* @__PURE__ */ new Set([
|
|
|
24986
26096
|
"setup",
|
|
24987
26097
|
"login",
|
|
24988
26098
|
"logout",
|
|
26099
|
+
"delete-account",
|
|
24989
26100
|
"whoami",
|
|
24990
26101
|
"update",
|
|
24991
26102
|
"guard",
|