@hraness/message-like-me 0.8.5 → 0.8.6
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 +16 -0
- package/README.md +1 -1
- package/dist/cli.js +548 -132
- package/docs/command-runtime.md +42 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.6 (2026-09-07)
|
|
4
|
+
|
|
5
|
+
- Make the exact public `@hraness/message-like-me@0.8.6` npm package the
|
|
6
|
+
supported default install, with the same reviewed bytes mirrored in the
|
|
7
|
+
immutable GitHub Release.
|
|
8
|
+
- Give the paired Message Like Me and Ensoul skill installer one command-owned
|
|
9
|
+
lifecycle through copying, backup, publication and sequential recovery.
|
|
10
|
+
- Bound physical inventories and verify copied bytes before publication. Retain
|
|
11
|
+
unknown or substituted entries during cleanup, and preserve missing project
|
|
12
|
+
directory creation beneath a verified physical ancestor.
|
|
13
|
+
- Keep both installed skills after publication succeeds, even when backup cleanup
|
|
14
|
+
or output confirmation fails. Report incomplete cleanup with fixed diagnostics
|
|
15
|
+
that omit private data while preserving the original failure.
|
|
16
|
+
- Strengthen the development Effect checker with generator alias and wrapper
|
|
17
|
+
regressions. Public protocol graphs and local-only behavior remain unchanged.
|
|
18
|
+
|
|
3
19
|
## 0.8.5 (2026-09-06)
|
|
4
20
|
|
|
5
21
|
- Make the exact public `@hraness/message-like-me@0.8.5` npm package the
|
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Message Like Me requires Bun 1.3.14 or newer. Install the exact public npm
|
|
|
43
43
|
package, then install both bundled Agent Skills:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
|
-
bun add --global @hraness/message-like-me@0.8.
|
|
46
|
+
bun add --global @hraness/message-like-me@0.8.6
|
|
47
47
|
messagelikeme skill install
|
|
48
48
|
```
|
|
49
49
|
|
package/dist/cli.js
CHANGED
|
@@ -19637,117 +19637,527 @@ async function readStablePrivateJson(path, label, maximumBytes) {
|
|
|
19637
19637
|
}
|
|
19638
19638
|
|
|
19639
19639
|
// src/skill-install.ts
|
|
19640
|
-
import {
|
|
19641
|
-
import { cp, lstat as lstat4, mkdir as mkdir2, realpath as realpath3, rename, rm } from "fs/promises";
|
|
19642
|
-
import { homedir as homedir4 } from "os";
|
|
19643
|
-
import { dirname as dirname3, join as join7, resolve as resolve5 } from "path";
|
|
19640
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
19644
19641
|
import { fileURLToPath } from "url";
|
|
19645
|
-
async function exists5(path) {
|
|
19646
|
-
try {
|
|
19647
|
-
await lstat4(path);
|
|
19648
|
-
return true;
|
|
19649
|
-
} catch (error) {
|
|
19650
|
-
if (error.code === "ENOENT")
|
|
19651
|
-
return false;
|
|
19652
|
-
throw error;
|
|
19653
|
-
}
|
|
19654
|
-
}
|
|
19655
19642
|
function bundledSkillPath() {
|
|
19656
19643
|
return resolve5(dirname3(fileURLToPath(import.meta.url)), "../skills/message-like-me");
|
|
19657
19644
|
}
|
|
19658
19645
|
function bundledEnsoulSkillPath() {
|
|
19659
19646
|
return resolve5(dirname3(fileURLToPath(import.meta.url)), "../skills/ensoul");
|
|
19660
19647
|
}
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19648
|
+
|
|
19649
|
+
// src/skill-install-model.ts
|
|
19650
|
+
var SKILL_INSTALL_PAIR = ["message-like-me", "ensoul"];
|
|
19651
|
+
function skillInstallWarning(outcome) {
|
|
19652
|
+
if (outcome.residuals.length === 0 && !(outcome.committed && outcome.operation._tag === "Failure"))
|
|
19653
|
+
return null;
|
|
19654
|
+
const state = outcome.committed ? "committed" : "not committed";
|
|
19655
|
+
const details = outcome.residuals.map(({ skill, phase }) => `${skill}:${phase}`).join(", ");
|
|
19656
|
+
return `Skill installation ${state}; ${details.length > 0 ? `cleanup incomplete (${details})` : "output confirmation failed"}. Retained installation files need review.
|
|
19657
|
+
`;
|
|
19664
19658
|
}
|
|
19665
|
-
|
|
19666
|
-
|
|
19667
|
-
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
|
|
19659
|
+
|
|
19660
|
+
// src/skill-install-program.ts
|
|
19661
|
+
class SkillInstallPlatform extends exports_Context.Tag("@hraness/message-like-me/SkillInstallPlatform")() {
|
|
19662
|
+
}
|
|
19663
|
+
function installSkillProgram(options) {
|
|
19664
|
+
return exports_Effect.uninterruptible(exports_Effect.gen(function* () {
|
|
19665
|
+
const platform2 = yield* SkillInstallPlatform;
|
|
19666
|
+
const prepared = yield* exports_Effect.exit(platform2.preflight(options).pipe(exports_Effect.flatMap((plan) => platform2.transaction(plan))));
|
|
19667
|
+
if (exports_Exit.isFailure(prepared))
|
|
19668
|
+
return { operation: exports_Exit.failCause(prepared.cause), committed: false, residuals: [] };
|
|
19669
|
+
const transaction = prepared.value;
|
|
19670
|
+
let committed = false;
|
|
19671
|
+
const operation = yield* exports_Effect.exit(exports_Effect.gen(function* () {
|
|
19672
|
+
yield* platform2.acquire(transaction);
|
|
19673
|
+
for (const skill of SKILL_INSTALL_PAIR)
|
|
19674
|
+
yield* platform2.copy(transaction, skill);
|
|
19675
|
+
for (const skill of SKILL_INSTALL_PAIR)
|
|
19676
|
+
yield* platform2.backup(transaction, skill);
|
|
19677
|
+
for (const skill of SKILL_INSTALL_PAIR)
|
|
19678
|
+
yield* platform2.publish(transaction, skill);
|
|
19679
|
+
committed = true;
|
|
19680
|
+
}));
|
|
19681
|
+
const residuals = [];
|
|
19682
|
+
const cleanup = (skill, phase) => exports_Effect.gen(function* () {
|
|
19683
|
+
const result = yield* exports_Effect.exit(platform2.cleanup(transaction, skill, phase));
|
|
19684
|
+
if (exports_Exit.isFailure(result))
|
|
19685
|
+
residuals.push({ skill, phase, cause: result.cause });
|
|
19686
|
+
});
|
|
19687
|
+
if (!committed) {
|
|
19688
|
+
for (const skill of [...SKILL_INSTALL_PAIR].reverse()) {
|
|
19689
|
+
yield* cleanup(skill, "rollback-published");
|
|
19690
|
+
yield* cleanup(skill, "restore-backup");
|
|
19691
|
+
yield* cleanup(skill, "stage-cleanup");
|
|
19692
|
+
}
|
|
19693
|
+
} else {
|
|
19694
|
+
for (const skill of SKILL_INSTALL_PAIR)
|
|
19695
|
+
yield* cleanup(skill, "backup-cleanup");
|
|
19673
19696
|
}
|
|
19674
|
-
const
|
|
19675
|
-
|
|
19676
|
-
|
|
19697
|
+
const output = exports_Exit.isFailure(operation) ? exports_Exit.failCause(operation.cause) : yield* exports_Effect.exit(platform2.destinations(transaction));
|
|
19698
|
+
yield* cleanup("pair", "transaction-cleanup");
|
|
19699
|
+
yield* cleanup("pair", "lock-cleanup");
|
|
19700
|
+
return { operation: output, committed, residuals };
|
|
19701
|
+
}));
|
|
19702
|
+
}
|
|
19703
|
+
|
|
19704
|
+
// src/skill-install-platform.ts
|
|
19705
|
+
import { createHash as createHash5, randomBytes as randomBytes2 } from "crypto";
|
|
19706
|
+
import { constants as constants2 } from "fs";
|
|
19707
|
+
import * as fs from "fs/promises";
|
|
19708
|
+
import { homedir as homedir4 } from "os";
|
|
19709
|
+
import { basename as basename4, dirname as dirname4, join as join7, relative, resolve as resolve6, sep } from "path";
|
|
19710
|
+
var MAX_ENTRIES = 64;
|
|
19711
|
+
var MAX_FILE_BYTES = 256 * 1024;
|
|
19712
|
+
var MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
19713
|
+
var MAX_DEPTH = 8;
|
|
19714
|
+
var plans = new WeakMap;
|
|
19715
|
+
var transactions = new WeakMap;
|
|
19716
|
+
var unsafe = () => new CliError("unsafe-path", "Skill installation path changed or cannot be used safely");
|
|
19717
|
+
var bounded = () => new CliError("invalid-data", "Skill directory exceeds the supported physical inventory bounds");
|
|
19718
|
+
function isMissing(error) {
|
|
19719
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
19720
|
+
}
|
|
19721
|
+
async function optionalStat(path) {
|
|
19722
|
+
try {
|
|
19723
|
+
return await fs.lstat(path, { bigint: true });
|
|
19724
|
+
} catch (error) {
|
|
19725
|
+
if (isMissing(error))
|
|
19726
|
+
return null;
|
|
19727
|
+
throw error;
|
|
19728
|
+
}
|
|
19729
|
+
}
|
|
19730
|
+
function physical(stat2) {
|
|
19731
|
+
if (typeof process.getuid !== "function" || !stat2.isDirectory() && !stat2.isFile() || stat2.uid !== BigInt(process.getuid()))
|
|
19732
|
+
throw unsafe();
|
|
19733
|
+
}
|
|
19734
|
+
function same(left3, right3) {
|
|
19735
|
+
return left3.dev === right3.dev && left3.ino === right3.ino && left3.mode === right3.mode && left3.uid === right3.uid && left3.isDirectory() === right3.isDirectory() && (left3.isDirectory() || left3.size === right3.size && left3.nlink === right3.nlink && left3.mtimeNs === right3.mtimeNs && left3.ctimeNs === right3.ctimeNs);
|
|
19736
|
+
}
|
|
19737
|
+
async function assertIdentity(path, expected) {
|
|
19738
|
+
const actual = await optionalStat(path);
|
|
19739
|
+
if (actual === null || !same(actual, expected))
|
|
19740
|
+
throw unsafe();
|
|
19741
|
+
}
|
|
19742
|
+
function contains5(parent, child) {
|
|
19743
|
+
const path = relative(parent, child);
|
|
19744
|
+
return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !path.startsWith(sep);
|
|
19745
|
+
}
|
|
19746
|
+
|
|
19747
|
+
class ReadCloseFailure {
|
|
19748
|
+
primary;
|
|
19749
|
+
cleanup;
|
|
19750
|
+
constructor(primary, cleanup) {
|
|
19751
|
+
this.primary = primary;
|
|
19752
|
+
this.cleanup = cleanup;
|
|
19753
|
+
}
|
|
19754
|
+
}
|
|
19755
|
+
function nativeFailure(error) {
|
|
19756
|
+
const cleanup = [];
|
|
19757
|
+
while (error instanceof ReadCloseFailure) {
|
|
19758
|
+
cleanup.push(error.cleanup);
|
|
19759
|
+
error = error.primary;
|
|
19760
|
+
}
|
|
19761
|
+
return cleanup.length === 0 ? commandFailure(error) : { ...commandFailure(error), nativeCleanupCauses: Object.freeze(cleanup.reverse()) };
|
|
19762
|
+
}
|
|
19763
|
+
async function digestFile(path, expected) {
|
|
19764
|
+
if (expected.size > BigInt(MAX_FILE_BYTES) || expected.size < 0n)
|
|
19765
|
+
throw bounded();
|
|
19766
|
+
const handle = await fs.open(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
19767
|
+
let result;
|
|
19768
|
+
try {
|
|
19769
|
+
if (!same(await handle.stat({ bigint: true }), expected))
|
|
19770
|
+
throw unsafe();
|
|
19771
|
+
const hash2 = createHash5("sha256");
|
|
19772
|
+
const buffer = Buffer.alloc(16 * 1024);
|
|
19773
|
+
let count = 0;
|
|
19774
|
+
for (;; ) {
|
|
19775
|
+
const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, Number(expected.size) + 1 - count), count);
|
|
19776
|
+
count += bytesRead;
|
|
19777
|
+
if (count > Number(expected.size))
|
|
19778
|
+
throw unsafe();
|
|
19779
|
+
if (bytesRead === 0)
|
|
19780
|
+
break;
|
|
19781
|
+
hash2.update(buffer.subarray(0, bytesRead));
|
|
19677
19782
|
}
|
|
19783
|
+
if (count !== Number(expected.size) || !same(await handle.stat({ bigint: true }), expected))
|
|
19784
|
+
throw unsafe();
|
|
19785
|
+
result = { ok: true, value: hash2.digest("hex") };
|
|
19786
|
+
} catch (error) {
|
|
19787
|
+
result = { ok: false, error };
|
|
19678
19788
|
}
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19789
|
+
try {
|
|
19790
|
+
await handle.close();
|
|
19791
|
+
} catch (error) {
|
|
19792
|
+
if (!result.ok)
|
|
19793
|
+
throw new ReadCloseFailure(result.error, error);
|
|
19794
|
+
throw error;
|
|
19795
|
+
}
|
|
19796
|
+
if (!result.ok)
|
|
19797
|
+
throw result.error;
|
|
19798
|
+
return result.value;
|
|
19799
|
+
}
|
|
19800
|
+
async function inventory(root) {
|
|
19801
|
+
const entries2 = [];
|
|
19802
|
+
let bytes = 0n;
|
|
19803
|
+
const visit = async (name, depth) => {
|
|
19804
|
+
if (depth > MAX_DEPTH || name.length > 4096 || entries2.length >= MAX_ENTRIES)
|
|
19805
|
+
throw bounded();
|
|
19806
|
+
const path = join7(root, name);
|
|
19807
|
+
const stat2 = await fs.lstat(path, { bigint: true });
|
|
19808
|
+
physical(stat2);
|
|
19809
|
+
if (stat2.isFile()) {
|
|
19810
|
+
bytes += stat2.size;
|
|
19811
|
+
if (bytes > BigInt(MAX_TOTAL_BYTES))
|
|
19812
|
+
throw bounded();
|
|
19813
|
+
entries2.push({ name, stat: stat2, digest: await digestFile(path, stat2) });
|
|
19814
|
+
} else {
|
|
19815
|
+
entries2.push({ name, stat: stat2 });
|
|
19816
|
+
const directory = await fs.opendir(path);
|
|
19817
|
+
let read = { ok: true };
|
|
19818
|
+
try {
|
|
19819
|
+
for (;; ) {
|
|
19820
|
+
const child = await directory.read();
|
|
19821
|
+
if (child === null)
|
|
19822
|
+
break;
|
|
19823
|
+
await visit(join7(name, child.name), depth + 1);
|
|
19824
|
+
}
|
|
19825
|
+
} catch (error) {
|
|
19826
|
+
read = { ok: false, error };
|
|
19690
19827
|
}
|
|
19691
|
-
|
|
19692
|
-
|
|
19828
|
+
try {
|
|
19829
|
+
await directory.close();
|
|
19830
|
+
} catch (error) {
|
|
19831
|
+
if (!read.ok)
|
|
19832
|
+
throw new ReadCloseFailure(read.error, error);
|
|
19833
|
+
throw error;
|
|
19693
19834
|
}
|
|
19835
|
+
if (!read.ok)
|
|
19836
|
+
throw read.error;
|
|
19837
|
+
await assertIdentity(path, stat2);
|
|
19694
19838
|
}
|
|
19695
|
-
}
|
|
19696
|
-
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
19703
|
-
|
|
19704
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
19839
|
+
};
|
|
19840
|
+
await visit("", 0);
|
|
19841
|
+
if (!entries2[0]?.stat.isDirectory())
|
|
19842
|
+
throw unsafe();
|
|
19843
|
+
return entries2;
|
|
19844
|
+
}
|
|
19845
|
+
function sameContents(left3, right3) {
|
|
19846
|
+
const byName = new Map(right3.map((entry) => [entry.name, entry]));
|
|
19847
|
+
return left3.length === right3.length && left3.every((entry) => {
|
|
19848
|
+
const other = byName.get(entry.name);
|
|
19849
|
+
return other !== undefined && entry.stat.isDirectory() === other.stat.isDirectory() && entry.stat.size === (entry.stat.isFile() ? other.stat.size : entry.stat.size) && entry.digest === other.digest;
|
|
19850
|
+
});
|
|
19851
|
+
}
|
|
19852
|
+
async function assertInventory(root, expected) {
|
|
19853
|
+
const actual = await inventory(root);
|
|
19854
|
+
if (!sameContents(expected, actual))
|
|
19855
|
+
throw unsafe();
|
|
19856
|
+
const byName = new Map(actual.map((entry) => [entry.name, entry.stat]));
|
|
19857
|
+
if (!expected.every((entry) => {
|
|
19858
|
+
const value = byName.get(entry.name);
|
|
19859
|
+
return value !== undefined && same(entry.stat, value);
|
|
19860
|
+
}))
|
|
19861
|
+
throw unsafe();
|
|
19862
|
+
}
|
|
19863
|
+
async function parents(plan) {
|
|
19864
|
+
for (const parent of plan.parents)
|
|
19865
|
+
await assertIdentity(parent.path, parent.stat);
|
|
19866
|
+
}
|
|
19867
|
+
function state(token3) {
|
|
19868
|
+
const value = transactions.get(token3);
|
|
19869
|
+
if (value === undefined)
|
|
19870
|
+
throw unsafe();
|
|
19871
|
+
return value;
|
|
19872
|
+
}
|
|
19873
|
+
async function custody(transaction) {
|
|
19874
|
+
await parents(transaction.plan);
|
|
19875
|
+
if (transaction.lockStat !== undefined)
|
|
19876
|
+
await assertIdentity(transaction.lock, transaction.lockStat);
|
|
19877
|
+
if (transaction.directoryStat !== undefined)
|
|
19878
|
+
await assertIdentity(transaction.directory, transaction.directoryStat);
|
|
19879
|
+
}
|
|
19880
|
+
async function absent(path) {
|
|
19881
|
+
if (await optionalStat(path) !== null)
|
|
19882
|
+
throw unsafe();
|
|
19883
|
+
}
|
|
19884
|
+
async function projectAnchor(requested) {
|
|
19885
|
+
let cursor = resolve6(requested);
|
|
19886
|
+
if (Buffer.byteLength(cursor, "utf8") > 4096)
|
|
19887
|
+
throw unsafe();
|
|
19888
|
+
const suffix = [];
|
|
19889
|
+
for (;; ) {
|
|
19890
|
+
let existing;
|
|
19891
|
+
try {
|
|
19892
|
+
existing = await fs.realpath(cursor);
|
|
19893
|
+
} catch (error) {
|
|
19894
|
+
if (!isMissing(error))
|
|
19895
|
+
throw error;
|
|
19896
|
+
if (await optionalStat(cursor) !== null || suffix.length >= 64)
|
|
19897
|
+
throw unsafe();
|
|
19898
|
+
const parent = dirname4(cursor);
|
|
19899
|
+
if (parent === cursor)
|
|
19900
|
+
throw error;
|
|
19901
|
+
suffix.push(basename4(cursor));
|
|
19902
|
+
cursor = parent;
|
|
19903
|
+
continue;
|
|
19707
19904
|
}
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19905
|
+
const stat2 = await fs.lstat(existing, { bigint: true });
|
|
19906
|
+
physical(stat2);
|
|
19907
|
+
if (!stat2.isDirectory())
|
|
19908
|
+
throw unsafe();
|
|
19909
|
+
const missing = [];
|
|
19910
|
+
let anchor = existing;
|
|
19911
|
+
for (const name of suffix.reverse()) {
|
|
19912
|
+
anchor = join7(anchor, name);
|
|
19913
|
+
missing.push(anchor);
|
|
19914
|
+
}
|
|
19915
|
+
return { anchor, parents: [{ path: existing, stat: stat2 }], missing };
|
|
19916
|
+
}
|
|
19917
|
+
}
|
|
19918
|
+
async function preflight(options) {
|
|
19919
|
+
const { anchor, parents: observed, missing } = await projectAnchor(options.scope === "user" ? homedir4() : options.projectDirectory ?? process.cwd());
|
|
19920
|
+
const folder = options.target === "agents" ? ".agents" : options.target === "codex" ? ".codex" : ".claude";
|
|
19921
|
+
const root = join7(anchor, folder, "skills");
|
|
19922
|
+
for (const path of [join7(anchor, folder), root]) {
|
|
19923
|
+
const stat2 = await optionalStat(path);
|
|
19924
|
+
if (stat2 === null)
|
|
19925
|
+
missing.push(path);
|
|
19926
|
+
else {
|
|
19927
|
+
physical(stat2);
|
|
19928
|
+
if (!stat2.isDirectory())
|
|
19929
|
+
throw unsafe();
|
|
19930
|
+
observed.push({ path, stat: stat2 });
|
|
19931
|
+
}
|
|
19932
|
+
}
|
|
19933
|
+
const items = {};
|
|
19934
|
+
for (const name of SKILL_INSTALL_PAIR) {
|
|
19935
|
+
const sourcePath = name === "message-like-me" ? bundledSkillPath() : bundledEnsoulSkillPath();
|
|
19936
|
+
const sourceStat = await optionalStat(sourcePath);
|
|
19937
|
+
if (sourceStat === null)
|
|
19938
|
+
throw new CliError("not-found", `Bundled ${name} skill is missing at ${sourcePath}`);
|
|
19939
|
+
physical(sourceStat);
|
|
19940
|
+
if (!sourceStat.isDirectory())
|
|
19941
|
+
throw unsafe();
|
|
19942
|
+
const source = await fs.realpath(sourcePath);
|
|
19943
|
+
if (contains5(source, root) || contains5(root, source))
|
|
19944
|
+
throw unsafe();
|
|
19945
|
+
const sourceInventory = await inventory(source);
|
|
19946
|
+
const destination = join7(root, name);
|
|
19947
|
+
const current = await optionalStat(destination);
|
|
19948
|
+
if (current !== null) {
|
|
19949
|
+
physical(current);
|
|
19950
|
+
if (!current.isDirectory())
|
|
19951
|
+
throw new CliError("unsafe-path", `Refusing to replace non-directory ${destination}`);
|
|
19952
|
+
if (!options.force)
|
|
19953
|
+
throw new CliError("conflict", `Skill already exists at ${destination}; pass --force to replace both bundled skills`);
|
|
19954
|
+
}
|
|
19955
|
+
items[name] = { source, destination, sourceInventory, original: current === null ? null : await inventory(destination) };
|
|
19956
|
+
}
|
|
19957
|
+
return { parents: observed, missing, root, items };
|
|
19958
|
+
}
|
|
19959
|
+
async function removeObserved(transaction, root, entries2) {
|
|
19960
|
+
for (const entry of [...entries2].reverse()) {
|
|
19961
|
+
await custody(transaction);
|
|
19962
|
+
for (const parent of entries2) {
|
|
19963
|
+
if (parent.stat.isDirectory() && contains5(join7(root, parent.name), join7(root, entry.name))) {
|
|
19964
|
+
await assertIdentity(join7(root, parent.name), parent.stat);
|
|
19965
|
+
}
|
|
19966
|
+
}
|
|
19967
|
+
const path = join7(root, entry.name);
|
|
19968
|
+
await assertIdentity(path, entry.stat);
|
|
19969
|
+
if (entry.stat.isDirectory())
|
|
19970
|
+
await fs.rmdir(path);
|
|
19971
|
+
else
|
|
19972
|
+
await fs.unlink(path);
|
|
19973
|
+
}
|
|
19974
|
+
}
|
|
19975
|
+
var foreign = (operation) => exports_Effect.uninterruptible(exports_Effect.tryPromise({ try: operation, catch: nativeFailure }));
|
|
19976
|
+
var skillInstallPlatform = {
|
|
19977
|
+
preflight: (options) => foreign(async () => {
|
|
19978
|
+
const plan = await preflight(options);
|
|
19979
|
+
const token3 = Object.freeze({ _tag: "SkillInstallPlan" });
|
|
19980
|
+
plans.set(token3, plan);
|
|
19981
|
+
return token3;
|
|
19982
|
+
}),
|
|
19983
|
+
transaction: (token3) => exports_Effect.try({ try: () => {
|
|
19984
|
+
const plan = plans.get(token3);
|
|
19985
|
+
if (plan === undefined)
|
|
19986
|
+
throw unsafe();
|
|
19987
|
+
const directory = join7(plan.root, `.skill-install.${randomBytes2(16).toString("hex")}`);
|
|
19988
|
+
const itemState = (name) => ({ stage: join7(directory, `${name}.stage`), backup: join7(directory, `${name}.backup`), staged: [], backupAttempted: false, publishAttempted: false });
|
|
19989
|
+
const transaction = Object.freeze({ _tag: "SkillInstallTransaction" });
|
|
19990
|
+
transactions.set(transaction, {
|
|
19991
|
+
plan,
|
|
19992
|
+
lock: join7(plan.root, ".message-like-me.install-lock"),
|
|
19993
|
+
lockAttempted: false,
|
|
19994
|
+
directory,
|
|
19995
|
+
directoryAttempted: false,
|
|
19996
|
+
items: { "message-like-me": itemState("message-like-me"), ensoul: itemState("ensoul") }
|
|
19997
|
+
});
|
|
19998
|
+
return transaction;
|
|
19999
|
+
}, catch: commandFailure }),
|
|
20000
|
+
acquire: (token3) => foreign(async () => {
|
|
20001
|
+
const transaction = state(token3);
|
|
20002
|
+
await parents(transaction.plan);
|
|
20003
|
+
for (const path of transaction.plan.missing) {
|
|
20004
|
+
await parents(transaction.plan);
|
|
20005
|
+
await fs.mkdir(path, { mode: 448 });
|
|
20006
|
+
const stat2 = await fs.lstat(path, { bigint: true });
|
|
20007
|
+
physical(stat2);
|
|
20008
|
+
if (!stat2.isDirectory())
|
|
20009
|
+
throw unsafe();
|
|
20010
|
+
transaction.plan.parents.push({ path, stat: stat2 });
|
|
20011
|
+
}
|
|
20012
|
+
await parents(transaction.plan);
|
|
20013
|
+
transaction.lockAttempted = true;
|
|
20014
|
+
await fs.mkdir(transaction.lock, { mode: 448 });
|
|
20015
|
+
transaction.lockStat = await fs.lstat(transaction.lock, { bigint: true });
|
|
20016
|
+
physical(transaction.lockStat);
|
|
20017
|
+
if (!transaction.lockStat.isDirectory())
|
|
20018
|
+
throw unsafe();
|
|
20019
|
+
await custody(transaction);
|
|
20020
|
+
transaction.directoryAttempted = true;
|
|
20021
|
+
await fs.mkdir(transaction.directory, { mode: 448 });
|
|
20022
|
+
transaction.directoryStat = await fs.lstat(transaction.directory, { bigint: true });
|
|
20023
|
+
physical(transaction.directoryStat);
|
|
20024
|
+
if (!transaction.directoryStat.isDirectory())
|
|
20025
|
+
throw unsafe();
|
|
20026
|
+
}),
|
|
20027
|
+
copy: (token3, name) => foreign(async () => {
|
|
20028
|
+
const transaction = state(token3);
|
|
20029
|
+
const item = transaction.plan.items[name];
|
|
20030
|
+
const current = transaction.items[name];
|
|
20031
|
+
await custody(transaction);
|
|
20032
|
+
await assertInventory(item.source, item.sourceInventory);
|
|
20033
|
+
for (const entry of item.sourceInventory) {
|
|
20034
|
+
await custody(transaction);
|
|
20035
|
+
for (const parent of current.staged) {
|
|
20036
|
+
if (parent.stat.isDirectory() && contains5(join7(current.stage, parent.name), join7(current.stage, entry.name))) {
|
|
20037
|
+
await assertIdentity(join7(current.stage, parent.name), parent.stat);
|
|
20038
|
+
}
|
|
19712
20039
|
}
|
|
20040
|
+
const target = join7(current.stage, entry.name);
|
|
20041
|
+
await assertIdentity(join7(item.source, entry.name), entry.stat);
|
|
20042
|
+
if (entry.stat.isDirectory())
|
|
20043
|
+
await fs.mkdir(target, { mode: 448 });
|
|
20044
|
+
else
|
|
20045
|
+
await fs.cp(join7(item.source, entry.name), target, { force: false, errorOnExist: true, dereference: false });
|
|
20046
|
+
const stat2 = await fs.lstat(target, { bigint: true });
|
|
20047
|
+
physical(stat2);
|
|
20048
|
+
if (stat2.isDirectory() !== entry.stat.isDirectory())
|
|
20049
|
+
throw unsafe();
|
|
20050
|
+
current.staged.push({ name: entry.name, stat: stat2, ...entry.digest === undefined ? {} : { digest: entry.digest } });
|
|
20051
|
+
}
|
|
20052
|
+
const copied = await inventory(current.stage);
|
|
20053
|
+
if (!sameContents(item.sourceInventory, copied))
|
|
20054
|
+
throw unsafe();
|
|
20055
|
+
await assertInventory(item.source, item.sourceInventory);
|
|
20056
|
+
current.staged = [...copied];
|
|
20057
|
+
}),
|
|
20058
|
+
backup: (token3, name) => foreign(async () => {
|
|
20059
|
+
const transaction = state(token3);
|
|
20060
|
+
const item = transaction.plan.items[name];
|
|
20061
|
+
const current = transaction.items[name];
|
|
20062
|
+
await custody(transaction);
|
|
20063
|
+
await absent(current.backup);
|
|
20064
|
+
if (item.original === null) {
|
|
20065
|
+
await absent(item.destination);
|
|
20066
|
+
return;
|
|
19713
20067
|
}
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
}
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
20068
|
+
await assertInventory(item.destination, item.original);
|
|
20069
|
+
await custody(transaction);
|
|
20070
|
+
current.backupAttempted = true;
|
|
20071
|
+
await fs.rename(item.destination, current.backup);
|
|
20072
|
+
}),
|
|
20073
|
+
publish: (token3, name) => foreign(async () => {
|
|
20074
|
+
const transaction = state(token3);
|
|
20075
|
+
const item = transaction.plan.items[name];
|
|
20076
|
+
const current = transaction.items[name];
|
|
20077
|
+
await custody(transaction);
|
|
20078
|
+
await assertInventory(current.stage, current.staged);
|
|
20079
|
+
await custody(transaction);
|
|
20080
|
+
await absent(item.destination);
|
|
20081
|
+
current.publishAttempted = true;
|
|
20082
|
+
await fs.rename(current.stage, item.destination);
|
|
20083
|
+
}),
|
|
20084
|
+
cleanup: (token3, name, phase) => foreign(async () => {
|
|
20085
|
+
const transaction = state(token3);
|
|
20086
|
+
if (phase === "lock-cleanup") {
|
|
20087
|
+
if (transaction.lockStat === undefined) {
|
|
20088
|
+
if (transaction.lockAttempted) {
|
|
20089
|
+
await parents(transaction.plan);
|
|
20090
|
+
await absent(transaction.lock);
|
|
20091
|
+
}
|
|
20092
|
+
return;
|
|
19728
20093
|
}
|
|
19729
|
-
await
|
|
20094
|
+
await parents(transaction.plan);
|
|
20095
|
+
await assertIdentity(transaction.lock, transaction.lockStat);
|
|
20096
|
+
await fs.rmdir(transaction.lock);
|
|
20097
|
+
return;
|
|
20098
|
+
}
|
|
20099
|
+
if (phase === "transaction-cleanup") {
|
|
20100
|
+
if (transaction.directoryStat === undefined) {
|
|
20101
|
+
if (transaction.directoryAttempted) {
|
|
20102
|
+
await parents(transaction.plan);
|
|
20103
|
+
await absent(transaction.directory);
|
|
20104
|
+
}
|
|
19730
20105
|
return;
|
|
19731
|
-
}
|
|
20106
|
+
}
|
|
20107
|
+
await custody(transaction);
|
|
20108
|
+
await fs.rmdir(transaction.directory);
|
|
20109
|
+
return;
|
|
19732
20110
|
}
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
20111
|
+
if (name === "pair")
|
|
20112
|
+
throw unsafe();
|
|
20113
|
+
const current = transaction.items[name];
|
|
20114
|
+
const item = transaction.plan.items[name];
|
|
20115
|
+
if (phase === "rollback-published") {
|
|
20116
|
+
if (!current.publishAttempted)
|
|
19738
20117
|
return;
|
|
19739
|
-
|
|
20118
|
+
await custody(transaction);
|
|
20119
|
+
const destination = await optionalStat(item.destination);
|
|
20120
|
+
if (destination === null)
|
|
20121
|
+
return;
|
|
20122
|
+
const root = current.staged[0];
|
|
20123
|
+
if (root === undefined || !same(root.stat, destination))
|
|
20124
|
+
throw unsafe();
|
|
20125
|
+
await removeObserved(transaction, item.destination, current.staged);
|
|
20126
|
+
} else if (phase === "restore-backup") {
|
|
20127
|
+
if (!current.backupAttempted || item.original === null)
|
|
20128
|
+
return;
|
|
20129
|
+
await custody(transaction);
|
|
20130
|
+
if (await optionalStat(current.backup) === null) {
|
|
20131
|
+
await assertInventory(item.destination, item.original);
|
|
20132
|
+
return;
|
|
20133
|
+
}
|
|
20134
|
+
await assertInventory(current.backup, item.original);
|
|
20135
|
+
await absent(item.destination);
|
|
20136
|
+
await fs.rename(current.backup, item.destination);
|
|
20137
|
+
} else if (phase === "stage-cleanup") {
|
|
20138
|
+
if (current.staged.length === 0)
|
|
20139
|
+
return;
|
|
20140
|
+
await custody(transaction);
|
|
20141
|
+
if (await optionalStat(current.stage) !== null)
|
|
20142
|
+
await removeObserved(transaction, current.stage, current.staged);
|
|
20143
|
+
} else if (phase === "backup-cleanup") {
|
|
20144
|
+
if (item.original !== null)
|
|
20145
|
+
await removeObserved(transaction, current.backup, item.original);
|
|
19740
20146
|
}
|
|
19741
|
-
}
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
20147
|
+
}),
|
|
20148
|
+
destinations: (token3) => foreign(async () => {
|
|
20149
|
+
const transaction = state(token3);
|
|
20150
|
+
await custody(transaction);
|
|
20151
|
+
for (const name of SKILL_INSTALL_PAIR)
|
|
20152
|
+
await assertInventory(transaction.plan.items[name].destination, transaction.items[name].staged);
|
|
20153
|
+
return Object.freeze({ messageLikeMe: await fs.realpath(transaction.plan.items["message-like-me"].destination), ensoul: await fs.realpath(transaction.plan.items.ensoul.destination) });
|
|
20154
|
+
})
|
|
20155
|
+
};
|
|
20156
|
+
var skillInstallPlatformLive = exports_Layer.succeed(SkillInstallPlatform, skillInstallPlatform);
|
|
19747
20157
|
|
|
19748
20158
|
// src/store.ts
|
|
19749
20159
|
import { Database as Database3 } from "bun:sqlite";
|
|
19750
|
-
import { createHash as
|
|
20160
|
+
import { createHash as createHash6 } from "crypto";
|
|
19751
20161
|
import {
|
|
19752
20162
|
closeSync as closeSync2,
|
|
19753
20163
|
constants as fsConstants6,
|
|
@@ -21068,7 +21478,7 @@ function globalCorpusRevision(database) {
|
|
|
21068
21478
|
}));
|
|
21069
21479
|
}
|
|
21070
21480
|
function sourceStateRevision(database, sourceId) {
|
|
21071
|
-
const hash2 =
|
|
21481
|
+
const hash2 = createHash6("sha256");
|
|
21072
21482
|
hash2.update("message-like-me\x00stored-source-state-v1\x00", "utf8");
|
|
21073
21483
|
const append4 = (kind, row) => {
|
|
21074
21484
|
const encoded = canonicalJson(row);
|
|
@@ -22677,8 +23087,8 @@ class LocalStore {
|
|
|
22677
23087
|
}
|
|
22678
23088
|
preparedHandoffReceiptStatus(value) {
|
|
22679
23089
|
const handoff = parseAgentMessageHandoffV1(value);
|
|
22680
|
-
const
|
|
22681
|
-
if (
|
|
23090
|
+
const exists5 = get13(this.#database, "SELECT handoff_id FROM agent_message_handoffs WHERE handoff_id=?", handoff.handoffId);
|
|
23091
|
+
if (exists5 === null)
|
|
22682
23092
|
return "absent";
|
|
22683
23093
|
const audit = this.handoffAudit(handoff.handoffId);
|
|
22684
23094
|
return audit.handoffSha256 === handoff.integrity.canonicalSha256 && audit.contactIdSha256 === sha256(handoff.contact.contactId) && audit.routeCandidateIdSha256 === sha256(handoff.contact.routeCandidateId) && audit.sourceIdSha256 === sha256(handoff.contact.sourceId) && audit.conversationIdSha256 === sha256(handoff.contact.conversationId) && audit.corpusRevision === handoff.evidence.corpusRevision && audit.sourceRevision === handoff.evidence.sourceRevision && audit.profileState === handoff.evidence.profileState && audit.profileEvidenceRevision === handoff.evidence.profileEvidenceRevision && audit.wrenchContractHash === handoff.wrench.contractHash && audit.routeRefSha256 === sha256(handoff.wrench.routeRef) && audit.contextRefSha256 === sha256(handoff.wrench.contextRef) && audit.exactDataRevisionSha256 === handoff.wrench.exactDataRevision && audit.latestMessageRevisionSha256 === handoff.wrench.latestMessageRevision && audit.turnDigest === wrenchMessagingTurnDigestV1(handoff) && audit.partCount === handoff.turn.bubbles.length && audit.createdAt === handoff.createdAt && audit.expiresAt === handoff.expiresAt ? "committed" : "different";
|
|
@@ -22813,17 +23223,17 @@ class LocalStore {
|
|
|
22813
23223
|
}
|
|
22814
23224
|
|
|
22815
23225
|
// src/x-archive.ts
|
|
22816
|
-
import { createHash as
|
|
23226
|
+
import { createHash as createHash7 } from "crypto";
|
|
22817
23227
|
import {
|
|
22818
23228
|
closeSync as closeSync3,
|
|
22819
|
-
constants as
|
|
23229
|
+
constants as constants3,
|
|
22820
23230
|
fstatSync as fstatSync3,
|
|
22821
23231
|
lstatSync as lstatSync5,
|
|
22822
23232
|
openSync as openSync3,
|
|
22823
23233
|
readSync as readSync3,
|
|
22824
23234
|
realpathSync as realpathSync4
|
|
22825
23235
|
} from "fs";
|
|
22826
|
-
import { isAbsolute as isAbsolute5, resolve as
|
|
23236
|
+
import { isAbsolute as isAbsolute5, resolve as resolve7 } from "path";
|
|
22827
23237
|
|
|
22828
23238
|
// src/x-archive-zip.ts
|
|
22829
23239
|
import { readSync as readSync2 } from "fs";
|
|
@@ -22854,7 +23264,7 @@ var U32_MAX = 4294967295;
|
|
|
22854
23264
|
var MAX_X_ZIP_ARCHIVE_BYTES = 16 * 1024 * 1024 * 1024;
|
|
22855
23265
|
var MAX_X_ZIP_MEMBER_BYTES = 256 * 1024 * 1024;
|
|
22856
23266
|
var MAX_COMPRESSED_MEMBER_BYTES = 64 * 1024 * 1024;
|
|
22857
|
-
var
|
|
23267
|
+
var MAX_ENTRIES2 = 1e5;
|
|
22858
23268
|
var MAX_CENTRAL_BYTES = 64 * 1024 * 1024;
|
|
22859
23269
|
var MAX_TOTAL_SELECTED_BYTES = 768 * 1024 * 1024;
|
|
22860
23270
|
var MAX_TOTAL_DECLARED_BYTES = 64 * 1024 * 1024 * 1024;
|
|
@@ -22872,8 +23282,8 @@ var CRC32_TABLE = (() => {
|
|
|
22872
23282
|
}
|
|
22873
23283
|
return table;
|
|
22874
23284
|
})();
|
|
22875
|
-
function updateCrc32(
|
|
22876
|
-
let value =
|
|
23285
|
+
function updateCrc32(state2, bytes) {
|
|
23286
|
+
let value = state2;
|
|
22877
23287
|
for (const byte of bytes)
|
|
22878
23288
|
value = CRC32_TABLE[(value ^ byte) & 255] ^ value >>> 8;
|
|
22879
23289
|
return value;
|
|
@@ -22941,7 +23351,7 @@ function directoryFromEocd(descriptor3, archiveSize) {
|
|
|
22941
23351
|
if (legacyDisk !== 0 || legacyCentralDisk !== 0 || legacyOnDisk !== legacyCount) {
|
|
22942
23352
|
throw new Error("X ZIP multi-disk archives are not supported");
|
|
22943
23353
|
}
|
|
22944
|
-
if (legacyCount < 1 || legacyCount >
|
|
23354
|
+
if (legacyCount < 1 || legacyCount > MAX_ENTRIES2 || legacySize > MAX_CENTRAL_BYTES) {
|
|
22945
23355
|
throw new Error("X ZIP central directory exceeds its bounds");
|
|
22946
23356
|
}
|
|
22947
23357
|
const end4 = checkedEnd(legacyOffset, legacySize, "central directory");
|
|
@@ -22966,7 +23376,7 @@ function directoryFromEocd(descriptor3, archiveSize) {
|
|
|
22966
23376
|
const offset = u64(zip64, 48, "ZIP64 central directory offset");
|
|
22967
23377
|
if (onDisk !== count)
|
|
22968
23378
|
throw new Error("X ZIP multi-disk archives are not supported");
|
|
22969
|
-
if (count < 1 || count >
|
|
23379
|
+
if (count < 1 || count > MAX_ENTRIES2 || size9 > MAX_CENTRAL_BYTES) {
|
|
22970
23380
|
throw new Error("X ZIP central directory exceeds its bounds");
|
|
22971
23381
|
}
|
|
22972
23382
|
const end3 = checkedEnd(offset, size9, "ZIP64 central directory");
|
|
@@ -23379,7 +23789,7 @@ var PROVIDER_ID = /^[1-9][0-9]{0,39}$/u;
|
|
|
23379
23789
|
var OPAQUE_PROVIDER_ID = /^-?[0-9]{1,40}$/u;
|
|
23380
23790
|
var HANDLE = /^[A-Za-z0-9_]{1,15}$/u;
|
|
23381
23791
|
function sha2563(value) {
|
|
23382
|
-
return
|
|
23792
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
23383
23793
|
}
|
|
23384
23794
|
function plain(value, label) {
|
|
23385
23795
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -23725,16 +24135,16 @@ function parseConversations(member, selfId, group, seenConversationIds, seenMess
|
|
|
23725
24135
|
if (!/^[0-9]+(?:-[0-9]+)?$/u.test(conversationId)) {
|
|
23726
24136
|
throw new Error(`${label}.dmConversation.conversationId is invalid`);
|
|
23727
24137
|
}
|
|
23728
|
-
let
|
|
23729
|
-
if (
|
|
24138
|
+
let state2 = combined.get(conversationId);
|
|
24139
|
+
if (state2 === undefined) {
|
|
23730
24140
|
if (seenConversationIds.has(conversationId)) {
|
|
23731
24141
|
throw new Error(`${label} repeats an X conversation across direct and group members`);
|
|
23732
24142
|
}
|
|
23733
24143
|
seenConversationIds.add(conversationId);
|
|
23734
|
-
|
|
23735
|
-
combined.set(conversationId,
|
|
24144
|
+
state2 = { participants: new Set([selfId]), events: [] };
|
|
24145
|
+
combined.set(conversationId, state2);
|
|
23736
24146
|
}
|
|
23737
|
-
const participants =
|
|
24147
|
+
const participants = state2.participants;
|
|
23738
24148
|
const directIds = group ? null : conversationId.split("-").map((id, index) => providerId(id, `${label}.conversationId[${index}]`));
|
|
23739
24149
|
if (directIds !== null) {
|
|
23740
24150
|
if (directIds.length !== 2 || !directIds.includes(selfId)) {
|
|
@@ -23779,17 +24189,17 @@ function parseConversations(member, selfId, group, seenConversationIds, seenMess
|
|
|
23779
24189
|
if (directIds !== null && [...participants].some((id) => !directIds.includes(id))) {
|
|
23780
24190
|
throw new Error(`${eventLabel} names a user outside its direct conversation`);
|
|
23781
24191
|
}
|
|
23782
|
-
|
|
24192
|
+
state2.events.push(parsed);
|
|
23783
24193
|
}
|
|
23784
24194
|
}
|
|
23785
24195
|
const signatures = new Map;
|
|
23786
|
-
const conversations = [...combined.entries()].map(([conversationId,
|
|
23787
|
-
const orderedEvents =
|
|
24196
|
+
const conversations = [...combined.entries()].map(([conversationId, state2]) => {
|
|
24197
|
+
const orderedEvents = state2.events.sort((left3, right3) => left3.createdAt.localeCompare(right3.createdAt) || eventSortKey(left3).localeCompare(eventSortKey(right3)));
|
|
23788
24198
|
signatures.set(conversationId, orderedEvents.filter((event) => event.kind === "message-create").map(eventHeaderSignature).sort());
|
|
23789
24199
|
return {
|
|
23790
24200
|
conversationId,
|
|
23791
24201
|
kind: group ? "group" : "direct",
|
|
23792
|
-
participantIds: [...
|
|
24202
|
+
participantIds: [...state2.participants].sort(),
|
|
23793
24203
|
events: orderedEvents
|
|
23794
24204
|
};
|
|
23795
24205
|
});
|
|
@@ -24067,7 +24477,7 @@ function parseXArchiveMembers(members) {
|
|
|
24067
24477
|
};
|
|
24068
24478
|
}
|
|
24069
24479
|
function sha256Descriptor(descriptor3, size9) {
|
|
24070
|
-
const digest5 =
|
|
24480
|
+
const digest5 = createHash7("sha256");
|
|
24071
24481
|
const buffer = Buffer.allocUnsafe(8 * 1024 * 1024);
|
|
24072
24482
|
let position = 0;
|
|
24073
24483
|
while (position < size9) {
|
|
@@ -24083,18 +24493,18 @@ function sameStat(left3, right3) {
|
|
|
24083
24493
|
return left3.dev === right3.dev && left3.ino === right3.ino && left3.size === right3.size && left3.mtimeNs === right3.mtimeNs && left3.ctimeNs === right3.ctimeNs && left3.mode === right3.mode && left3.uid === right3.uid && left3.nlink === right3.nlink;
|
|
24084
24494
|
}
|
|
24085
24495
|
async function readXArchive(path) {
|
|
24086
|
-
if (typeof path !== "string" || path.length < 1 || path.includes("\x00") || !isAbsolute5(path) ||
|
|
24496
|
+
if (typeof path !== "string" || path.length < 1 || path.includes("\x00") || !isAbsolute5(path) || resolve7(path) !== path)
|
|
24087
24497
|
throw new Error("X archive path must be a normalized absolute path");
|
|
24088
|
-
let
|
|
24498
|
+
let physical2;
|
|
24089
24499
|
try {
|
|
24090
|
-
|
|
24500
|
+
physical2 = realpathSync4(path);
|
|
24091
24501
|
} catch (error) {
|
|
24092
24502
|
throw new Error("X archive path cannot be resolved", { cause: error });
|
|
24093
24503
|
}
|
|
24094
|
-
if (
|
|
24504
|
+
if (physical2 !== path)
|
|
24095
24505
|
throw new Error("X archive path must not traverse a symbolic link");
|
|
24096
24506
|
const pathBefore = lstatSync5(path, { bigint: true });
|
|
24097
|
-
const descriptor3 = openSync3(path,
|
|
24507
|
+
const descriptor3 = openSync3(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
24098
24508
|
try {
|
|
24099
24509
|
const before2 = fstatSync3(descriptor3, { bigint: true });
|
|
24100
24510
|
const uid = typeof process.getuid === "function" ? BigInt(process.getuid()) : null;
|
|
@@ -24146,11 +24556,11 @@ function translatedFailure(translate, error) {
|
|
|
24146
24556
|
}
|
|
24147
24557
|
function commandPlatformLive(io, cleanupFailure) {
|
|
24148
24558
|
const attempt = (tryOperation) => exports_Effect.try({ try: tryOperation, catch: commandFailure });
|
|
24149
|
-
const
|
|
24559
|
+
const foreign2 = (tryOperation) => exports_Effect.tryPromise({ try: tryOperation, catch: commandFailure });
|
|
24150
24560
|
const paths = (explicit) => attempt(() => dataPaths(explicit));
|
|
24151
|
-
const initialize = (requested) =>
|
|
24152
|
-
const installKey = (path) =>
|
|
24153
|
-
const
|
|
24561
|
+
const initialize = (requested) => foreign2(() => initializeDataPaths(requested));
|
|
24562
|
+
const installKey = (path) => foreign2(() => loadOrCreateInstallKey(path));
|
|
24563
|
+
const exists5 = (path) => foreign2(async () => {
|
|
24154
24564
|
try {
|
|
24155
24565
|
await lstat5(path);
|
|
24156
24566
|
return true;
|
|
@@ -24190,7 +24600,7 @@ function commandPlatformLive(io, cleanupFailure) {
|
|
|
24190
24600
|
})));
|
|
24191
24601
|
const existingSession = (explicit) => exports_Effect.gen(function* () {
|
|
24192
24602
|
const requested = yield* paths(explicit);
|
|
24193
|
-
if (!(yield*
|
|
24603
|
+
if (!(yield* exists5(requested.root)) || !(yield* exists5(requested.database))) {
|
|
24194
24604
|
return yield* exports_Effect.fail(commandFailure(new CliError("not-found", "Message Like Me is not initialized; run messagelikeme init or an ingest command")));
|
|
24195
24605
|
}
|
|
24196
24606
|
const initialized = yield* initialize(requested);
|
|
@@ -24218,7 +24628,7 @@ function commandPlatformLive(io, cleanupFailure) {
|
|
|
24218
24628
|
paths,
|
|
24219
24629
|
initialize,
|
|
24220
24630
|
installKey,
|
|
24221
|
-
exists:
|
|
24631
|
+
exists: exists5,
|
|
24222
24632
|
existingSession,
|
|
24223
24633
|
writableSession,
|
|
24224
24634
|
openStore,
|
|
@@ -24226,9 +24636,9 @@ function commandPlatformLive(io, cleanupFailure) {
|
|
|
24226
24636
|
readContacts: (path, key) => exports_Effect.try({ try: () => readMacOSContacts(path, { hmacKey: key }), catch: (error) => translatedFailure(translateContactsError, error) }),
|
|
24227
24637
|
readBundle: (path, key) => exports_Effect.tryPromise({ try: () => readMessageBundle(path, { hmacKey: key }), catch: (error) => translatedFailure(translateBundleError, error) }),
|
|
24228
24638
|
readXArchive: (path) => exports_Effect.tryPromise({ try: () => readXArchive(path), catch: (error) => translatedFailure(translateXArchiveError, error) }),
|
|
24229
|
-
readProfile: (path) =>
|
|
24230
|
-
readPrivateJson: (path, label, maximumBytes) =>
|
|
24231
|
-
installSkill: (options) =>
|
|
24639
|
+
readProfile: (path) => foreign2(() => readStyleProfile(path)),
|
|
24640
|
+
readPrivateJson: (path, label, maximumBytes) => foreign2(() => readStablePrivateJson(path, label, maximumBytes)),
|
|
24641
|
+
installSkill: (options) => installSkillProgram(options).pipe(exports_Effect.provide(skillInstallPlatformLive))
|
|
24232
24642
|
});
|
|
24233
24643
|
}
|
|
24234
24644
|
|
|
@@ -24329,10 +24739,10 @@ function rejectUnused(parsed, allowedOptions, allowedFlags) {
|
|
|
24329
24739
|
}
|
|
24330
24740
|
|
|
24331
24741
|
// src/command-input.ts
|
|
24332
|
-
import { isAbsolute as isAbsolute6, resolve as
|
|
24742
|
+
import { isAbsolute as isAbsolute6, resolve as resolve8 } from "path";
|
|
24333
24743
|
|
|
24334
24744
|
// src/version.ts
|
|
24335
|
-
var MESSAGE_LIKE_ME_VERSION = "0.8.
|
|
24745
|
+
var MESSAGE_LIKE_ME_VERSION = "0.8.6";
|
|
24336
24746
|
|
|
24337
24747
|
// src/command-input.ts
|
|
24338
24748
|
var HELP = `Message Like Me ${MESSAGE_LIKE_ME_VERSION}
|
|
@@ -24435,7 +24845,7 @@ function absolutePrivatePath(value, label) {
|
|
|
24435
24845
|
throw new CliError("usage", `${label} is required`);
|
|
24436
24846
|
if (!isAbsolute6(value))
|
|
24437
24847
|
throw new CliError("unsafe-path", `${label} must be an absolute private path`);
|
|
24438
|
-
return
|
|
24848
|
+
return resolve8(value);
|
|
24439
24849
|
}
|
|
24440
24850
|
function handoffExpiry(createdAt, contextExpiresAt, lifetimeMilliseconds) {
|
|
24441
24851
|
return new Date(Math.min(Date.parse(createdAt) + lifetimeMilliseconds, Date.parse(contextExpiresAt))).toISOString();
|
|
@@ -24487,7 +24897,7 @@ function commandArtifactsLive(reports) {
|
|
|
24487
24897
|
}
|
|
24488
24898
|
|
|
24489
24899
|
// src/metrics.ts
|
|
24490
|
-
import { createHash as
|
|
24900
|
+
import { createHash as createHash8 } from "crypto";
|
|
24491
24901
|
var DEFAULT_SESSION_GAP_SECONDS = 8 * 60 * 60;
|
|
24492
24902
|
var DEFAULT_BURST_GAP_SECONDS = 5 * 60;
|
|
24493
24903
|
var DEFAULT_STUDY_LIMIT = 12;
|
|
@@ -24500,7 +24910,7 @@ var DEFAULT_MAX_STUDY_PACKET_BODY_BYTES = 256 * 1024;
|
|
|
24500
24910
|
var MAX_STUDY_PACKET_BODY_BYTES = 1024 * 1024;
|
|
24501
24911
|
var MAX_GAP_SECONDS = 30 * 24 * 60 * 60;
|
|
24502
24912
|
function digest5(namespace, parts2) {
|
|
24503
|
-
const hash2 =
|
|
24913
|
+
const hash2 = createHash8("sha256");
|
|
24504
24914
|
hash2.update(`message-like-me\x00${namespace}\x00`, "utf8");
|
|
24505
24915
|
for (const part of parts2)
|
|
24506
24916
|
hash2.update(`${part.length}:`, "utf8").update(part, "utf8");
|
|
@@ -24938,15 +25348,15 @@ function studyMessages(response, byId, maximumTextBytes, maximumMessagesPerDirec
|
|
|
24938
25348
|
let emittedBodyBytes = sourceBodyBytes;
|
|
24939
25349
|
if (sourceBodyBytes > maximumTextBytes) {
|
|
24940
25350
|
let bytes = 0;
|
|
24941
|
-
let
|
|
25351
|
+
let bounded2 = "";
|
|
24942
25352
|
for (const symbol3 of sourceBody) {
|
|
24943
25353
|
const symbolBytes = Buffer.byteLength(symbol3, "utf8");
|
|
24944
25354
|
if (bytes + symbolBytes > maximumTextBytes)
|
|
24945
25355
|
break;
|
|
24946
|
-
|
|
25356
|
+
bounded2 += symbol3;
|
|
24947
25357
|
bytes += symbolBytes;
|
|
24948
25358
|
}
|
|
24949
|
-
body =
|
|
25359
|
+
body = bounded2;
|
|
24950
25360
|
emittedBodyBytes = bytes;
|
|
24951
25361
|
}
|
|
24952
25362
|
return Object.freeze({
|
|
@@ -27010,13 +27420,19 @@ function commandProgram(argv) {
|
|
|
27010
27420
|
if (project3 !== undefined && scope5 !== "project") {
|
|
27011
27421
|
return yield* exports_Effect.fail(commandFailure(new CliError("usage", "--project requires --scope project")));
|
|
27012
27422
|
}
|
|
27013
|
-
const
|
|
27423
|
+
const installed = yield* platform2.installSkill({
|
|
27014
27424
|
target,
|
|
27015
27425
|
scope: scope5,
|
|
27016
27426
|
...project3 === undefined ? {} : { projectDirectory: project3 },
|
|
27017
27427
|
force: parsed.flags.has("force")
|
|
27018
27428
|
});
|
|
27019
|
-
|
|
27429
|
+
const destinations = exports_Exit.isSuccess(installed.operation) ? installed.operation.value : null;
|
|
27430
|
+
const output = destinations === null ? exports_Exit.asVoid(installed.operation) : yield* exports_Effect.exit(platform2.emit(json, { destination: destinations.messageLikeMe, destinations, target, scope: scope5 }, `Installed message-like-me and ensoul skills at ${destinations.messageLikeMe} and ${destinations.ensoul}`));
|
|
27431
|
+
const warning = skillInstallWarning({ ...installed, operation: exports_Exit.isFailure(output) ? output : installed.operation });
|
|
27432
|
+
const diagnostic = warning === null ? exports_Exit.void : yield* exports_Effect.exit(platform2.stderr(warning));
|
|
27433
|
+
if (exports_Exit.isFailure(output))
|
|
27434
|
+
return yield* output;
|
|
27435
|
+
yield* diagnostic;
|
|
27020
27436
|
return;
|
|
27021
27437
|
}
|
|
27022
27438
|
if (command === "doctor" && subcommand === undefined) {
|
package/docs/command-runtime.md
CHANGED
|
@@ -18,7 +18,8 @@ failures retain the original error as a cause in the closed `CommandFailure`
|
|
|
18
18
|
union. Native adapters translate source-specific failures at the same boundaries
|
|
19
19
|
as before. The runtime has a narrow compatibility bridge for existing pure
|
|
20
20
|
validators that throw `CliError`; other defects remain defects. CLI text, stdout
|
|
21
|
-
formatting
|
|
21
|
+
formatting and exit-code selection are unchanged. The paired skill installer adds
|
|
22
|
+
the bounded residual diagnostics described below.
|
|
22
23
|
|
|
23
24
|
## Publication and receipt recovery
|
|
24
25
|
|
|
@@ -60,12 +61,51 @@ Effect and TypeScript do not remove that operating-system limitation. Directory
|
|
|
60
61
|
sync failures after removal are reported as `removed-unconfirmed`. The runtime
|
|
61
62
|
never claims that uncertain cleanup succeeded.
|
|
62
63
|
|
|
64
|
+
## Paired skill installation
|
|
65
|
+
|
|
66
|
+
The existing command runtime directly composes the fixed `message-like-me` and
|
|
67
|
+
`ensoul` installer. Its program owns preflight, exclusive directory lock, private
|
|
68
|
+
transaction directory, copies, backups, both publish renames, recovery, cleanup,
|
|
69
|
+
and output. Both publish acknowledgements commit the pair immediately. Backup
|
|
70
|
+
cleanup, path confirmation, or stdout failure afterward never rolls it back.
|
|
71
|
+
There is no automatic retry or additional runtime. Admitted native work and
|
|
72
|
+
sequential recovery remain masked until the actual filesystem promises settle.
|
|
73
|
+
|
|
74
|
+
Before creating missing installation parents, read-only preflight rejects
|
|
75
|
+
conflicts, observed source/target overlap, symlinked installation parents, and
|
|
76
|
+
nonphysical source entries. Project anchors resolve existing aliases first;
|
|
77
|
+
missing project directories are then created in order beneath the observed
|
|
78
|
+
physical ancestor. Requested paths allow 4,096 UTF-8 bytes and at most 64 missing
|
|
79
|
+
project components. These directories are retained on later failure. Each
|
|
80
|
+
skill inventory permits at most 64 entries, eight levels, 256 KiB per file, and
|
|
81
|
+
2 MiB total. These bounds also apply to the old directory on forced replacement.
|
|
82
|
+
Copied types, bytes, and digests must match the admitted source before publication.
|
|
83
|
+
Copies explicitly refuse existing entries. A cooperating installer's existing
|
|
84
|
+
lock is never reclaimed; an ambiguous acquisition is left for inspection.
|
|
85
|
+
|
|
86
|
+
Before publication and cleanup the adapter rechecks observed physical parents
|
|
87
|
+
and entry identities. Recovery runs in reverse pair order; independent actions
|
|
88
|
+
continue after cleanup failure. Restoration requires an absent destination and
|
|
89
|
+
the observed backup. Cleanup removes individually observed leaves and then empty
|
|
90
|
+
directories, retaining unknown or substituted entries. Creating missing parent
|
|
91
|
+
directories is not rolled back. This is observation-bound custody, not a kernel
|
|
92
|
+
conditional unlink, cross-process atomic visibility, hostile same-user race
|
|
93
|
+
immunity, or crash recovery.
|
|
94
|
+
|
|
95
|
+
Clean success stdout is unchanged. Residual cleanup or a postcommit output
|
|
96
|
+
failure adds one stderr warning containing only fixed skill, phase, and commit
|
|
97
|
+
state labels. Private cleanup causes remain internal. The original primary
|
|
98
|
+
rejection, including `undefined`, `null`, or `false`, wins over cleanup and a
|
|
99
|
+
failed warning writer. A successful install with failed warning output instead
|
|
100
|
+
reports that output failure; it does not repeat installation.
|
|
101
|
+
|
|
63
102
|
## Enforced architecture and distribution
|
|
64
103
|
|
|
65
104
|
`bun run check:effect` uses the repository's TypeScript compiler API and the
|
|
66
105
|
reviewed policy in `scripts/check-effect-architecture.ts`. Only `commands.ts`
|
|
67
106
|
may execute a runtime; native calls belong in `command-platform.ts` or
|
|
68
|
-
`command-artifacts.ts`. The
|
|
107
|
+
`command-artifacts.ts`, or `skill-install-platform.ts`. The installer model and
|
|
108
|
+
program compose under that same command owner. The checker rejects unclassified Effect modules,
|
|
69
109
|
floating Effects, erased channels, unsafe assertions, failure-discard shortcuts,
|
|
70
110
|
suppressions, and selected ambient I/O. Its paired fixtures cover common aliases.
|
|
71
111
|
It does not prove purity, linear lifetime safety, domain correctness, foreign
|
package/package.json
CHANGED