memgineering 0.26.0 → 0.27.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 +138 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-recall/SKILL.md +64 -10
- package/assets/memgineering-setup/SKILL.md +15 -9
- package/dist/index.js +1010 -590
- 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,8 +6865,8 @@ var init_cloud_pointer = __esm({
|
|
|
6739
6865
|
});
|
|
6740
6866
|
|
|
6741
6867
|
// src/commands/link.ts
|
|
6742
|
-
import { readFile as
|
|
6743
|
-
import { join as
|
|
6868
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
6869
|
+
import { join as join19 } from "path";
|
|
6744
6870
|
import { Command as Command13 } from "commander";
|
|
6745
6871
|
import prompts2 from "prompts";
|
|
6746
6872
|
function sameBasename(all, entry) {
|
|
@@ -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 readFile19(join19(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(
|
|
@@ -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 readFile25 } from "fs/promises";
|
|
7757
|
+
import { join as join25 } 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(join25(brandHome(), "hook-files.lock"), fn, waitMs);
|
|
7733
7789
|
}
|
|
7734
7790
|
async function readSettings(path) {
|
|
7735
|
-
const raw = await
|
|
7791
|
+
const raw = await readFile25(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
|
|
|
@@ -8128,16 +8181,16 @@ var init_setup_signin = __esm({
|
|
|
8128
8181
|
});
|
|
8129
8182
|
|
|
8130
8183
|
// src/lib/next-step.ts
|
|
8131
|
-
import { mkdir as mkdir16, readFile as
|
|
8132
|
-
import { join as
|
|
8184
|
+
import { mkdir as mkdir16, readFile as readFile26, writeFile as writeFile14 } from "fs/promises";
|
|
8185
|
+
import { join as join26 } 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 join26(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
|
|
8138
8191
|
}
|
|
8139
8192
|
async function connectedNoticeShown(at) {
|
|
8140
|
-
return
|
|
8193
|
+
return readFile26(connectedMarker(at), "utf8").then(
|
|
8141
8194
|
() => true,
|
|
8142
8195
|
() => false
|
|
8143
8196
|
);
|
|
@@ -8145,21 +8198,21 @@ async function connectedNoticeShown(at) {
|
|
|
8145
8198
|
async function markConnectedNoticeShown(at) {
|
|
8146
8199
|
try {
|
|
8147
8200
|
await mkdir16(brandHome(), { recursive: true });
|
|
8148
|
-
await
|
|
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 = join26(brandHome(), "setup-notice-shown");
|
|
8155
8208
|
try {
|
|
8156
|
-
await
|
|
8209
|
+
await readFile26(marker, "utf8");
|
|
8157
8210
|
return false;
|
|
8158
8211
|
} catch {
|
|
8159
8212
|
}
|
|
8160
8213
|
try {
|
|
8161
8214
|
await mkdir16(brandHome(), { recursive: true });
|
|
8162
|
-
await
|
|
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 join27, 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" ? join27(prefix, "node_modules") : join27(prefix, "lib", "node_modules"),
|
|
8467
8520
|
install: "npm i -g memgineering@latest"
|
|
8468
8521
|
},
|
|
8469
8522
|
{
|
|
@@ -8502,7 +8555,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
8502
8555
|
import { realpathSync as realpathSync2 } from "fs";
|
|
8503
8556
|
import { readdir as readdir9, stat as stat8 } 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 join28, 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) => join28(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(join28(dir, entry.name)).catch(() => []);
|
|
8533
8586
|
count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
|
|
8534
8587
|
}
|
|
8535
8588
|
if (count > 999) break;
|
|
@@ -8921,8 +8974,8 @@ 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 mkdir17, readFile as
|
|
8925
|
-
import { dirname as dirname11, join as
|
|
8977
|
+
import { access as access2, mkdir as mkdir17, readFile as readFile27 } from "fs/promises";
|
|
8978
|
+
import { dirname as dirname11, join as join29 } from "path";
|
|
8926
8979
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8927
8980
|
import { Command as Command38 } from "commander";
|
|
8928
8981
|
import prompts5 from "prompts";
|
|
@@ -9289,7 +9342,7 @@ async function applySetup(choice, dryRun) {
|
|
|
9289
9342
|
}
|
|
9290
9343
|
async function write2() {
|
|
9291
9344
|
for (const tool of choice.tools) {
|
|
9292
|
-
const hubPath =
|
|
9345
|
+
const hubPath = join29(agentHome(), tool.hubFile);
|
|
9293
9346
|
const written = await injectBlock(
|
|
9294
9347
|
hubPath,
|
|
9295
9348
|
hub + tailFor(tool, choice.hook),
|
|
@@ -9304,8 +9357,8 @@ async function applySetup(choice, dryRun) {
|
|
|
9304
9357
|
});
|
|
9305
9358
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
9306
9359
|
for (const skill of BRAND.subSkills) {
|
|
9307
|
-
const content = await loadAsset(
|
|
9308
|
-
const path =
|
|
9360
|
+
const content = await loadAsset(join29(skill, "SKILL.md"));
|
|
9361
|
+
const path = join29(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
9309
9362
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
9310
9363
|
results.push({
|
|
9311
9364
|
tool: tool.label,
|
|
@@ -9395,7 +9448,7 @@ async function hooksArmed(tools) {
|
|
|
9395
9448
|
const capable = tools.filter((t) => t.hooks && t.hooksFile);
|
|
9396
9449
|
if (capable.length === 0) return void 0;
|
|
9397
9450
|
for (const tool of capable) {
|
|
9398
|
-
const raw = await
|
|
9451
|
+
const raw = await readFile27(join29(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
|
|
9399
9452
|
() => ""
|
|
9400
9453
|
);
|
|
9401
9454
|
if (hooksFor([tool]).some((spec) => raw.includes(spec.signature))) return "on";
|
|
@@ -9435,7 +9488,7 @@ async function detectTools() {
|
|
|
9435
9488
|
const home = agentHome();
|
|
9436
9489
|
const found = [];
|
|
9437
9490
|
for (const tool of TOOLS) {
|
|
9438
|
-
if (await exists2(
|
|
9491
|
+
if (await exists2(join29(home, tool.dir))) found.push(tool);
|
|
9439
9492
|
}
|
|
9440
9493
|
return found;
|
|
9441
9494
|
}
|
|
@@ -9535,7 +9588,7 @@ async function injectBlock(filePath, content, dryRun, weWroteThis) {
|
|
|
9535
9588
|
const block = `${BRAND.hubMarkerStart}
|
|
9536
9589
|
${content.trimEnd()}
|
|
9537
9590
|
${BRAND.hubMarkerEnd}`;
|
|
9538
|
-
const existing = await
|
|
9591
|
+
const existing = await readFile27(filePath, "utf8").catch(() => null);
|
|
9539
9592
|
if (existing === null) {
|
|
9540
9593
|
if (!dryRun) {
|
|
9541
9594
|
await mkdir17(dirname11(filePath), { recursive: true });
|
|
@@ -9578,7 +9631,7 @@ function looksLikeOurGuide(text) {
|
|
|
9578
9631
|
return text.includes("memgineering recall") && text.includes("memgineering remember");
|
|
9579
9632
|
}
|
|
9580
9633
|
async function writeIfDifferent(filePath, content, dryRun) {
|
|
9581
|
-
const existing = await
|
|
9634
|
+
const existing = await readFile27(filePath, "utf8").catch(() => null);
|
|
9582
9635
|
const after = fingerprint(content);
|
|
9583
9636
|
if (existing === content) return { outcome: "unchanged", before: existing, after };
|
|
9584
9637
|
if (!dryRun) {
|
|
@@ -9599,7 +9652,7 @@ async function refreshOwnHookCommands() {
|
|
|
9599
9652
|
const changed = [];
|
|
9600
9653
|
const skipped = [];
|
|
9601
9654
|
for (const tool of TOOLS.filter((t) => t.hooks && t.hooksFile && DEFAULT_HOOK_TOOLS.includes(t.id))) {
|
|
9602
|
-
const path =
|
|
9655
|
+
const path = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9603
9656
|
const outcome = await refreshHookFile(path, moves);
|
|
9604
9657
|
if (outcome === "changed") changed.push(path);
|
|
9605
9658
|
else if (outcome === "symlink" || outcome === "refused") skipped.push({ path, why: outcome });
|
|
@@ -9626,8 +9679,8 @@ function hooksFor(tools) {
|
|
|
9626
9679
|
);
|
|
9627
9680
|
}
|
|
9628
9681
|
async function installHook(spec, tool, dryRun) {
|
|
9629
|
-
const settingsPath =
|
|
9630
|
-
const raw = await
|
|
9682
|
+
const settingsPath = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9683
|
+
const raw = await readFile27(settingsPath, "utf8").catch(() => null);
|
|
9631
9684
|
let settings = {};
|
|
9632
9685
|
if (raw !== null) {
|
|
9633
9686
|
try {
|
|
@@ -9676,8 +9729,8 @@ async function installHook(spec, tool, dryRun) {
|
|
|
9676
9729
|
return raw === null ? "injected" : "updated";
|
|
9677
9730
|
}
|
|
9678
9731
|
async function removeHook(spec, tool, dryRun) {
|
|
9679
|
-
const settingsPath =
|
|
9680
|
-
const raw = await
|
|
9732
|
+
const settingsPath = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9733
|
+
const raw = await readFile27(settingsPath, "utf8").catch(() => null);
|
|
9681
9734
|
if (raw === null) return "unchanged";
|
|
9682
9735
|
let parsed;
|
|
9683
9736
|
try {
|
|
@@ -9701,7 +9754,7 @@ async function removeHook(spec, tool, dryRun) {
|
|
|
9701
9754
|
return "removed";
|
|
9702
9755
|
}
|
|
9703
9756
|
async function loadAsset(relPath) {
|
|
9704
|
-
const raw = await
|
|
9757
|
+
const raw = await readFile27(join29(await assetRoot(), relPath), "utf8");
|
|
9705
9758
|
if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
|
|
9706
9759
|
const end = raw.indexOf("\n---", 4);
|
|
9707
9760
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -9710,12 +9763,12 @@ async function loadAsset(relPath) {
|
|
|
9710
9763
|
}
|
|
9711
9764
|
async function assetRoot() {
|
|
9712
9765
|
const here = dirname11(fileURLToPath3(import.meta.url));
|
|
9713
|
-
const bundled =
|
|
9714
|
-
if (await exists2(
|
|
9766
|
+
const bundled = join29(here, "..", "assets");
|
|
9767
|
+
if (await exists2(join29(bundled, BRAND.hubFileName))) return bundled;
|
|
9715
9768
|
let dir = here;
|
|
9716
9769
|
for (; ; ) {
|
|
9717
|
-
const candidate =
|
|
9718
|
-
if (await exists2(
|
|
9770
|
+
const candidate = join29(dir, "packages", "skills");
|
|
9771
|
+
if (await exists2(join29(candidate, BRAND.hubFileName))) return candidate;
|
|
9719
9772
|
const parent = dirname11(dir);
|
|
9720
9773
|
if (parent === dir) break;
|
|
9721
9774
|
dir = parent;
|
|
@@ -10234,6 +10287,21 @@ var ReportReasonSchema = z10.enum(["spam", "harmful", "incorrect", "copyright",
|
|
|
10234
10287
|
|
|
10235
10288
|
// ../../packages/contracts/src/errors.ts
|
|
10236
10289
|
import { z as z11 } from "zod";
|
|
10290
|
+
|
|
10291
|
+
// ../../packages/contracts/src/refresh-race.ts
|
|
10292
|
+
var REFRESH_RACE_FLAG = "refresh_rotated_by_another_request";
|
|
10293
|
+
function isRefreshRace(body) {
|
|
10294
|
+
if (typeof body !== "object" || body === null) return false;
|
|
10295
|
+
const error = body.error;
|
|
10296
|
+
if (typeof error !== "object" || error === null) return false;
|
|
10297
|
+
const { code, flags } = error;
|
|
10298
|
+
if (code !== "not_authenticated" || !Array.isArray(flags)) return false;
|
|
10299
|
+
return flags.some(
|
|
10300
|
+
(flag2) => typeof flag2 === "object" && flag2 !== null && flag2.code === REFRESH_RACE_FLAG
|
|
10301
|
+
);
|
|
10302
|
+
}
|
|
10303
|
+
|
|
10304
|
+
// ../../packages/contracts/src/errors.ts
|
|
10237
10305
|
var ErrorCodeSchema = z11.enum([
|
|
10238
10306
|
"generic_failure",
|
|
10239
10307
|
// 1
|
|
@@ -11183,16 +11251,33 @@ var PublishPackageRequestSchema = z19.object({
|
|
|
11183
11251
|
*/
|
|
11184
11252
|
content: z19.string().min(50).max(1e5),
|
|
11185
11253
|
/**
|
|
11186
|
-
*
|
|
11187
|
-
*
|
|
11254
|
+
* Optional, and refused when it disagrees. core-api stores the title,
|
|
11255
|
+
* description and type it reads from the frontmatter of `content`, which must
|
|
11256
|
+
* carry all three (the title may come from `name:`). A value here that
|
|
11257
|
+
* differs from the frontmatter's, compared trimmed, is refused as
|
|
11258
|
+
* `usage_error` (HTTP 400) with a hint naming the frontmatter key; absent
|
|
11259
|
+
* means no claim. A `type` the registry cannot publish at all (`scoped-rule`,
|
|
11260
|
+
* `mdc-rule`, `plugin`) names no key to write: its hint says the type cannot
|
|
11261
|
+
* be published yet and to drop `--type`. `kordis push` ≤0.15.2 sends
|
|
11262
|
+
* `--type` here, and writes `type: skill` into the frontmatter of a SKILL.md
|
|
11263
|
+
* or *.skill.md file, a folder or a .skill bundle that names no type — so
|
|
11264
|
+
* `--type hook` on one is refused, where until 2026-10 it published a skill.
|
|
11265
|
+
* Later versions send no `type`: they write `--type` into the frontmatter.
|
|
11188
11266
|
*/
|
|
11189
11267
|
title: z19.string().min(1).max(200).optional(),
|
|
11190
11268
|
description: z19.string().min(1).max(2048).optional(),
|
|
11191
11269
|
type: HarnessTypeSchema.optional(),
|
|
11270
|
+
/**
|
|
11271
|
+
* A new package is published under this slug instead of the one derived from
|
|
11272
|
+
* the frontmatter title. An update keeps the slug of the package it updates:
|
|
11273
|
+
* a slug here that differs from the target's is refused (`usage_error`).
|
|
11274
|
+
*/
|
|
11192
11275
|
slug: SlugSchema.optional(),
|
|
11193
11276
|
/**
|
|
11194
|
-
* When updating, the target full_slug
|
|
11195
|
-
*
|
|
11277
|
+
* When updating, the target full_slug; its presence is what makes a publish
|
|
11278
|
+
* an update (`PUT /api/packages/:u/:s` sets it from the path). Omitted for a
|
|
11279
|
+
* new package, whose slug is `slug` or else derived from the frontmatter
|
|
11280
|
+
* title — never derived on an update.
|
|
11196
11281
|
*/
|
|
11197
11282
|
target_full_slug: FullSlugSchema.optional(),
|
|
11198
11283
|
/**
|
|
@@ -11200,6 +11285,12 @@ var PublishPackageRequestSchema = z19.object({
|
|
|
11200
11285
|
*/
|
|
11201
11286
|
bump: z19.enum(["patch", "minor", "major"]).optional(),
|
|
11202
11287
|
changelog: z19.string().max(2e3).optional(),
|
|
11288
|
+
/**
|
|
11289
|
+
* Refused when non-empty (`usage_error`): a package's dependencies are read
|
|
11290
|
+
* from its frontmatter's `depends:` and `recommends:` lists only. Kept in the
|
|
11291
|
+
* schema so the server sees them to refuse them — this object is non-strict,
|
|
11292
|
+
* and an unlisted key would be stripped without a word.
|
|
11293
|
+
*/
|
|
11203
11294
|
depends: z19.array(z19.string()).optional(),
|
|
11204
11295
|
recommends: z19.array(z19.string()).optional(),
|
|
11205
11296
|
/**
|
|
@@ -11673,6 +11764,120 @@ var AdminTelemetrySummaryResponseSchema = z22.object({
|
|
|
11673
11764
|
daily: z22.array(TelemetryDailyBucketSchema)
|
|
11674
11765
|
});
|
|
11675
11766
|
|
|
11767
|
+
// ../../packages/contracts/src/review-patterns.ts
|
|
11768
|
+
var STAGE_1_REJECT_PATTERNS = [
|
|
11769
|
+
{
|
|
11770
|
+
code: "curl_pipe_shell",
|
|
11771
|
+
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,
|
|
11772
|
+
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."
|
|
11773
|
+
},
|
|
11774
|
+
{
|
|
11775
|
+
code: "wget_pipe_shell",
|
|
11776
|
+
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,
|
|
11777
|
+
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."
|
|
11778
|
+
},
|
|
11779
|
+
{
|
|
11780
|
+
code: "shell_process_substitution",
|
|
11781
|
+
pattern: /(?:bash|sh|zsh)\s*<\(\s*(?:curl|wget)\b/i,
|
|
11782
|
+
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."
|
|
11783
|
+
},
|
|
11784
|
+
{
|
|
11785
|
+
code: "eval_atob",
|
|
11786
|
+
pattern: /\beval\s*\(\s*atob\b/i,
|
|
11787
|
+
hint: "Encoded eval (`eval(atob(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
11788
|
+
},
|
|
11789
|
+
{
|
|
11790
|
+
code: "eval_buffer_from",
|
|
11791
|
+
pattern: /\beval\s*\(\s*Buffer\.from\b/i,
|
|
11792
|
+
hint: "Encoded eval (`eval(Buffer.from(...))`) is a hallmark of obfuscated payloads. Move the logic into plain code the reader can audit."
|
|
11793
|
+
},
|
|
11794
|
+
{
|
|
11795
|
+
code: "aws_access_key",
|
|
11796
|
+
pattern: /\b(?:AKIA|ASIA|AGPA)[0-9A-Z]{16}\b/,
|
|
11797
|
+
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."
|
|
11798
|
+
},
|
|
11799
|
+
{
|
|
11800
|
+
code: "private_key_block",
|
|
11801
|
+
pattern: /-----BEGIN\s+(?:[A-Z0-9]+\s+)?PRIVATE\s+KEY(?:\s+BLOCK)?-----/,
|
|
11802
|
+
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."
|
|
11803
|
+
},
|
|
11804
|
+
{
|
|
11805
|
+
code: "google_api_key",
|
|
11806
|
+
pattern: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
11807
|
+
hint: "Remove the Google API key from the body. Reference credentials via env var; rotate the leaked key immediately if it was real."
|
|
11808
|
+
},
|
|
11809
|
+
{
|
|
11810
|
+
code: "slack_token",
|
|
11811
|
+
pattern: /\b(?:xox[abpors]|xapp|xoxe)-[0-9a-zA-Z-]{10,}\b/,
|
|
11812
|
+
hint: "Remove the Slack token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
11813
|
+
},
|
|
11814
|
+
{
|
|
11815
|
+
code: "github_token",
|
|
11816
|
+
pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{36,}|github_pat_[A-Za-z0-9_]{60,})\b/,
|
|
11817
|
+
hint: "Remove the GitHub token from the body. Reference tokens via env var; revoke the leaked token immediately if it was real."
|
|
11818
|
+
}
|
|
11819
|
+
];
|
|
11820
|
+
var STAGE_1_5_AUP_PATTERNS = [
|
|
11821
|
+
{
|
|
11822
|
+
// Stolen-card / bank-credential trafficking storefronts. Anchored to the
|
|
11823
|
+
// trafficking jargon that only appears in carding markets, not in the
|
|
11824
|
+
// defensive "how to detect carding" literature. `dumps` is also an
|
|
11825
|
+
// ordinary developer verb — "this hook dumps vendor files", "dumps store
|
|
11826
|
+
// state", "dumps market data to CSV" all matched the shared noun list —
|
|
11827
|
+
// so after `dumps` only sale framing (`shop`, `for sale`) counts, while
|
|
11828
|
+
// `cvv` and `fullz` keep the full list. Narrowed when comments started
|
|
11829
|
+
// running this table (2026-10-14).
|
|
11830
|
+
code: "aup_stolen_financial_data_market",
|
|
11831
|
+
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,
|
|
11832
|
+
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."
|
|
11833
|
+
},
|
|
11834
|
+
{
|
|
11835
|
+
// DDoS-for-hire (booter/stresser) services sold on a subscription.
|
|
11836
|
+
code: "aup_ddos_for_hire",
|
|
11837
|
+
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,
|
|
11838
|
+
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."
|
|
11839
|
+
},
|
|
11840
|
+
{
|
|
11841
|
+
// Malware/stealer/ransomware sold or distributed as a product ("builder
|
|
11842
|
+
// for sale", "buy our stealer"). A skill that DEFENDS against these, or a
|
|
11843
|
+
// malware-analysis skill, does not use buy/sell framing.
|
|
11844
|
+
code: "aup_malware_sale",
|
|
11845
|
+
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,
|
|
11846
|
+
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."
|
|
11847
|
+
},
|
|
11848
|
+
{
|
|
11849
|
+
// Child sexual abuse material. Unlike the rest of this table this entry is
|
|
11850
|
+
// NOT anchored to solicitation: it matches the spelled-out phrases ("child
|
|
11851
|
+
// porn", "child pornography", "child sexual abuse material") wherever they
|
|
11852
|
+
// appear, child-safety and moderation education included. Only the
|
|
11853
|
+
// acronym "CSAM" on its own does not match ("how platforms detect CSAM
|
|
11854
|
+
// for NCMEC reporting" passes). Kept this broad on purpose (2026-10-14):
|
|
11855
|
+
// comments have no Stage-2 review after this table, so a miss costs more
|
|
11856
|
+
// than a false positive.
|
|
11857
|
+
code: "aup_csam",
|
|
11858
|
+
pattern: /\bchild\s+(?:porn(?:ography)?|sexual\s+abuse\s+material)\b/i,
|
|
11859
|
+
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."
|
|
11860
|
+
},
|
|
11861
|
+
{
|
|
11862
|
+
// Drug / untraceable-weapon brokering with explicit no-id / untraceable /
|
|
11863
|
+
// dark-web fulfillment language — not a bare mention of a substance or
|
|
11864
|
+
// firearm topic (e.g. harm-reduction or policy content does not use this
|
|
11865
|
+
// buy/sell + untraceable-fulfillment framing together).
|
|
11866
|
+
code: "aup_drug_weapon_brokering",
|
|
11867
|
+
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,
|
|
11868
|
+
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."
|
|
11869
|
+
},
|
|
11870
|
+
{
|
|
11871
|
+
// A directive to dox or swat a specific, named/tagged target — not a
|
|
11872
|
+
// bare mention of doxxing/swatting as a topic (e.g. "how to protect
|
|
11873
|
+
// yourself from doxxing" does not match).
|
|
11874
|
+
code: "aup_targeted_harassment_directive",
|
|
11875
|
+
pattern: /\b(?:dox|swat)\s+(?:this (?:guy|girl|person|user)|him|her|@[\w.]+)\b/i,
|
|
11876
|
+
hint: "This reads as a directive to dox or swat a specific target, which violates the Kordis AUP. Remove the targeted-harassment instruction."
|
|
11877
|
+
}
|
|
11878
|
+
];
|
|
11879
|
+
var DETERMINISTIC_REJECT_PATTERNS = [...STAGE_1_REJECT_PATTERNS, ...STAGE_1_5_AUP_PATTERNS];
|
|
11880
|
+
|
|
11676
11881
|
// ../../packages/api-client/src/errors.ts
|
|
11677
11882
|
function httpStatusToCode(status) {
|
|
11678
11883
|
if (status === 401) return "not_authenticated";
|
|
@@ -12209,7 +12414,7 @@ Wait ${err.retryAfter}s before trying again.` : "";
|
|
|
12209
12414
|
|
|
12210
12415
|
// src/commands/base-write.ts
|
|
12211
12416
|
init_errors();
|
|
12212
|
-
import { readFile as
|
|
12417
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
12213
12418
|
import { resolve as resolve4 } from "path";
|
|
12214
12419
|
import { Command as Command2 } from "commander";
|
|
12215
12420
|
|
|
@@ -12219,8 +12424,8 @@ init_brand();
|
|
|
12219
12424
|
init_errors();
|
|
12220
12425
|
init_ui();
|
|
12221
12426
|
import { createHash as createHash3 } from "crypto";
|
|
12222
|
-
import { appendFile, mkdir as mkdir6, readFile as
|
|
12223
|
-
import { join as
|
|
12427
|
+
import { appendFile, mkdir as mkdir6, readFile as readFile7, readdir as readdir3, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
12428
|
+
import { join as join9 } from "path";
|
|
12224
12429
|
import { z as z23 } from "zod";
|
|
12225
12430
|
|
|
12226
12431
|
// src/lib/ids.ts
|
|
@@ -12443,20 +12648,20 @@ function sanitizeRationale(raw) {
|
|
|
12443
12648
|
return capped2;
|
|
12444
12649
|
}
|
|
12445
12650
|
function ledgerDir(vaultRoot) {
|
|
12446
|
-
return
|
|
12651
|
+
return join9(vaultRoot, BRAND.vaultLedgerDir, "ledger");
|
|
12447
12652
|
}
|
|
12448
12653
|
function ledgerFile(vaultRoot, ts) {
|
|
12449
|
-
return
|
|
12654
|
+
return join9(ledgerDir(vaultRoot), `ops-${ts.slice(0, 7)}.jsonl`);
|
|
12450
12655
|
}
|
|
12451
12656
|
function hashContent(content) {
|
|
12452
12657
|
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
12453
12658
|
}
|
|
12454
12659
|
function stateDirFor(vaultRoot) {
|
|
12455
12660
|
const hash = createHash3("sha256").update(vaultRoot).digest("hex").slice(0, 16);
|
|
12456
|
-
return
|
|
12661
|
+
return join9(brandHome(), "state", hash);
|
|
12457
12662
|
}
|
|
12458
12663
|
function snapshotDir(vaultRoot) {
|
|
12459
|
-
return
|
|
12664
|
+
return join9(stateDirFor(vaultRoot), "undo");
|
|
12460
12665
|
}
|
|
12461
12666
|
async function appendOp(vaultRoot, op) {
|
|
12462
12667
|
const ts = (op.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12478,7 +12683,7 @@ async function appendOp(vaultRoot, op) {
|
|
|
12478
12683
|
if (op.snapshotBefore != null) {
|
|
12479
12684
|
const dir = snapshotDir(vaultRoot);
|
|
12480
12685
|
await mkdir6(dir, { recursive: true });
|
|
12481
|
-
await writeFile5(
|
|
12686
|
+
await writeFile5(join9(dir, `${record.op_id}.txt`), op.snapshotBefore, {
|
|
12482
12687
|
encoding: "utf8",
|
|
12483
12688
|
mode: 384
|
|
12484
12689
|
});
|
|
@@ -12490,7 +12695,7 @@ async function appendOp(vaultRoot, op) {
|
|
|
12490
12695
|
}
|
|
12491
12696
|
async function readSnapshot(vaultRoot, opId) {
|
|
12492
12697
|
try {
|
|
12493
|
-
return await
|
|
12698
|
+
return await readFile7(join9(snapshotDir(vaultRoot), `${opId}.txt`), "utf8");
|
|
12494
12699
|
} catch {
|
|
12495
12700
|
return null;
|
|
12496
12701
|
}
|
|
@@ -12498,14 +12703,14 @@ async function readSnapshot(vaultRoot, opId) {
|
|
|
12498
12703
|
async function readOps(vaultRoot) {
|
|
12499
12704
|
let files2;
|
|
12500
12705
|
try {
|
|
12501
|
-
files2 = (await
|
|
12706
|
+
files2 = (await readdir3(ledgerDir(vaultRoot))).filter((f) => /^ops-\d{4}-\d{2}\.jsonl$/.test(f)).sort();
|
|
12502
12707
|
} catch {
|
|
12503
12708
|
return { ops: [], skipped: 0 };
|
|
12504
12709
|
}
|
|
12505
12710
|
const ops = [];
|
|
12506
12711
|
let skipped = 0;
|
|
12507
12712
|
for (const file of files2) {
|
|
12508
|
-
const raw = await
|
|
12713
|
+
const raw = await readFile7(join9(ledgerDir(vaultRoot), file), "utf8");
|
|
12509
12714
|
for (const line of raw.split("\n")) {
|
|
12510
12715
|
if (line.trim() === "") continue;
|
|
12511
12716
|
try {
|
|
@@ -12801,7 +13006,7 @@ async function readBody(source) {
|
|
|
12801
13006
|
process.stdin.on("error", reject);
|
|
12802
13007
|
});
|
|
12803
13008
|
}
|
|
12804
|
-
return
|
|
13009
|
+
return readFile11(source, "utf8").catch(() => source);
|
|
12805
13010
|
}
|
|
12806
13011
|
|
|
12807
13012
|
// src/commands/brain-admin.ts
|
|
@@ -12814,8 +13019,8 @@ init_errors();
|
|
|
12814
13019
|
init_cloud_brain();
|
|
12815
13020
|
init_config();
|
|
12816
13021
|
init_held_back();
|
|
12817
|
-
import { readFile as
|
|
12818
|
-
import { join as
|
|
13022
|
+
import { readFile as readFile12, rm as rm5 } from "fs/promises";
|
|
13023
|
+
import { join as join12 } from "path";
|
|
12819
13024
|
import { Command as Command3 } from "commander";
|
|
12820
13025
|
init_vault();
|
|
12821
13026
|
init_ui();
|
|
@@ -13033,7 +13238,7 @@ function syncRulesCommand() {
|
|
|
13033
13238
|
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
13239
|
const { brain } = await resolveBrain(opts.vault);
|
|
13035
13240
|
const root = brain.root;
|
|
13036
|
-
const existingRaw = await
|
|
13241
|
+
const existingRaw = await readFile12(join12(root, RULES_FILE), "utf8").catch(() => void 0);
|
|
13037
13242
|
const current = await readRulesFile(root);
|
|
13038
13243
|
const [dotDeny, dotIgnore] = await Promise.all([
|
|
13039
13244
|
readLines(root, ".memgdeny"),
|
|
@@ -13061,7 +13266,7 @@ function syncRulesCommand() {
|
|
|
13061
13266
|
});
|
|
13062
13267
|
return;
|
|
13063
13268
|
}
|
|
13064
|
-
await writeThenRename(
|
|
13269
|
+
await writeThenRename(join12(root, RULES_FILE), next);
|
|
13065
13270
|
const after = await readRulesFile(root);
|
|
13066
13271
|
const lost = [
|
|
13067
13272
|
...neverRead.filter((e) => !after.raw.never_read.includes(e)),
|
|
@@ -13094,7 +13299,7 @@ function syncRulesCommand() {
|
|
|
13094
13299
|
});
|
|
13095
13300
|
}
|
|
13096
13301
|
async function readLines(root, name) {
|
|
13097
|
-
const raw = await
|
|
13302
|
+
const raw = await readFile12(join12(root, name), "utf8").catch(() => "");
|
|
13098
13303
|
return raw.split("\n").map((l) => l.replace(/\r$/, "").trim()).filter((l) => l !== "" && !l.startsWith("#"));
|
|
13099
13304
|
}
|
|
13100
13305
|
async function stopReadingHosted(path) {
|
|
@@ -13304,11 +13509,11 @@ init_index_build();
|
|
|
13304
13509
|
|
|
13305
13510
|
// src/lib/lock.ts
|
|
13306
13511
|
init_errors();
|
|
13307
|
-
import { mkdir as mkdir7, readFile as
|
|
13308
|
-
import { join as
|
|
13512
|
+
import { mkdir as mkdir7, readFile as readFile13, rm as rm6, writeFile as writeFile6 } from "fs/promises";
|
|
13513
|
+
import { join as join13 } from "path";
|
|
13309
13514
|
var STALE_MS = 6e4;
|
|
13310
13515
|
function lockPath(vaultRoot) {
|
|
13311
|
-
return
|
|
13516
|
+
return join13(stateDirFor(vaultRoot), "write.lock");
|
|
13312
13517
|
}
|
|
13313
13518
|
function holderIsGone(pid) {
|
|
13314
13519
|
try {
|
|
@@ -13351,7 +13556,7 @@ async function withFileLock(path, dir, fn) {
|
|
|
13351
13556
|
}
|
|
13352
13557
|
async function readLock(path) {
|
|
13353
13558
|
try {
|
|
13354
|
-
const parsed = JSON.parse(await
|
|
13559
|
+
const parsed = JSON.parse(await readFile13(path, "utf8"));
|
|
13355
13560
|
if (typeof parsed.pid === "number" && typeof parsed.at === "string") {
|
|
13356
13561
|
return { pid: parsed.pid, at: parsed.at };
|
|
13357
13562
|
}
|
|
@@ -13370,15 +13575,15 @@ init_errors();
|
|
|
13370
13575
|
import { Command as Command5 } from "commander";
|
|
13371
13576
|
|
|
13372
13577
|
// src/lib/events.ts
|
|
13373
|
-
import { appendFile as appendFile2, mkdir as mkdir8, readFile as
|
|
13374
|
-
import { join as
|
|
13578
|
+
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile15, readdir as readdir5 } from "fs/promises";
|
|
13579
|
+
import { join as join15 } from "path";
|
|
13375
13580
|
import { z as z24 } from "zod";
|
|
13376
13581
|
|
|
13377
13582
|
// src/lib/project.ts
|
|
13378
13583
|
init_config();
|
|
13379
13584
|
init_vault();
|
|
13380
|
-
import { readFile as
|
|
13381
|
-
import { basename, dirname as
|
|
13585
|
+
import { readFile as readFile14, stat as stat3 } from "fs/promises";
|
|
13586
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join14, resolve as resolve5 } from "path";
|
|
13382
13587
|
async function projectOf(cwd = process.cwd()) {
|
|
13383
13588
|
const here = await canonicalize(resolve5(cwd));
|
|
13384
13589
|
const repo = await findRepository(here);
|
|
@@ -13409,37 +13614,37 @@ async function boundName(key2) {
|
|
|
13409
13614
|
return cfg.projects.bindings.find((b) => resolve5(b.dir) === key2)?.name ?? null;
|
|
13410
13615
|
}
|
|
13411
13616
|
async function derivedName(repo) {
|
|
13412
|
-
const url = await originUrl(
|
|
13617
|
+
const url = await originUrl(join14(repo.commonDir, "config"));
|
|
13413
13618
|
const fromRemote = url === null ? null : ownerRepo(url);
|
|
13414
13619
|
if (fromRemote !== null) return { name: fromRemote, via: "remote" };
|
|
13415
|
-
const folder =
|
|
13620
|
+
const folder = basename2(repo.root);
|
|
13416
13621
|
return folder === "" ? { name: null, via: "none" } : { name: folder, via: "dirname" };
|
|
13417
13622
|
}
|
|
13418
13623
|
async function findRepository(from) {
|
|
13419
13624
|
let dir = from;
|
|
13420
13625
|
for (; ; ) {
|
|
13421
|
-
const found = await readGitLink(
|
|
13626
|
+
const found = await readGitLink(join14(dir, ".git"), dir);
|
|
13422
13627
|
if (found !== null) return found;
|
|
13423
|
-
const parent =
|
|
13628
|
+
const parent = dirname4(dir);
|
|
13424
13629
|
if (parent === dir) return null;
|
|
13425
13630
|
dir = parent;
|
|
13426
13631
|
}
|
|
13427
13632
|
}
|
|
13428
13633
|
async function readGitLink(dot, dir) {
|
|
13429
|
-
const info = await
|
|
13634
|
+
const info = await stat3(dot).catch(() => null);
|
|
13430
13635
|
if (info === null) return null;
|
|
13431
13636
|
if (info.isDirectory()) return { root: dir, commonDir: dot };
|
|
13432
|
-
const raw = await
|
|
13637
|
+
const raw = await readFile14(dot, "utf8").catch(() => null);
|
|
13433
13638
|
const pointed = raw === null ? null : /^gitdir:\s*(.+)$/m.exec(raw)?.[1]?.trim();
|
|
13434
13639
|
if (pointed === null || pointed === void 0 || pointed === "") return null;
|
|
13435
13640
|
const gitDir = isAbsolute2(pointed) ? resolve5(pointed) : resolve5(dir, pointed);
|
|
13436
|
-
const common = (await
|
|
13641
|
+
const common = (await readFile14(join14(gitDir, "commondir"), "utf8").catch(() => null))?.trim();
|
|
13437
13642
|
const commonDir = common === void 0 || common === "" ? gitDir : isAbsolute2(common) ? resolve5(common) : resolve5(gitDir, common);
|
|
13438
|
-
const root =
|
|
13643
|
+
const root = basename2(commonDir) === ".git" ? await canonicalize(dirname4(commonDir)) : dir;
|
|
13439
13644
|
return { root, commonDir };
|
|
13440
13645
|
}
|
|
13441
13646
|
async function originUrl(configPath2) {
|
|
13442
|
-
const text = await
|
|
13647
|
+
const text = await readFile14(configPath2, "utf8").catch(() => null);
|
|
13443
13648
|
if (text === null) return null;
|
|
13444
13649
|
let inOrigin = false;
|
|
13445
13650
|
let url = null;
|
|
@@ -13541,7 +13746,7 @@ var EventSchema = z24.object({
|
|
|
13541
13746
|
opened_id: z24.string().nullable().default(null)
|
|
13542
13747
|
});
|
|
13543
13748
|
function eventsFile(stateDir, ts) {
|
|
13544
|
-
return
|
|
13749
|
+
return join15(stateDir, `events-${ts.slice(0, 7)}.jsonl`);
|
|
13545
13750
|
}
|
|
13546
13751
|
function ledgerStateDir(target) {
|
|
13547
13752
|
return target.kind === "cloud" ? stateDirFor(`cloud:${target.brainId}`) : stateDirFor(target.brain.root);
|
|
@@ -13585,13 +13790,13 @@ async function readEvents(vaultRoot) {
|
|
|
13585
13790
|
async function readEventsAt(stateDir) {
|
|
13586
13791
|
let files2;
|
|
13587
13792
|
try {
|
|
13588
|
-
files2 = (await
|
|
13793
|
+
files2 = (await readdir5(stateDir)).filter((f) => /^events-\d{4}-\d{2}\.jsonl$/.test(f)).sort();
|
|
13589
13794
|
} catch {
|
|
13590
13795
|
return [];
|
|
13591
13796
|
}
|
|
13592
13797
|
const events = [];
|
|
13593
13798
|
for (const file of files2) {
|
|
13594
|
-
const raw = await
|
|
13799
|
+
const raw = await readFile15(join15(stateDir, file), "utf8");
|
|
13595
13800
|
for (const line of raw.split("\n")) {
|
|
13596
13801
|
if (line.trim() === "") continue;
|
|
13597
13802
|
try {
|
|
@@ -14039,8 +14244,8 @@ init_exclude();
|
|
|
14039
14244
|
init_adapter();
|
|
14040
14245
|
init_walk();
|
|
14041
14246
|
import { createHash as createHash5 } from "crypto";
|
|
14042
|
-
import { link, lstat as lstat3, mkdir as mkdir10, readFile as
|
|
14043
|
-
import { dirname as
|
|
14247
|
+
import { link, lstat as lstat3, mkdir as mkdir10, readFile as readFile16, rm as rm7, writeFile as writeFile8 } from "fs/promises";
|
|
14248
|
+
import { dirname as dirname5, join as join17, resolve as resolve7 } from "path";
|
|
14044
14249
|
var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
14045
14250
|
tier = 1;
|
|
14046
14251
|
#read;
|
|
@@ -14131,7 +14336,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14131
14336
|
}
|
|
14132
14337
|
const gate = await this.#gateExisting(path, content);
|
|
14133
14338
|
if (!gate.ok) throw gate.error;
|
|
14134
|
-
const current = await
|
|
14339
|
+
const current = await readFile16(gate.abs);
|
|
14135
14340
|
const currentHash = createHash5("sha256").update(current).digest("hex");
|
|
14136
14341
|
if (currentHash !== opts.baseRevision.contentHash) {
|
|
14137
14342
|
const st = await lstat3(gate.abs);
|
|
@@ -14155,7 +14360,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14155
14360
|
async create(path, content) {
|
|
14156
14361
|
const gate = await this.#gateNew(path, content);
|
|
14157
14362
|
if (!gate.ok) throw gate.error;
|
|
14158
|
-
await mkdir10(
|
|
14363
|
+
await mkdir10(dirname5(gate.abs), { recursive: true });
|
|
14159
14364
|
const tmp = `${gate.abs}.${process.pid}.tmp`;
|
|
14160
14365
|
try {
|
|
14161
14366
|
await writeFile8(tmp, content, { encoding: "utf8", mode: 384 });
|
|
@@ -14166,7 +14371,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14166
14371
|
if (st.isSymbolicLink()) {
|
|
14167
14372
|
throw new Error(`refusing to follow a symlink inside the vault: ${path}`);
|
|
14168
14373
|
}
|
|
14169
|
-
const existing = await
|
|
14374
|
+
const existing = await readFile16(gate.abs);
|
|
14170
14375
|
return {
|
|
14171
14376
|
ok: false,
|
|
14172
14377
|
reason: "revision_conflict",
|
|
@@ -14224,8 +14429,8 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14224
14429
|
return { ok: false, error: err };
|
|
14225
14430
|
}
|
|
14226
14431
|
const rel = relativeTo(this.#root, abs);
|
|
14227
|
-
const
|
|
14228
|
-
return
|
|
14432
|
+
const refusal2 = this.#refuse(rel, path, content);
|
|
14433
|
+
return refusal2 ? { ok: false, error: refusal2 } : { ok: true, abs, rel };
|
|
14229
14434
|
}
|
|
14230
14435
|
/**
|
|
14231
14436
|
* Gates for a path that must NOT already exist.
|
|
@@ -14250,7 +14455,7 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14250
14455
|
}
|
|
14251
14456
|
let cursor = resolve7(this.#root);
|
|
14252
14457
|
for (const segment of rel.split("/").slice(0, -1)) {
|
|
14253
|
-
cursor =
|
|
14458
|
+
cursor = join17(cursor, segment);
|
|
14254
14459
|
try {
|
|
14255
14460
|
const st = await lstat3(cursor);
|
|
14256
14461
|
if (st.isSymbolicLink()) {
|
|
@@ -14264,8 +14469,8 @@ var LocalFileCasAdapter = class _LocalFileCasAdapter {
|
|
|
14264
14469
|
return { ok: false, error: err };
|
|
14265
14470
|
}
|
|
14266
14471
|
}
|
|
14267
|
-
const
|
|
14268
|
-
return
|
|
14472
|
+
const refusal2 = this.#refuse(rel, path, content);
|
|
14473
|
+
return refusal2 ? { ok: false, error: refusal2 } : { ok: true, abs, rel };
|
|
14269
14474
|
}
|
|
14270
14475
|
/**
|
|
14271
14476
|
* The rules, applied to a write. Returns the refusal or null.
|
|
@@ -14335,7 +14540,7 @@ init_ui();
|
|
|
14335
14540
|
|
|
14336
14541
|
// src/commands/revise.ts
|
|
14337
14542
|
init_src();
|
|
14338
|
-
import { readFile as
|
|
14543
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
14339
14544
|
import { Command as Command7 } from "commander";
|
|
14340
14545
|
init_errors();
|
|
14341
14546
|
init_index_build();
|
|
@@ -14353,10 +14558,10 @@ init_vault();
|
|
|
14353
14558
|
|
|
14354
14559
|
// src/lib/guard-fast.ts
|
|
14355
14560
|
init_brand();
|
|
14356
|
-
import { randomUUID as
|
|
14357
|
-
import { mkdir as mkdir11, readdir as
|
|
14358
|
-
import { join as
|
|
14359
|
-
var fastDir = () =>
|
|
14561
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
14562
|
+
import { mkdir as mkdir11, readdir as readdir7, rename as rename4, stat as stat4, unlink as unlink4, utimes, writeFile as writeFile9 } from "fs/promises";
|
|
14563
|
+
import { join as join18 } from "path";
|
|
14564
|
+
var fastDir = () => join18(brandHome(), "guard", "fast");
|
|
14360
14565
|
var EMPTY_TTL_S = 120;
|
|
14361
14566
|
var SESSION_ID = /^[A-Za-z0-9-]{1,128}$/;
|
|
14362
14567
|
var EMPTY_MARK = /^(.+)\.empty\.(\d+)$/;
|
|
@@ -14364,14 +14569,14 @@ var RULES_CHANGED = ".rules-changed";
|
|
|
14364
14569
|
var TMP_PREFIX = ".tmp-";
|
|
14365
14570
|
async function place(name, asOfMs) {
|
|
14366
14571
|
await mkdir11(fastDir(), { recursive: true, mode: 448 });
|
|
14367
|
-
const tmp =
|
|
14572
|
+
const tmp = join18(fastDir(), `${TMP_PREFIX}${process.pid}-${randomUUID3()}`);
|
|
14368
14573
|
try {
|
|
14369
14574
|
await writeFile9(tmp, "", { mode: 384 });
|
|
14370
14575
|
const dated = (asOfMs - 1e3) / 1e3;
|
|
14371
14576
|
await utimes(tmp, dated, dated);
|
|
14372
|
-
await rename4(tmp,
|
|
14577
|
+
await rename4(tmp, join18(fastDir(), name));
|
|
14373
14578
|
} catch (err) {
|
|
14374
|
-
await
|
|
14579
|
+
await unlink4(tmp).catch(() => void 0);
|
|
14375
14580
|
throw err;
|
|
14376
14581
|
}
|
|
14377
14582
|
}
|
|
@@ -14383,31 +14588,31 @@ async function markEmpty(sessionId, asOfMs) {
|
|
|
14383
14588
|
if (!SESSION_ID.test(sessionId)) return;
|
|
14384
14589
|
const until = Math.floor(asOfMs / 1e3) + EMPTY_TTL_S;
|
|
14385
14590
|
await place(`${sessionId}.empty.${until}`, asOfMs).catch(() => void 0);
|
|
14386
|
-
await
|
|
14591
|
+
await unlink4(join18(fastDir(), sessionId)).catch(() => void 0);
|
|
14387
14592
|
}
|
|
14388
14593
|
async function forgetEmptyChecks() {
|
|
14389
|
-
await mkdir11(fastDir(), { recursive: true, mode: 448 }).then(() => writeFile9(
|
|
14390
|
-
const names = await
|
|
14594
|
+
await mkdir11(fastDir(), { recursive: true, mode: 448 }).then(() => writeFile9(join18(fastDir(), RULES_CHANGED), "", { mode: 384 })).catch(() => void 0);
|
|
14595
|
+
const names = await readdir7(fastDir()).catch(() => []);
|
|
14391
14596
|
await Promise.all(
|
|
14392
|
-
names.filter((name) => EMPTY_MARK.test(name)).map((name) =>
|
|
14597
|
+
names.filter((name) => EMPTY_MARK.test(name)).map((name) => unlink4(join18(fastDir(), name)).catch(() => void 0))
|
|
14393
14598
|
);
|
|
14394
14599
|
}
|
|
14395
14600
|
var SERVED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14396
14601
|
var TMP_TTL_MS = 60 * 1e3;
|
|
14397
14602
|
async function pruneFast(nowMs = Date.now()) {
|
|
14398
|
-
const names = await
|
|
14603
|
+
const names = await readdir7(fastDir()).catch(() => []);
|
|
14399
14604
|
const nowS = Math.floor(nowMs / 1e3);
|
|
14400
14605
|
await Promise.all(
|
|
14401
14606
|
names.map(async (name) => {
|
|
14402
|
-
const path =
|
|
14607
|
+
const path = join18(fastDir(), name);
|
|
14403
14608
|
const empty = EMPTY_MARK.exec(name);
|
|
14404
14609
|
if (empty) {
|
|
14405
|
-
if (Number(empty[2]) <= nowS) await
|
|
14610
|
+
if (Number(empty[2]) <= nowS) await unlink4(path).catch(() => void 0);
|
|
14406
14611
|
return;
|
|
14407
14612
|
}
|
|
14408
|
-
const info = await
|
|
14613
|
+
const info = await stat4(path).catch(() => null);
|
|
14409
14614
|
const ttl = name.startsWith(TMP_PREFIX) ? TMP_TTL_MS : SERVED_TTL_MS;
|
|
14410
|
-
if (info && nowMs - info.mtimeMs > ttl) await
|
|
14615
|
+
if (info && nowMs - info.mtimeMs > ttl) await unlink4(path).catch(() => void 0);
|
|
14411
14616
|
})
|
|
14412
14617
|
);
|
|
14413
14618
|
}
|
|
@@ -15365,7 +15570,7 @@ async function readInput(source) {
|
|
|
15365
15570
|
process.stdin.on("data", (chunk) => buf += chunk);
|
|
15366
15571
|
process.stdin.on("end", () => resolve17(buf));
|
|
15367
15572
|
process.stdin.on("error", reject);
|
|
15368
|
-
}) : await
|
|
15573
|
+
}) : await readFile17(source, "utf8").catch(() => source);
|
|
15369
15574
|
try {
|
|
15370
15575
|
return JSON.parse(raw);
|
|
15371
15576
|
} catch {
|
|
@@ -16865,7 +17070,7 @@ async function deleteCloudBrain(args) {
|
|
|
16865
17070
|
|
|
16866
17071
|
// src/commands/curation.ts
|
|
16867
17072
|
init_src();
|
|
16868
|
-
import { stat as
|
|
17073
|
+
import { stat as stat5 } from "fs/promises";
|
|
16869
17074
|
import { resolve as resolve8 } from "path";
|
|
16870
17075
|
import { Command as Command14 } from "commander";
|
|
16871
17076
|
|
|
@@ -16874,12 +17079,12 @@ init_atomic();
|
|
|
16874
17079
|
init_exclude();
|
|
16875
17080
|
init_walk();
|
|
16876
17081
|
import { createHash as createHash6 } from "crypto";
|
|
16877
|
-
import { readFile as
|
|
17082
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
16878
17083
|
var FILE = ".memgignore";
|
|
16879
17084
|
var hashOf = (content) => createHash6("sha256").update(Buffer.from(content, "utf8")).digest("hex");
|
|
16880
17085
|
async function readIgnoreFileState(root) {
|
|
16881
17086
|
try {
|
|
16882
|
-
const content = await
|
|
17087
|
+
const content = await readFile18(resolveInside(root, FILE), "utf8");
|
|
16883
17088
|
return { content, exists: true, hash: hashOf(content) };
|
|
16884
17089
|
} catch (err) {
|
|
16885
17090
|
if (err?.code === "ENOENT") {
|
|
@@ -17181,7 +17386,7 @@ async function mustMatchAFile(root, adapter, path) {
|
|
|
17181
17386
|
const wanted = path.normalize("NFC").replace(/^\/+/, "");
|
|
17182
17387
|
const absolute = resolve8(root, wanted);
|
|
17183
17388
|
if (isInside(absolute, root)) {
|
|
17184
|
-
const found = await
|
|
17389
|
+
const found = await stat5(absolute).catch(() => null);
|
|
17185
17390
|
if (found?.isFile()) return wanted;
|
|
17186
17391
|
}
|
|
17187
17392
|
const hits = sameBasename(await adapter.list(""), path);
|
|
@@ -17755,8 +17960,8 @@ function ago(iso, now) {
|
|
|
17755
17960
|
init_brand();
|
|
17756
17961
|
init_config();
|
|
17757
17962
|
import { createHash as createHash7 } from "crypto";
|
|
17758
|
-
import { mkdir as mkdir12, readdir as
|
|
17759
|
-
import { dirname as
|
|
17963
|
+
import { mkdir as mkdir12, readdir as readdir8, stat as stat6, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
|
|
17964
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3, join as join20, resolve as resolve9 } from "path";
|
|
17760
17965
|
import { Command as Command17 } from "commander";
|
|
17761
17966
|
|
|
17762
17967
|
// src/lib/rules.ts
|
|
@@ -17859,7 +18064,7 @@ async function runGuard(payload, contextDirFlag) {
|
|
|
17859
18064
|
if (!FILE_TOOLS.has(toolName)) return null;
|
|
17860
18065
|
const filePath = fileFrom(payload);
|
|
17861
18066
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : process.cwd();
|
|
17862
|
-
const contextDir = contextDirFlag ?? (filePath === null ? cwd :
|
|
18067
|
+
const contextDir = contextDirFlag ?? (filePath === null ? cwd : dirname6(resolve9(cwd, filePath)));
|
|
17863
18068
|
const sessionId = typeof payload.session_id === "string" ? payload.session_id : null;
|
|
17864
18069
|
if (sessionId === null) return null;
|
|
17865
18070
|
const misdelivered = await misdeliveryNotice(toolName, filePath, sessionId);
|
|
@@ -17976,7 +18181,7 @@ async function readPayload() {
|
|
|
17976
18181
|
return null;
|
|
17977
18182
|
}
|
|
17978
18183
|
}
|
|
17979
|
-
var guardDir = () =>
|
|
18184
|
+
var guardDir = () => join20(brandHome(), "guard");
|
|
17980
18185
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
17981
18186
|
"Write",
|
|
17982
18187
|
"Edit",
|
|
@@ -17997,7 +18202,7 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
|
|
|
17997
18202
|
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
18203
|
}
|
|
17999
18204
|
function markerFor(sessionId, brain) {
|
|
18000
|
-
return
|
|
18205
|
+
return join20(
|
|
18001
18206
|
guardDir(),
|
|
18002
18207
|
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
18003
18208
|
);
|
|
@@ -18014,21 +18219,21 @@ async function claimSession(sessionId, brain) {
|
|
|
18014
18219
|
return true;
|
|
18015
18220
|
}
|
|
18016
18221
|
async function releaseSession(sessionId, brain) {
|
|
18017
|
-
await
|
|
18222
|
+
await unlink5(markerFor(sessionId, brain)).catch(() => void 0);
|
|
18018
18223
|
}
|
|
18019
18224
|
async function claimExists(sessionId, brain) {
|
|
18020
|
-
return
|
|
18225
|
+
return stat6(markerFor(sessionId, brain)).then(() => true).catch(() => false);
|
|
18021
18226
|
}
|
|
18022
18227
|
var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
18023
18228
|
async function pruneOldMarkers() {
|
|
18024
18229
|
try {
|
|
18025
18230
|
const dir = guardDir();
|
|
18026
18231
|
const now = Date.now();
|
|
18027
|
-
for (const name of await
|
|
18028
|
-
const path =
|
|
18232
|
+
for (const name of await readdir8(dir)) {
|
|
18233
|
+
const path = join20(dir, name);
|
|
18029
18234
|
if (!isAbsolute3(path)) continue;
|
|
18030
|
-
const info = await
|
|
18031
|
-
if (info && now - info.mtimeMs > MARKER_TTL_MS) await
|
|
18235
|
+
const info = await stat6(path).catch(() => null);
|
|
18236
|
+
if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink5(path).catch(() => void 0);
|
|
18032
18237
|
}
|
|
18033
18238
|
await pruneFast(now);
|
|
18034
18239
|
} catch {
|
|
@@ -18072,7 +18277,7 @@ init_link();
|
|
|
18072
18277
|
// src/commands/log.ts
|
|
18073
18278
|
init_src();
|
|
18074
18279
|
init_walk();
|
|
18075
|
-
import { lstat as lstat4, readFile as
|
|
18280
|
+
import { lstat as lstat4, readFile as readFile20 } from "fs/promises";
|
|
18076
18281
|
import { Command as Command19 } from "commander";
|
|
18077
18282
|
init_errors();
|
|
18078
18283
|
init_cloud_brain();
|
|
@@ -18638,7 +18843,7 @@ async function readOrNull(root, path) {
|
|
|
18638
18843
|
if (absolute === null) return null;
|
|
18639
18844
|
const info = await lstat4(absolute).catch(() => null);
|
|
18640
18845
|
if (!info || !info.isFile()) return null;
|
|
18641
|
-
return
|
|
18846
|
+
return readFile20(absolute, "utf8").catch(() => null);
|
|
18642
18847
|
}
|
|
18643
18848
|
|
|
18644
18849
|
// src/commands/project.ts
|
|
@@ -18774,48 +18979,153 @@ init_api_client();
|
|
|
18774
18979
|
init_credentials();
|
|
18775
18980
|
init_config();
|
|
18776
18981
|
init_pending_login();
|
|
18777
|
-
init_ui();
|
|
18778
18982
|
import { Command as Command22 } from "commander";
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
|
|
18789
|
-
|
|
18790
|
-
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
18799
|
-
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18983
|
+
|
|
18984
|
+
// src/lib/registry-session.ts
|
|
18985
|
+
init_errors();
|
|
18986
|
+
init_credentials();
|
|
18987
|
+
init_version();
|
|
18988
|
+
function registryApiUrl(stored) {
|
|
18989
|
+
const override = process.env["KORDIS_API_URL"];
|
|
18990
|
+
return override !== void 0 && override !== "" ? override : stored;
|
|
18991
|
+
}
|
|
18992
|
+
async function requireRegistryCredentials() {
|
|
18993
|
+
const credentials = await readCredentials();
|
|
18994
|
+
const registry = credentials?.registry;
|
|
18995
|
+
if (!registry) {
|
|
18996
|
+
throw memgError(
|
|
18997
|
+
"not_linked",
|
|
18998
|
+
"this machine holds no registry identity",
|
|
18999
|
+
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."
|
|
19000
|
+
);
|
|
19001
|
+
}
|
|
19002
|
+
return registry;
|
|
19003
|
+
}
|
|
19004
|
+
var RACE_WAIT_MS = 2e3;
|
|
19005
|
+
var RACE_POLL_MS = 50;
|
|
19006
|
+
function lostRace(err) {
|
|
19007
|
+
return err instanceof KordisApiError && err.status === 401 && isRefreshRace({ error: { code: err.code, flags: err.flags } });
|
|
19008
|
+
}
|
|
19009
|
+
async function newerRegistry(sent) {
|
|
19010
|
+
const deadline = Date.now() + RACE_WAIT_MS;
|
|
19011
|
+
for (; ; ) {
|
|
19012
|
+
const registry = (await readCredentials())?.registry;
|
|
19013
|
+
if (registry !== void 0 && registry.refresh_token !== sent) return registry;
|
|
19014
|
+
if (Date.now() >= deadline) return null;
|
|
19015
|
+
await new Promise((resolve17) => setTimeout(resolve17, RACE_POLL_MS));
|
|
19016
|
+
}
|
|
19017
|
+
}
|
|
19018
|
+
async function refreshRegistrySession(stored, opts = {}) {
|
|
19019
|
+
const attempt = async (registry) => {
|
|
19020
|
+
const baseUrl = (opts.apiUrlOf ?? ((r) => registryApiUrl(r.api_url)))(registry);
|
|
19021
|
+
const anonymous = new KordisClient({
|
|
19022
|
+
baseUrl,
|
|
19023
|
+
userAgent: `memgineering/${VERSION}`,
|
|
19024
|
+
...opts.fetchImpl ? { fetch: opts.fetchImpl } : {},
|
|
19025
|
+
...opts.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: opts.requestTimeoutMs }
|
|
19026
|
+
});
|
|
19027
|
+
return { registry, baseUrl, tokens: await anonymous.refreshSession(registry.refresh_token) };
|
|
19028
|
+
};
|
|
19029
|
+
try {
|
|
19030
|
+
return await attempt(stored);
|
|
19031
|
+
} catch (err) {
|
|
19032
|
+
if (!lostRace(err)) throw err;
|
|
19033
|
+
const newer = await newerRegistry(stored.refresh_token);
|
|
19034
|
+
if (newer === null) throw err;
|
|
19035
|
+
return attempt(newer);
|
|
19036
|
+
}
|
|
19037
|
+
}
|
|
19038
|
+
function refusal(err) {
|
|
19039
|
+
if (lostRace(err)) {
|
|
19040
|
+
return memgError(
|
|
19041
|
+
"not_authenticated",
|
|
19042
|
+
err.message,
|
|
19043
|
+
"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."
|
|
19044
|
+
);
|
|
19045
|
+
}
|
|
19046
|
+
const busyOrUnreachable = err instanceof KordisNetworkError || err instanceof KordisApiError && (err.status === 429 || err.status >= 500);
|
|
19047
|
+
return (busyOrUnreachable ? adaptRegistryError(err) : null) ?? memgError(
|
|
19048
|
+
"not_linked",
|
|
19049
|
+
`the registry refused this machine's stored sign-in: ${err instanceof Error ? err.message : String(err)}`,
|
|
19050
|
+
"Nothing was sent. The link between this brain and your registry account has expired or been revoked. `memgineering login` re-links both."
|
|
19051
|
+
);
|
|
19052
|
+
}
|
|
19053
|
+
async function openRegistrySession(fetchImpl) {
|
|
19054
|
+
const stored = await requireRegistryCredentials();
|
|
19055
|
+
let refreshed;
|
|
19056
|
+
try {
|
|
19057
|
+
refreshed = await refreshRegistrySession(stored, fetchImpl ? { fetchImpl } : {});
|
|
19058
|
+
} catch (err) {
|
|
19059
|
+
throw refusal(err);
|
|
19060
|
+
}
|
|
19061
|
+
const { registry, baseUrl, tokens } = refreshed;
|
|
19062
|
+
const credentialsRotated = await persistRotatedRefresh(
|
|
19063
|
+
registry,
|
|
19064
|
+
tokens.refresh_token,
|
|
19065
|
+
tokens.session_id
|
|
19066
|
+
);
|
|
19067
|
+
const client = new KordisClient({
|
|
19068
|
+
baseUrl,
|
|
19069
|
+
userAgent: `memgineering/${VERSION}`,
|
|
19070
|
+
getAccessToken: () => tokens.access_token,
|
|
19071
|
+
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
19072
|
+
});
|
|
19073
|
+
return { client, username: registry.username, apiUrl: baseUrl, credentialsRotated };
|
|
19074
|
+
}
|
|
19075
|
+
async function persistRotatedRefresh(sent, refreshToken, sessionId) {
|
|
19076
|
+
if (refreshToken === sent.refresh_token && sessionId === sent.session_id) return false;
|
|
19077
|
+
try {
|
|
19078
|
+
return await saveRotatedRegistry(sent.refresh_token, {
|
|
19079
|
+
refresh_token: refreshToken,
|
|
19080
|
+
session_id: sessionId
|
|
19081
|
+
});
|
|
19082
|
+
} catch {
|
|
19083
|
+
return false;
|
|
19084
|
+
}
|
|
19085
|
+
}
|
|
19086
|
+
|
|
19087
|
+
// src/commands/logout.ts
|
|
19088
|
+
init_ui();
|
|
19089
|
+
function logoutCommand() {
|
|
19090
|
+
return new Command22("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(
|
|
19091
|
+
"--local",
|
|
19092
|
+
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
19093
|
+
).action(async (opts) => {
|
|
19094
|
+
const base2 = apiUrl();
|
|
19095
|
+
const credentials = await readCredentials();
|
|
19096
|
+
await clearPendingLogin();
|
|
19097
|
+
await clearSetupFollowUps();
|
|
19098
|
+
if (!credentials) {
|
|
19099
|
+
printDual({
|
|
19100
|
+
json: { signed_out: true, was_signed_in: false, api_url: base2 },
|
|
19101
|
+
human: () => printHuman("Not signed in \u2014 nothing to do.")
|
|
19102
|
+
});
|
|
19103
|
+
return;
|
|
19104
|
+
}
|
|
19105
|
+
if (opts.local) {
|
|
19106
|
+
await clearCredentials();
|
|
19107
|
+
printDual({
|
|
19108
|
+
json: { signed_out: true, revoked_on_server: false, api_url: credentials.api_url },
|
|
19109
|
+
human: () => {
|
|
19110
|
+
printHuman("Forgotten here.");
|
|
19111
|
+
printHuman(
|
|
19112
|
+
c.yellow(
|
|
19113
|
+
" The token was NOT revoked \u2014 it still works for anyone holding a copy.\n Run `memgineering logout --all` once the server is reachable."
|
|
19114
|
+
)
|
|
19115
|
+
);
|
|
19116
|
+
}
|
|
19117
|
+
});
|
|
19118
|
+
return;
|
|
19119
|
+
}
|
|
19120
|
+
let revoked = null;
|
|
19121
|
+
try {
|
|
19122
|
+
if (opts.all) {
|
|
19123
|
+
const res = await apiRequest({
|
|
19124
|
+
method: "POST",
|
|
19125
|
+
path: "/v1/tokens/revoke-all",
|
|
19126
|
+
token: credentials.token,
|
|
19127
|
+
baseUrl: credentials.api_url
|
|
19128
|
+
});
|
|
18819
19129
|
revoked = res.body?.revoked ?? null;
|
|
18820
19130
|
} else {
|
|
18821
19131
|
const me = await apiRequest({
|
|
@@ -18845,12 +19155,13 @@ function logoutCommand() {
|
|
|
18845
19155
|
}) : err;
|
|
18846
19156
|
}
|
|
18847
19157
|
}
|
|
18848
|
-
const
|
|
19158
|
+
const registry = (await readCredentials())?.registry ?? credentials.registry;
|
|
19159
|
+
const registryRevoked = await revokeRegistrySession(registry);
|
|
18849
19160
|
await clearCredentials();
|
|
18850
19161
|
printDual({
|
|
18851
19162
|
json: {
|
|
18852
19163
|
signed_out: true,
|
|
18853
|
-
registry:
|
|
19164
|
+
registry: registry === void 0 ? "not-linked" : registryRevoked ? "revoked" : "revoke-failed-expires-on-its-own",
|
|
18854
19165
|
revoked_on_server: true,
|
|
18855
19166
|
revoked,
|
|
18856
19167
|
all: opts.all === true,
|
|
@@ -18875,18 +19186,16 @@ function logoutCommand() {
|
|
|
18875
19186
|
async function revokeRegistrySession(reg) {
|
|
18876
19187
|
if (!reg) return false;
|
|
18877
19188
|
try {
|
|
18878
|
-
const
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
body: JSON.stringify({ refresh_token: reg.refresh_token }),
|
|
18882
|
-
signal: AbortSignal.timeout(3e3)
|
|
19189
|
+
const { registry, tokens } = await refreshRegistrySession(reg, {
|
|
19190
|
+
apiUrlOf: (r) => r.api_url,
|
|
19191
|
+
requestTimeoutMs: 3e3
|
|
18883
19192
|
});
|
|
18884
|
-
if (
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
const del = await fetch(`${
|
|
19193
|
+
if (typeof tokens.access_token !== "string" || typeof tokens.session_id !== "string") {
|
|
19194
|
+
return false;
|
|
19195
|
+
}
|
|
19196
|
+
const del = await fetch(`${registry.api_url}/auth/sessions/${tokens.session_id}`, {
|
|
18888
19197
|
method: "DELETE",
|
|
18889
|
-
headers: { authorization: `Bearer ${
|
|
19198
|
+
headers: { authorization: `Bearer ${tokens.access_token}` },
|
|
18890
19199
|
signal: AbortSignal.timeout(3e3)
|
|
18891
19200
|
});
|
|
18892
19201
|
return del.ok;
|
|
@@ -19048,8 +19357,8 @@ init_api_client();
|
|
|
19048
19357
|
init_config();
|
|
19049
19358
|
init_cloud_brain();
|
|
19050
19359
|
init_ui();
|
|
19051
|
-
import { mkdir as mkdir13, readFile as
|
|
19052
|
-
import { dirname as
|
|
19360
|
+
import { mkdir as mkdir13, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
19361
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join21, resolve as resolve11, sep as sep2 } from "path";
|
|
19053
19362
|
import { Command as Command24 } from "commander";
|
|
19054
19363
|
function pullCommand() {
|
|
19055
19364
|
return new Command24("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) => {
|
|
@@ -19117,7 +19426,7 @@ function pullCommand() {
|
|
|
19117
19426
|
already.push(note.path);
|
|
19118
19427
|
continue;
|
|
19119
19428
|
}
|
|
19120
|
-
await mkdir13(
|
|
19429
|
+
await mkdir13(dirname7(file), { recursive: true });
|
|
19121
19430
|
await writeFile11(file, note.content ?? "", "utf8");
|
|
19122
19431
|
written.push(note.path);
|
|
19123
19432
|
if (note.excluded === true) excluded.push(note.path);
|
|
@@ -19232,7 +19541,7 @@ function safeJoin(root, relative3) {
|
|
|
19232
19541
|
"Nothing was written for it. This should not happen \u2014 please report it."
|
|
19233
19542
|
);
|
|
19234
19543
|
}
|
|
19235
|
-
const full = resolve11(
|
|
19544
|
+
const full = resolve11(join21(root, relative3));
|
|
19236
19545
|
if (full !== root && !full.startsWith(root + sep2)) {
|
|
19237
19546
|
throw memgError(
|
|
19238
19547
|
"server_refused",
|
|
@@ -19244,7 +19553,7 @@ function safeJoin(root, relative3) {
|
|
|
19244
19553
|
}
|
|
19245
19554
|
async function exists(file) {
|
|
19246
19555
|
try {
|
|
19247
|
-
await
|
|
19556
|
+
await readFile21(file);
|
|
19248
19557
|
return true;
|
|
19249
19558
|
} catch {
|
|
19250
19559
|
return false;
|
|
@@ -19393,72 +19702,6 @@ ${body}`;
|
|
|
19393
19702
|
init_cloud_brain();
|
|
19394
19703
|
init_index_build();
|
|
19395
19704
|
|
|
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
19705
|
// src/lib/publish-version.ts
|
|
19463
19706
|
import semver from "semver";
|
|
19464
19707
|
|
|
@@ -20154,9 +20397,9 @@ init_errors();
|
|
|
20154
20397
|
init_credentials();
|
|
20155
20398
|
|
|
20156
20399
|
// src/lib/untrusted-fence.ts
|
|
20157
|
-
import { randomUUID as
|
|
20400
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
20158
20401
|
function makeUntrustedSentinel() {
|
|
20159
|
-
return `KORDIS-UNTRUSTED-${
|
|
20402
|
+
return `KORDIS-UNTRUSTED-${randomUUID4()}`;
|
|
20160
20403
|
}
|
|
20161
20404
|
function fenceUntrusted(sentinel, text) {
|
|
20162
20405
|
const neutralized = text.replace(/KORDIS-UNTRUSTED-/g, "KORDIS-UNTRUSTED\u200B-");
|
|
@@ -20651,8 +20894,8 @@ init_errors();
|
|
|
20651
20894
|
|
|
20652
20895
|
// src/lib/harness-context.ts
|
|
20653
20896
|
init_brand();
|
|
20654
|
-
import { access, readFile as
|
|
20655
|
-
import { join as
|
|
20897
|
+
import { access, readFile as readFile22 } from "fs/promises";
|
|
20898
|
+
import { join as join22 } from "path";
|
|
20656
20899
|
var PER_FILE_CAP = 16 * 1024;
|
|
20657
20900
|
var TOTAL_CAP = 48 * 1024;
|
|
20658
20901
|
var TRUNCATION_RESERVE = 64;
|
|
@@ -20697,13 +20940,13 @@ function extractHeadings(content) {
|
|
|
20697
20940
|
}
|
|
20698
20941
|
async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
20699
20942
|
const candidates = [
|
|
20700
|
-
{ path:
|
|
20701
|
-
{ path:
|
|
20702
|
-
{ path:
|
|
20703
|
-
{ path:
|
|
20704
|
-
{ path:
|
|
20705
|
-
{ path:
|
|
20706
|
-
{ path:
|
|
20943
|
+
{ path: join22(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
|
|
20944
|
+
{ path: join22(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
|
|
20945
|
+
{ path: join22(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
|
|
20946
|
+
{ path: join22(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
|
|
20947
|
+
{ path: join22(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
|
|
20948
|
+
{ path: join22(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
|
|
20949
|
+
{ path: join22(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
|
|
20707
20950
|
];
|
|
20708
20951
|
const files2 = [];
|
|
20709
20952
|
let totalBytes = 0;
|
|
@@ -20711,7 +20954,7 @@ async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
|
20711
20954
|
if (!await pathExists(cand.path)) continue;
|
|
20712
20955
|
let raw;
|
|
20713
20956
|
try {
|
|
20714
|
-
raw = await
|
|
20957
|
+
raw = await readFile22(cand.path, "utf8");
|
|
20715
20958
|
} catch {
|
|
20716
20959
|
continue;
|
|
20717
20960
|
}
|
|
@@ -21108,8 +21351,8 @@ function parseLimit2(raw) {
|
|
|
21108
21351
|
import { Command as Command30 } from "commander";
|
|
21109
21352
|
import prompts3 from "prompts";
|
|
21110
21353
|
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
21111
|
-
import { mkdir as mkdir15, readFile as
|
|
21112
|
-
import { dirname as
|
|
21354
|
+
import { mkdir as mkdir15, readFile as readFile24, stat as stat7, writeFile as writeFile13 } from "fs/promises";
|
|
21355
|
+
import { dirname as dirname8, join as join24, resolve as resolve12, sep as sep3 } from "path";
|
|
21113
21356
|
|
|
21114
21357
|
// ../../packages/translators/src/skill/identity.ts
|
|
21115
21358
|
function buildSkillFile(input, outputPath) {
|
|
@@ -21684,80 +21927,31 @@ function getTranslator(type, format) {
|
|
|
21684
21927
|
return fn;
|
|
21685
21928
|
}
|
|
21686
21929
|
|
|
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();
|
|
21930
|
+
// ../../packages/translators/src/merge-emitted-file.ts
|
|
21931
|
+
import tomlLib2 from "@iarna/toml";
|
|
21932
|
+
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
21933
|
+
var MergeRefusal = class extends Error {
|
|
21934
|
+
code;
|
|
21935
|
+
reason;
|
|
21936
|
+
constructor(reason) {
|
|
21937
|
+
super(refusalMessage(reason));
|
|
21938
|
+
this.name = "MergeRefusal";
|
|
21939
|
+
this.code = reason.kind === "prototype_key" ? "unsafe_package_config" : "merge_refused";
|
|
21940
|
+
this.reason = reason;
|
|
21734
21941
|
}
|
|
21735
|
-
|
|
21736
|
-
|
|
21737
|
-
|
|
21738
|
-
|
|
21942
|
+
};
|
|
21943
|
+
function refusalMessage(reason) {
|
|
21944
|
+
switch (reason.kind) {
|
|
21945
|
+
case "prototype_key":
|
|
21946
|
+
return `refusing to merge config fragment \u2014 prototype-polluting key "${reason.key}" at ${reason.path}`;
|
|
21947
|
+
case "malformed_file":
|
|
21948
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing file is not valid ${reason.format.toUpperCase()}: ${reason.parseError}`;
|
|
21949
|
+
case "non_object_root":
|
|
21950
|
+
return `refusing to merge into "${reason.targetPath}" \u2014 existing ${reason.format.toUpperCase()} root is not an object`;
|
|
21951
|
+
case "dangling_marker":
|
|
21952
|
+
return `refusing to merge \u2014 found a "kordis:${reason.fullSlug}" start marker with no matching end marker`;
|
|
21739
21953
|
}
|
|
21740
21954
|
}
|
|
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
21955
|
function isPlainObject(v) {
|
|
21762
21956
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21763
21957
|
}
|
|
@@ -21782,11 +21976,7 @@ function assertNoProtoKeys(value, path) {
|
|
|
21782
21976
|
if (!isPlainObject(value)) return;
|
|
21783
21977
|
for (const [k, v] of Object.entries(value)) {
|
|
21784
21978
|
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
|
-
);
|
|
21979
|
+
throw new MergeRefusal({ kind: "prototype_key", key: k, path: [...path, k].join(".") });
|
|
21790
21980
|
}
|
|
21791
21981
|
assertNoProtoKeys(v, [...path, k]);
|
|
21792
21982
|
}
|
|
@@ -21795,12 +21985,15 @@ function isOwnedServerLeaf(path) {
|
|
|
21795
21985
|
return path.length === 2 && (path[0] === "mcpServers" || path[0] === "mcp_servers");
|
|
21796
21986
|
}
|
|
21797
21987
|
function deepMergeFragment(existing, fragment, path = []) {
|
|
21798
|
-
|
|
21988
|
+
assertNoProtoKeys(fragment, path);
|
|
21989
|
+
return mergeAt(existing, fragment, path);
|
|
21990
|
+
}
|
|
21991
|
+
function mergeAt(existing, fragment, path) {
|
|
21799
21992
|
if (isOwnedServerLeaf(path)) return fragment;
|
|
21800
21993
|
if (isPlainObject(existing) && isPlainObject(fragment)) {
|
|
21801
21994
|
const out = { ...existing };
|
|
21802
21995
|
for (const [k, v] of Object.entries(fragment)) {
|
|
21803
|
-
out[k] =
|
|
21996
|
+
out[k] = mergeAt(existing[k], v, [...path, k]);
|
|
21804
21997
|
}
|
|
21805
21998
|
return out;
|
|
21806
21999
|
}
|
|
@@ -21843,18 +22036,15 @@ function mergeStructuredFile(existingRaw, fragmentRaw, format, targetPath) {
|
|
|
21843
22036
|
try {
|
|
21844
22037
|
existing = parseStructured(existingRaw, format);
|
|
21845
22038
|
} catch (err) {
|
|
21846
|
-
throw
|
|
21847
|
-
"
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
22039
|
+
throw new MergeRefusal({
|
|
22040
|
+
kind: "malformed_file",
|
|
22041
|
+
targetPath,
|
|
22042
|
+
format,
|
|
22043
|
+
parseError: err.message
|
|
22044
|
+
});
|
|
21851
22045
|
}
|
|
21852
22046
|
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
|
-
);
|
|
22047
|
+
throw new MergeRefusal({ kind: "non_object_root", targetPath, format });
|
|
21858
22048
|
}
|
|
21859
22049
|
const merged = deepMergeFragment(existing, fragment);
|
|
21860
22050
|
return stringifyStructured(merged, format);
|
|
@@ -21876,15 +22066,100 @@ function mergeFencedFile(existingRaw, fullSlug, block) {
|
|
|
21876
22066
|
return existingRaw.slice(0, startMatch.index) + trimmedBlock + existingRaw.slice(spanEnd);
|
|
21877
22067
|
}
|
|
21878
22068
|
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
|
-
);
|
|
22069
|
+
throw new MergeRefusal({ kind: "dangling_marker", fullSlug });
|
|
21884
22070
|
}
|
|
21885
22071
|
return existingRaw.trimEnd() + "\n\n" + trimmedBlock + "\n";
|
|
21886
22072
|
}
|
|
21887
22073
|
|
|
22074
|
+
// src/commands/registry/pull.ts
|
|
22075
|
+
init_errors();
|
|
22076
|
+
init_ui();
|
|
22077
|
+
|
|
22078
|
+
// src/lib/lockfile.ts
|
|
22079
|
+
init_brand();
|
|
22080
|
+
import { copyFile, mkdir as mkdir14, readFile as readFile23, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
|
|
22081
|
+
import { homedir as homedir4 } from "os";
|
|
22082
|
+
import { join as join23 } from "path";
|
|
22083
|
+
function effectiveRange(entry) {
|
|
22084
|
+
return entry.requestedRange ?? `^${entry.version}`;
|
|
22085
|
+
}
|
|
22086
|
+
function lockfilePath() {
|
|
22087
|
+
return join23(brandHome(), "installed.json");
|
|
22088
|
+
}
|
|
22089
|
+
function kordisLockfilePath() {
|
|
22090
|
+
return join23(process.env["KORDIS_HOME"] ?? join23(homedir4(), ".kordis"), "installed.json");
|
|
22091
|
+
}
|
|
22092
|
+
function lockfileDisplayPath() {
|
|
22093
|
+
const path = lockfilePath();
|
|
22094
|
+
const home = process.env["HOME"];
|
|
22095
|
+
return home !== void 0 && home !== "" && path.startsWith(home) ? "~" + path.slice(home.length) : path;
|
|
22096
|
+
}
|
|
22097
|
+
async function migrateFromKordis() {
|
|
22098
|
+
const source = kordisLockfilePath();
|
|
22099
|
+
const destination = lockfilePath();
|
|
22100
|
+
try {
|
|
22101
|
+
await mkdir14(brandHome(), { recursive: true });
|
|
22102
|
+
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
22103
|
+
} catch {
|
|
22104
|
+
return;
|
|
22105
|
+
}
|
|
22106
|
+
try {
|
|
22107
|
+
const raw = await readFile23(destination, "utf8");
|
|
22108
|
+
const parsed = JSON.parse(raw);
|
|
22109
|
+
if (parsed.migratedFrom === void 0) {
|
|
22110
|
+
parsed.migratedFrom = source;
|
|
22111
|
+
await writeLockfile(parsed);
|
|
22112
|
+
}
|
|
22113
|
+
} catch {
|
|
22114
|
+
}
|
|
22115
|
+
}
|
|
22116
|
+
async function readLockfile() {
|
|
22117
|
+
try {
|
|
22118
|
+
return JSON.parse(await readFile23(lockfilePath(), "utf8"));
|
|
22119
|
+
} catch {
|
|
22120
|
+
await migrateFromKordis();
|
|
22121
|
+
}
|
|
22122
|
+
try {
|
|
22123
|
+
return JSON.parse(await readFile23(lockfilePath(), "utf8"));
|
|
22124
|
+
} catch {
|
|
22125
|
+
return { packages: {} };
|
|
22126
|
+
}
|
|
22127
|
+
}
|
|
22128
|
+
async function writeLockfile(lf) {
|
|
22129
|
+
await mkdir14(brandHome(), { recursive: true });
|
|
22130
|
+
await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
22131
|
+
encoding: "utf8",
|
|
22132
|
+
mode: 384
|
|
22133
|
+
});
|
|
22134
|
+
}
|
|
22135
|
+
async function recordInstalled(fullSlug, entry) {
|
|
22136
|
+
const home = brandHome();
|
|
22137
|
+
await withFileLock(join23(home, "installed.lock"), home, async () => {
|
|
22138
|
+
const lf = await readLockfile();
|
|
22139
|
+
lf.packages[fullSlug] = entry;
|
|
22140
|
+
await writeLockfile(lf);
|
|
22141
|
+
});
|
|
22142
|
+
}
|
|
22143
|
+
|
|
22144
|
+
// src/lib/merge-refusal.ts
|
|
22145
|
+
init_errors();
|
|
22146
|
+
function adaptMergeRefusal(err) {
|
|
22147
|
+
if (!(err instanceof MergeRefusal)) return null;
|
|
22148
|
+
return memgError(err.code, err.message, mergeRefusalHint(err.reason));
|
|
22149
|
+
}
|
|
22150
|
+
function mergeRefusalHint(reason) {
|
|
22151
|
+
switch (reason.kind) {
|
|
22152
|
+
case "prototype_key":
|
|
22153
|
+
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.";
|
|
22154
|
+
case "malformed_file":
|
|
22155
|
+
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.`;
|
|
22156
|
+
case "non_object_root":
|
|
22157
|
+
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.`;
|
|
22158
|
+
case "dangling_marker":
|
|
22159
|
+
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.`;
|
|
22160
|
+
}
|
|
22161
|
+
}
|
|
22162
|
+
|
|
21888
22163
|
// src/commands/registry/pull.ts
|
|
21889
22164
|
async function requireExecutableApproval(pkg, fullSlug, files2, approve2) {
|
|
21890
22165
|
if (!pkg.capabilities.executable) return;
|
|
@@ -22015,7 +22290,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
22015
22290
|
);
|
|
22016
22291
|
for (const f of translated.files) {
|
|
22017
22292
|
const abs = safeInstallPath(installRoot, f.path);
|
|
22018
|
-
await mkdir15(
|
|
22293
|
+
await mkdir15(dirname8(abs), { recursive: true });
|
|
22019
22294
|
await applyEmittedFile(abs, f, fullSlug, notes);
|
|
22020
22295
|
written.push(abs);
|
|
22021
22296
|
}
|
|
@@ -22171,7 +22446,7 @@ var TOOL_MARKERS = [
|
|
|
22171
22446
|
async function detectTool(root) {
|
|
22172
22447
|
for (const [path, tool] of TOOL_MARKERS) {
|
|
22173
22448
|
try {
|
|
22174
|
-
await
|
|
22449
|
+
await stat7(join24(root, path));
|
|
22175
22450
|
return tool;
|
|
22176
22451
|
} catch {
|
|
22177
22452
|
}
|
|
@@ -22187,7 +22462,7 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
22187
22462
|
"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
22463
|
);
|
|
22189
22464
|
}
|
|
22190
|
-
const abs =
|
|
22465
|
+
const abs = join24(installRoot, relPath);
|
|
22191
22466
|
const resolvedRoot = resolve12(installRoot);
|
|
22192
22467
|
const resolvedAbs = resolve12(abs);
|
|
22193
22468
|
if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep3)) {
|
|
@@ -22203,9 +22478,9 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
22203
22478
|
} catch {
|
|
22204
22479
|
return abs;
|
|
22205
22480
|
}
|
|
22206
|
-
let ancestor =
|
|
22481
|
+
let ancestor = dirname8(resolvedAbs);
|
|
22207
22482
|
while (!existsSync(ancestor)) {
|
|
22208
|
-
const parent =
|
|
22483
|
+
const parent = dirname8(ancestor);
|
|
22209
22484
|
if (parent === ancestor) break;
|
|
22210
22485
|
ancestor = parent;
|
|
22211
22486
|
}
|
|
@@ -22253,12 +22528,17 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
22253
22528
|
}
|
|
22254
22529
|
let existingRaw = null;
|
|
22255
22530
|
try {
|
|
22256
|
-
existingRaw = await
|
|
22531
|
+
existingRaw = await readFile24(abs, "utf8");
|
|
22257
22532
|
} catch (err) {
|
|
22258
22533
|
if (err.code !== "ENOENT") throw err;
|
|
22259
22534
|
}
|
|
22260
22535
|
if (f.format === "json" || f.format === "yaml" || f.format === "toml") {
|
|
22261
|
-
|
|
22536
|
+
let merged;
|
|
22537
|
+
try {
|
|
22538
|
+
merged = mergeStructuredFile(existingRaw, f.content, f.format, abs);
|
|
22539
|
+
} catch (err) {
|
|
22540
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
22541
|
+
}
|
|
22262
22542
|
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
22263
22543
|
notes?.push(
|
|
22264
22544
|
`merged into ${f.path} \u2014 comments/formatting in the existing file are not preserved`
|
|
@@ -22267,7 +22547,13 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
22267
22547
|
await writeFile13(abs, merged, "utf8");
|
|
22268
22548
|
return;
|
|
22269
22549
|
}
|
|
22270
|
-
|
|
22550
|
+
let fenced;
|
|
22551
|
+
try {
|
|
22552
|
+
fenced = mergeFencedFile(existingRaw, fullSlug, f.content);
|
|
22553
|
+
} catch (err) {
|
|
22554
|
+
throw adaptMergeRefusal(err) ?? err;
|
|
22555
|
+
}
|
|
22556
|
+
await writeFile13(abs, fenced, "utf8");
|
|
22271
22557
|
}
|
|
22272
22558
|
|
|
22273
22559
|
// src/commands/registry/update.ts
|
|
@@ -22770,12 +23056,14 @@ init_push();
|
|
|
22770
23056
|
|
|
22771
23057
|
// src/commands/recall.ts
|
|
22772
23058
|
init_src();
|
|
23059
|
+
init_exclude();
|
|
22773
23060
|
init_config();
|
|
22774
23061
|
import { Command as Command37 } from "commander";
|
|
22775
|
-
import { basename as
|
|
23062
|
+
import { basename as basename3, resolve as resolve13 } from "path";
|
|
22776
23063
|
init_index_build();
|
|
22777
23064
|
init_cloud_brain();
|
|
22778
23065
|
init_errors();
|
|
23066
|
+
init_held_back();
|
|
22779
23067
|
init_vault();
|
|
22780
23068
|
init_ui();
|
|
22781
23069
|
function recallCommand() {
|
|
@@ -22800,6 +23088,10 @@ function recallCommand() {
|
|
|
22800
23088
|
let unsearchedBrains = [];
|
|
22801
23089
|
let unsearchedLocal = [];
|
|
22802
23090
|
let searchedLocalName = null;
|
|
23091
|
+
let nothingToSearch = null;
|
|
23092
|
+
let alsoLeftOut = null;
|
|
23093
|
+
let namedIsPointer = false;
|
|
23094
|
+
let serverHint;
|
|
22803
23095
|
let isScaffolding;
|
|
22804
23096
|
let templatesWithheld = 0;
|
|
22805
23097
|
let spread = null;
|
|
@@ -22846,8 +23138,19 @@ function recallCommand() {
|
|
|
22846
23138
|
}));
|
|
22847
23139
|
spread = isScopeSpread(answered.scope_spread) ? answered.scope_spread : null;
|
|
22848
23140
|
templatesWithheld = Number.isInteger(answered.templates_withheld) && (answered.templates_withheld ?? 0) > 0 ? answered.templates_withheld : 0;
|
|
22849
|
-
if (rows.
|
|
22850
|
-
|
|
23141
|
+
if (rows.every(({ card }) => unfilledIds.has(card.id))) {
|
|
23142
|
+
const listed = await listCloudBrains(cloud.token, cloud.apiUrl, { retries: 0 }).catch(
|
|
23143
|
+
() => []
|
|
23144
|
+
);
|
|
23145
|
+
unsearchedBrains = listed.filter((b) => b.id !== cloud.brainId);
|
|
23146
|
+
if (listed.find((b) => b.id === cloud.brainId)?.note_count === 0) {
|
|
23147
|
+
nothingToSearch = { kind: "no_notes" };
|
|
23148
|
+
}
|
|
23149
|
+
if (opts.brain !== void 0) {
|
|
23150
|
+
const pointer = await hostedPointer().catch(() => null);
|
|
23151
|
+
namedIsPointer = pointer !== null && pointer.id === cloud.brainId && (pointer.signedIn || pointer.signInPending);
|
|
23152
|
+
}
|
|
23153
|
+
serverHint = typeof answered.hint === "string" && answered.hint.trim() !== "" ? answered.hint : void 0;
|
|
22851
23154
|
}
|
|
22852
23155
|
isScaffolding = (card) => unfilledIds.has(card.id);
|
|
22853
23156
|
reasons = /* @__PURE__ */ new Map();
|
|
@@ -22889,7 +23192,11 @@ function recallCommand() {
|
|
|
22889
23192
|
);
|
|
22890
23193
|
isScaffolding = (card) => scaffolding.has(byId.get(card.id)?.path ?? "");
|
|
22891
23194
|
const sides = new Set(local.conflicts.flatMap((conflict) => conflict.between));
|
|
22892
|
-
const capped2 = capTemplates(
|
|
23195
|
+
const capped2 = capTemplates(
|
|
23196
|
+
local.memory_cards,
|
|
23197
|
+
isScaffolding,
|
|
23198
|
+
(card) => sides.has(card.id)
|
|
23199
|
+
);
|
|
22893
23200
|
templatesWithheld = capped2.withheld.length;
|
|
22894
23201
|
result = { ...local, memory_cards: capped2.kept };
|
|
22895
23202
|
entries = built.entries;
|
|
@@ -22902,8 +23209,16 @@ function recallCommand() {
|
|
|
22902
23209
|
built.entries.map((e) => declaredScope(e.memory))
|
|
22903
23210
|
);
|
|
22904
23211
|
}
|
|
22905
|
-
if (result.memory_cards.
|
|
22906
|
-
|
|
23212
|
+
if (result.memory_cards.every((card) => isScaffolding(card))) {
|
|
23213
|
+
const leftOut = await adapter.scan().then((scan) => leftOutOf(heldBack(scan), built.denied)).catch(() => void 0);
|
|
23214
|
+
if (built.entries.length === 0) {
|
|
23215
|
+
if (leftOut !== void 0) {
|
|
23216
|
+
nothingToSearch = leftOut === null ? { kind: "no_notes" } : { kind: "all_left_out", leftOut };
|
|
23217
|
+
}
|
|
23218
|
+
} else {
|
|
23219
|
+
alsoLeftOut = leftOut ?? null;
|
|
23220
|
+
}
|
|
23221
|
+
searchedLocalName = basename3(resolve13(brain.root));
|
|
22907
23222
|
unsearchedLocal = await loadConfig().then(
|
|
22908
23223
|
(cfg) => cfg.brain.brains.filter((other) => resolve13(other.root) !== resolve13(brain.root)).map((other) => ({ root: other.root }))
|
|
22909
23224
|
).catch(() => []);
|
|
@@ -22931,6 +23246,32 @@ function recallCommand() {
|
|
|
22931
23246
|
);
|
|
22932
23247
|
reasons = result.conflicts.length > 0 ? await conflictReasons(brain.root, result.conflicts, byId) : /* @__PURE__ */ new Map();
|
|
22933
23248
|
}
|
|
23249
|
+
const reachedSomethingWritten = rows.some(({ card }) => !isScaffolding(card));
|
|
23250
|
+
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
23251
|
+
const advice = !reachedSomethingWritten ? emptyRecallAdvice({
|
|
23252
|
+
query,
|
|
23253
|
+
depth,
|
|
23254
|
+
scoped: Boolean(opts.scope),
|
|
23255
|
+
chosenWith: {
|
|
23256
|
+
...opts.brain === void 0 || namedIsPointer ? {} : { brain: answeredBy ?? opts.brain },
|
|
23257
|
+
...opts.local === true ? { local: true } : {},
|
|
23258
|
+
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
23259
|
+
},
|
|
23260
|
+
nothingToSearch,
|
|
23261
|
+
alsoLeftOut,
|
|
23262
|
+
otherScript,
|
|
23263
|
+
unsearched: unsearchedBrains.length > 0 ? {
|
|
23264
|
+
searched: answeredBy ?? "",
|
|
23265
|
+
flag: "--brain",
|
|
23266
|
+
others: unsearchedBrains.map((other) => other.name)
|
|
23267
|
+
} : unsearchedLocal.length > 0 ? {
|
|
23268
|
+
searched: searchedLocalName ?? "",
|
|
23269
|
+
flag: "--vault",
|
|
23270
|
+
others: unsearchedLocal.map((other) => other.root)
|
|
23271
|
+
} : null,
|
|
23272
|
+
semanticHint: semantic.state === "skipped" ? semantic.hint : void 0,
|
|
23273
|
+
serverHint
|
|
23274
|
+
}) : null;
|
|
22934
23275
|
printDual({
|
|
22935
23276
|
json: {
|
|
22936
23277
|
query,
|
|
@@ -22969,20 +23310,29 @@ function recallCommand() {
|
|
|
22969
23310
|
// Counted, never silent: these matched and were left off because
|
|
22970
23311
|
// they are still exactly what `init` wrote.
|
|
22971
23312
|
...templatesWithheld > 0 ? { templates_withheld: templatesWithheld } : {},
|
|
22972
|
-
...spread === null ? {} : { scope_spread: spread }
|
|
23313
|
+
...spread === null ? {} : { scope_spread: spread },
|
|
23314
|
+
// Only when nothing the person wrote came back — no card, or only
|
|
23315
|
+
// untouched templates: the one next step the footer would have led
|
|
23316
|
+
// with. An agent reading `cards: []` alone cannot tell an empty
|
|
23317
|
+
// brain from a missed word from an unread brain, and one reading
|
|
23318
|
+
// template cards alone took them for the whole brain.
|
|
23319
|
+
...advice === null ? {} : { hint: advice.hint }
|
|
22973
23320
|
},
|
|
22974
23321
|
human: () => {
|
|
22975
23322
|
const brainHeading = answeredBy === null ? "" : ` \u2014 ${oneLine(answeredBy, WIDTH.title)}`;
|
|
22976
|
-
const
|
|
22977
|
-
const otherScript = reachedSomethingWritten ? null : entries ? unreachableScript(query, entries) : null;
|
|
22978
|
-
const sayWhy = () => {
|
|
23323
|
+
const sayWhy = (notice2) => {
|
|
22979
23324
|
printHuman(
|
|
22980
|
-
c.yellow(
|
|
22981
|
-
|
|
22982
|
-
|
|
23325
|
+
c.yellow(`\u26A0 ${notice2.headline}`) + c.gray(`
|
|
23326
|
+
${notice2.lines.map((line) => ` ${line}`).join("\n")}
|
|
23327
|
+
`)
|
|
23328
|
+
);
|
|
23329
|
+
};
|
|
23330
|
+
const sayOtherBrains = (otherBrains) => {
|
|
23331
|
+
printHuman(
|
|
23332
|
+
c.yellow(`\u26A0 ${otherBrains.headline}`) + c.gray(
|
|
22983
23333
|
`
|
|
22984
|
-
|
|
22985
|
-
|
|
23334
|
+
${otherBrains.commands.map((cmd) => ` ${cmd}`).join("\n")}
|
|
23335
|
+
${otherBrains.safe}
|
|
22986
23336
|
`
|
|
22987
23337
|
)
|
|
22988
23338
|
);
|
|
@@ -22995,60 +23345,18 @@ function recallCommand() {
|
|
|
22995
23345
|
`)
|
|
22996
23346
|
);
|
|
22997
23347
|
};
|
|
22998
|
-
if (rows.length === 0) {
|
|
23348
|
+
if (advice !== null && rows.length === 0) {
|
|
22999
23349
|
printHuman(`## recall: ${query}${brainHeading}
|
|
23000
23350
|
`);
|
|
23001
23351
|
printHuman("_Nothing matched._\n");
|
|
23002
23352
|
sayDegraded();
|
|
23003
|
-
if (
|
|
23004
|
-
|
|
23353
|
+
if (advice.leftOut !== null) sayWhy(advice.leftOut);
|
|
23354
|
+
if (advice.otherScript !== null) {
|
|
23355
|
+
sayWhy(advice.otherScript);
|
|
23005
23356
|
return;
|
|
23006
23357
|
}
|
|
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
|
-
);
|
|
23358
|
+
if (advice.otherBrains !== null) sayOtherBrains(advice.otherBrains);
|
|
23359
|
+
if (advice.withinThisBrain !== null) printHuman(c.gray(advice.withinThisBrain));
|
|
23052
23360
|
return;
|
|
23053
23361
|
}
|
|
23054
23362
|
printHuman(`## recall: ${query}${brainHeading} (${rows.length} cards)
|
|
@@ -23060,6 +23368,10 @@ ${unsearchedLocal.map(
|
|
|
23060
23368
|
"\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
23369
|
)
|
|
23062
23370
|
);
|
|
23371
|
+
if (advice !== null && advice.leftOut !== null) sayWhy(advice.leftOut);
|
|
23372
|
+
if (advice !== null && advice.otherScript === null && advice.otherBrains !== null) {
|
|
23373
|
+
sayOtherBrains(advice.otherBrains);
|
|
23374
|
+
}
|
|
23063
23375
|
}
|
|
23064
23376
|
const templateCount = rows.filter(({ card }) => isScaffolding(card)).length;
|
|
23065
23377
|
if (reachedSomethingWritten && templateCount > 0) {
|
|
@@ -23089,7 +23401,7 @@ ${unsearchedLocal.map(
|
|
|
23089
23401
|
)
|
|
23090
23402
|
);
|
|
23091
23403
|
}
|
|
23092
|
-
if (otherScript) sayWhy();
|
|
23404
|
+
if (otherScript) sayWhy(otherScriptNotice(otherScript));
|
|
23093
23405
|
const glossOn = rows.findIndex(
|
|
23094
23406
|
({ card }) => card.confidence === "stated" && !isScaffolding(card)
|
|
23095
23407
|
);
|
|
@@ -23257,6 +23569,114 @@ function ago2(days) {
|
|
|
23257
23569
|
if (days < 365) return `${Math.floor(days / 30)} months ago`;
|
|
23258
23570
|
return `${Math.floor(days / 365)} years ago`;
|
|
23259
23571
|
}
|
|
23572
|
+
function emptyRecallAdvice(facts) {
|
|
23573
|
+
const otherScript = facts.otherScript === null ? null : otherScriptNotice(facts.otherScript);
|
|
23574
|
+
const otherBrains = facts.unsearched === null || facts.unsearched.others.length === 0 ? null : unsearchedNotice(facts.query, facts.unsearched);
|
|
23575
|
+
const nothing = facts.nothingToSearch;
|
|
23576
|
+
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");
|
|
23577
|
+
const within = (
|
|
23578
|
+
// "Within this brain" is load-bearing when the warning above it has just
|
|
23579
|
+
// said another brain went unsearched. The enumeration reads as the whole
|
|
23580
|
+
// surface — a recheck tester called this the last line on screen and a
|
|
23581
|
+
// false all-clear, sitting below the one line that says otherwise. Below
|
|
23582
|
+
// a count of held-back notes it would be the same false all-clear.
|
|
23583
|
+
(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
|
|
23584
|
+
// argument they never supplied cost one tester a whole command to `--help`
|
|
23585
|
+
// to find out it was not their bug.
|
|
23586
|
+
(facts.scoped ? ", or drop --scope." : ".")
|
|
23587
|
+
);
|
|
23588
|
+
const hint = (() => {
|
|
23589
|
+
if (nothing !== null) return nothingToSearchHint(nothing, facts.chosenWith);
|
|
23590
|
+
const next = (() => {
|
|
23591
|
+
if (otherScript !== null) return [otherScript.headline, ...otherScript.lines].join(" ");
|
|
23592
|
+
if (otherBrains !== null) {
|
|
23593
|
+
const runnable = otherBrains.commands.map((cmd) => `\`${cmd}\``).join(", ");
|
|
23594
|
+
return `${otherBrains.headline} To search ${otherBrains.commands.length === 1 ? "the other one" : "the others"}: ${runnable}. ${otherBrains.safe}`;
|
|
23595
|
+
}
|
|
23596
|
+
if (facts.semanticHint !== void 0) {
|
|
23597
|
+
return `Meaning was not searched \u2014 words only: ${facts.semanticHint}`;
|
|
23598
|
+
}
|
|
23599
|
+
if (facts.serverHint !== void 0) return facts.serverHint;
|
|
23600
|
+
return within;
|
|
23601
|
+
})();
|
|
23602
|
+
return leftOut === null ? next : `${noticeText(leftOut)} ${next}`;
|
|
23603
|
+
})();
|
|
23604
|
+
return {
|
|
23605
|
+
hint,
|
|
23606
|
+
leftOut,
|
|
23607
|
+
otherScript,
|
|
23608
|
+
otherBrains,
|
|
23609
|
+
// Nothing was searched, so no wording reaches anything.
|
|
23610
|
+
withinThisBrain: nothing?.kind === "all_left_out" ? null : within
|
|
23611
|
+
};
|
|
23612
|
+
}
|
|
23613
|
+
function noticeText(notice2) {
|
|
23614
|
+
return [notice2.headline, ...notice2.lines].join(" ");
|
|
23615
|
+
}
|
|
23616
|
+
function otherScriptNotice(script) {
|
|
23617
|
+
return {
|
|
23618
|
+
headline: `This brain's notes are mostly written in ${script}, and this query is not.`,
|
|
23619
|
+
lines: [
|
|
23620
|
+
"Search matches text, not meaning, so no rewording in another language will reach them.",
|
|
23621
|
+
`Ask again in ${script}, or run \`memgineering resurface\` to see what is here.`
|
|
23622
|
+
]
|
|
23623
|
+
};
|
|
23624
|
+
}
|
|
23625
|
+
function unsearchedNotice(query, unsearched) {
|
|
23626
|
+
const total = unsearched.others.length + 1;
|
|
23627
|
+
const hosted = unsearched.flag === "--brain";
|
|
23628
|
+
return {
|
|
23629
|
+
headline: `Only \`${oneLine(unsearched.searched, WIDTH.title)}\` was searched \u2014 ${hosted ? `this account has ${total} brains` : `${total} brains are linked on this machine`}.`,
|
|
23630
|
+
commands: unsearched.others.map(
|
|
23631
|
+
(other) => `memgineering recall ${JSON.stringify(query)} ${unsearched.flag} ${JSON.stringify(other)}`
|
|
23632
|
+
),
|
|
23633
|
+
safe: `Safe to run \u2014 reading another brain does not change which one this ${hosted ? "machine" : "folder"} points at.`
|
|
23634
|
+
};
|
|
23635
|
+
}
|
|
23636
|
+
function nothingToSearchHint(nothing, chosenWith) {
|
|
23637
|
+
return nothing.kind === "no_notes" ? emptyBrainHint(chosenWith) : noticeText(leftOutNotice(nothing.leftOut, chosenWith, "all"));
|
|
23638
|
+
}
|
|
23639
|
+
function folderFlags(chosenWith) {
|
|
23640
|
+
return (chosenWith.local === true ? " --local" : "") + (chosenWith.vault === void 0 ? "" : ` --vault ${JSON.stringify(chosenWith.vault)}`);
|
|
23641
|
+
}
|
|
23642
|
+
function emptyBrainHint(chosenWith) {
|
|
23643
|
+
if (chosenWith.brain !== void 0) {
|
|
23644
|
+
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.`;
|
|
23645
|
+
}
|
|
23646
|
+
return `This brain has no notes yet; \`memgineering remember "<what to keep>" --reason "<why>"${folderFlags(chosenWith)}\` writes the first one.`;
|
|
23647
|
+
}
|
|
23648
|
+
function leftOutNotice(leftOut, chosenWith, share) {
|
|
23649
|
+
const notes = (n) => `${n} note${n === 1 ? "" : "s"}`;
|
|
23650
|
+
const parts = [
|
|
23651
|
+
leftOut.byRule > 0 ? `${notes(leftOut.byRule)} excluded by your rules` : null,
|
|
23652
|
+
leftOut.listed > 0 ? `${notes(leftOut.listed)} listed in .memgdeny` : null,
|
|
23653
|
+
leftOut.byName > 0 ? `${notes(leftOut.byName)} held back because the name looks private` : null,
|
|
23654
|
+
leftOut.byContent > 0 ? `${notes(leftOut.byContent)} refused because the content looks like a credential` : null,
|
|
23655
|
+
leftOut.symlinked > 0 ? `${notes(leftOut.symlinked)} behind a symlink, which is never followed` : null,
|
|
23656
|
+
leftOut.otherSymlinks > 0 ? `${leftOut.otherSymlinks} other symlink${leftOut.otherSymlinks === 1 ? "" : "s"}, never followed, so a folder behind one was not looked inside` : null,
|
|
23657
|
+
leftOut.folders > 0 ? `${leftOut.folders} excluded folder${leftOut.folders === 1 ? "" : "s"}, not looked inside` : null
|
|
23658
|
+
].filter((part) => part !== null);
|
|
23659
|
+
return {
|
|
23660
|
+
headline: `${share === "all" ? "Nothing in this brain could be searched" : "Not every note in this brain was searched"}: ${parts.join(", ")}.`,
|
|
23661
|
+
lines: [`\`memgineering reindex${folderFlags(chosenWith)}\` names each one and why.`]
|
|
23662
|
+
};
|
|
23663
|
+
}
|
|
23664
|
+
function notesIn(leftOut) {
|
|
23665
|
+
return leftOut.byRule + leftOut.listed + leftOut.byName + leftOut.byContent + leftOut.symlinked;
|
|
23666
|
+
}
|
|
23667
|
+
function leftOutOf(held, denied) {
|
|
23668
|
+
const refused = (reason) => denied.filter((d) => d.reason === reason).length;
|
|
23669
|
+
const leftOut = {
|
|
23670
|
+
byRule: held.byRule.filter((path) => !isExcluded(path, false)).length + refused("excluded"),
|
|
23671
|
+
listed: held.explicitly.length + refused("explicit"),
|
|
23672
|
+
byName: held.byName.length + refused("name"),
|
|
23673
|
+
byContent: refused("content"),
|
|
23674
|
+
symlinked: held.symlinked.length,
|
|
23675
|
+
otherSymlinks: held.otherSymlinks.filter((path) => !isExcluded(path, false)).length,
|
|
23676
|
+
folders: held.folders.filter((dir) => !isExcluded(dir, true)).length
|
|
23677
|
+
};
|
|
23678
|
+
return Object.values(leftOut).some((n) => n > 0) ? leftOut : null;
|
|
23679
|
+
}
|
|
23260
23680
|
function unreachableScript(query, entries) {
|
|
23261
23681
|
const queryScripts = scriptsIn(query);
|
|
23262
23682
|
if (queryScripts.size === 0) return null;
|
|
@@ -23726,16 +24146,16 @@ init_next_step();
|
|
|
23726
24146
|
init_provenance();
|
|
23727
24147
|
import { spawn as spawn4 } from "child_process";
|
|
23728
24148
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23729
|
-
import { mkdir as mkdir19, open, readFile as
|
|
23730
|
-
import { join as
|
|
24149
|
+
import { mkdir as mkdir19, open, readFile as readFile29, rename as rename5, rm as rm8, stat as stat9, unlink as unlink6, writeFile as writeFile16 } from "fs/promises";
|
|
24150
|
+
import { join as join31 } from "path";
|
|
23731
24151
|
import { Command as Command41 } from "commander";
|
|
23732
24152
|
|
|
23733
24153
|
// src/lib/update-check.ts
|
|
23734
24154
|
init_atomic();
|
|
23735
24155
|
init_brand();
|
|
23736
24156
|
import { execFile as execFile2 } from "child_process";
|
|
23737
|
-
import { mkdir as mkdir18, readFile as
|
|
23738
|
-
import { join as
|
|
24157
|
+
import { mkdir as mkdir18, readFile as readFile28, writeFile as writeFile15 } from "fs/promises";
|
|
24158
|
+
import { join as join30 } from "path";
|
|
23739
24159
|
import { promisify as promisify2 } from "util";
|
|
23740
24160
|
import semver4 from "semver";
|
|
23741
24161
|
import { z as z33 } from "zod";
|
|
@@ -23816,11 +24236,11 @@ var UpdateStateSchema = z33.object({
|
|
|
23816
24236
|
message: z33.string().nullable()
|
|
23817
24237
|
});
|
|
23818
24238
|
function statePath() {
|
|
23819
|
-
return
|
|
24239
|
+
return join30(brandHome(), "update-state.json");
|
|
23820
24240
|
}
|
|
23821
24241
|
async function readUpdateState() {
|
|
23822
24242
|
try {
|
|
23823
|
-
const parsed = UpdateStateSchema.safeParse(JSON.parse(await
|
|
24243
|
+
const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile28(statePath(), "utf8")));
|
|
23824
24244
|
return parsed.success ? parsed.data : null;
|
|
23825
24245
|
} catch {
|
|
23826
24246
|
return null;
|
|
@@ -23940,7 +24360,7 @@ async function runUpdate(opts, inherited) {
|
|
|
23940
24360
|
}
|
|
23941
24361
|
const held = await holdUpdateSlot(inherited);
|
|
23942
24362
|
if (held === null && await blockedBy() === "stuck") {
|
|
23943
|
-
const lock =
|
|
24363
|
+
const lock = join31(brandHome(), RECLAIM_LOCK);
|
|
23944
24364
|
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
24365
|
printDual({
|
|
23946
24366
|
json: { ...status, provenance: provenance.kind, updated: false, reason: "update_claim_stuck", hint },
|
|
@@ -24055,14 +24475,14 @@ function newClaimToken() {
|
|
|
24055
24475
|
}
|
|
24056
24476
|
async function claimOwner() {
|
|
24057
24477
|
try {
|
|
24058
|
-
return (await
|
|
24478
|
+
return (await readFile29(join31(brandHome(), "update-running"), "utf8")).trim() || null;
|
|
24059
24479
|
} catch {
|
|
24060
24480
|
return null;
|
|
24061
24481
|
}
|
|
24062
24482
|
}
|
|
24063
24483
|
async function releaseUpdateSlot(token) {
|
|
24064
24484
|
if (await claimOwner() !== token) return;
|
|
24065
|
-
await unlink6(
|
|
24485
|
+
await unlink6(join31(brandHome(), "update-running")).catch(() => void 0);
|
|
24066
24486
|
}
|
|
24067
24487
|
async function holdUpdateSlot(inherited) {
|
|
24068
24488
|
if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
|
|
@@ -24074,7 +24494,7 @@ async function holdUpdateSlot(inherited) {
|
|
|
24074
24494
|
var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
|
|
24075
24495
|
var UPDATE_CLAIM_MS = 15 * 6e4;
|
|
24076
24496
|
async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
|
|
24077
|
-
const path =
|
|
24497
|
+
const path = join31(brandHome(), "update-running");
|
|
24078
24498
|
try {
|
|
24079
24499
|
await mkdir19(brandHome(), { recursive: true });
|
|
24080
24500
|
const handle = await open(path, "wx");
|
|
@@ -24102,10 +24522,10 @@ var RECLAIM_LOCK = "update-running.reclaim";
|
|
|
24102
24522
|
var RECLAIM_LOCK_MAX_MS = 10 * 6e4;
|
|
24103
24523
|
async function withReclaimLock(fn) {
|
|
24104
24524
|
const home = brandHome();
|
|
24105
|
-
const lock =
|
|
24106
|
-
const staging =
|
|
24525
|
+
const lock = join31(home, RECLAIM_LOCK);
|
|
24526
|
+
const staging = join31(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
|
|
24107
24527
|
await mkdir19(staging, { recursive: true });
|
|
24108
|
-
await
|
|
24528
|
+
await writeFile16(join31(staging, "owner"), `${process.pid} ${Date.now()}
|
|
24109
24529
|
`);
|
|
24110
24530
|
try {
|
|
24111
24531
|
await rename5(staging, lock);
|
|
@@ -24123,11 +24543,11 @@ async function withReclaimLock(fn) {
|
|
|
24123
24543
|
}
|
|
24124
24544
|
async function breakAbandonedLock(lock) {
|
|
24125
24545
|
try {
|
|
24126
|
-
const seen = (await
|
|
24546
|
+
const seen = (await readFile29(join31(lock, "owner"), "utf8")).trim();
|
|
24127
24547
|
if (!lockAbandoned(seen)) return;
|
|
24128
24548
|
const aside = `${lock}.dead.${randomBytes2(4).toString("hex")}`;
|
|
24129
24549
|
await rename5(lock, aside);
|
|
24130
|
-
const moved = (await
|
|
24550
|
+
const moved = (await readFile29(join31(aside, "owner"), "utf8").catch(() => "")).trim();
|
|
24131
24551
|
if (moved !== seen) {
|
|
24132
24552
|
await rename5(aside, lock).catch(() => void 0);
|
|
24133
24553
|
return;
|
|
@@ -24151,9 +24571,9 @@ function lockAbandoned(owner, now = Date.now()) {
|
|
|
24151
24571
|
}
|
|
24152
24572
|
async function blockedBy() {
|
|
24153
24573
|
try {
|
|
24154
|
-
const old = Date.now() - (await stat9(
|
|
24574
|
+
const old = Date.now() - (await stat9(join31(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
|
|
24155
24575
|
if (old) {
|
|
24156
|
-
await stat9(
|
|
24576
|
+
await stat9(join31(brandHome(), RECLAIM_LOCK));
|
|
24157
24577
|
return "stuck";
|
|
24158
24578
|
}
|
|
24159
24579
|
} catch {
|
|
@@ -24185,8 +24605,8 @@ function reportBelowMinimum(below) {
|
|
|
24185
24605
|
|
|
24186
24606
|
// src/commands/undo.ts
|
|
24187
24607
|
init_src();
|
|
24188
|
-
import { readFile as
|
|
24189
|
-
import { join as
|
|
24608
|
+
import { readFile as readFile30, rm as rm9 } from "fs/promises";
|
|
24609
|
+
import { join as join32 } from "path";
|
|
24190
24610
|
import { Command as Command42 } from "commander";
|
|
24191
24611
|
init_atomic();
|
|
24192
24612
|
init_errors();
|
|
@@ -24283,8 +24703,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
24283
24703
|
);
|
|
24284
24704
|
}
|
|
24285
24705
|
const record = await withWriteLock(brain.root, async () => {
|
|
24286
|
-
const absolute =
|
|
24287
|
-
const current = await
|
|
24706
|
+
const absolute = join32(brain.root, target.target);
|
|
24707
|
+
const current = await readFile30(absolute, "utf8").catch(() => null);
|
|
24288
24708
|
const actualHash = current === null ? null : hashContent(current);
|
|
24289
24709
|
if (actualHash !== target.hash_after) {
|
|
24290
24710
|
throw memgError(
|
|
@@ -24346,7 +24766,7 @@ function minutesSince(iso) {
|
|
|
24346
24766
|
return Math.max(0, Math.floor((Date.now() - at) / 6e4));
|
|
24347
24767
|
}
|
|
24348
24768
|
async function reverse(brain, op, current) {
|
|
24349
|
-
const absolute =
|
|
24769
|
+
const absolute = join32(brain.root, op.target);
|
|
24350
24770
|
if (op.verb === "remember") {
|
|
24351
24771
|
if (current === null) {
|
|
24352
24772
|
throw memgError(
|
|
@@ -24403,7 +24823,7 @@ async function conflictHint(vaultRoot, op) {
|
|
|
24403
24823
|
Read what it says now, then decide what should stay.`;
|
|
24404
24824
|
const snapshot = await readSnapshot(vaultRoot, op.op_id);
|
|
24405
24825
|
return snapshot === null ? base2 : `${base2}
|
|
24406
|
-
The text from before that change is still on this machine: ${
|
|
24826
|
+
The text from before that change is still on this machine: ${join32(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
24407
24827
|
}
|
|
24408
24828
|
|
|
24409
24829
|
// src/commands/brains.ts
|