mdmeld 0.1.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.
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HASH_PLACEHOLDER,
4
+ computeHash,
5
+ computeHashBytes,
6
+ decodeBase64,
7
+ dump,
8
+ load
9
+ } from "./chunk-UJZ43GZC.js";
10
+
11
+ // src/cli/commands/finalize.ts
12
+ import { readFileSync, writeFileSync } from "fs";
13
+ import { resolve } from "path";
14
+ var TEXT_ENCODER = new TextEncoder();
15
+ async function runFinalize(args) {
16
+ const filePath = resolve(args.file);
17
+ const content = readFileSync(filePath, "utf-8");
18
+ let input = content;
19
+ let headerComment = "";
20
+ if (input.startsWith("<!--")) {
21
+ const commentEnd = input.indexOf("-->");
22
+ if (commentEnd !== -1) {
23
+ headerComment = input.slice(0, commentEnd + 3);
24
+ input = input.slice(commentEnd + 3).trimStart();
25
+ }
26
+ }
27
+ if (!input.startsWith("---")) {
28
+ console.error("Invalid archive: missing YAML frontmatter");
29
+ process.exit(1);
30
+ }
31
+ const secondDelim = input.indexOf("\n---", 3);
32
+ if (secondDelim === -1) {
33
+ console.error("Invalid archive: missing closing --- delimiter");
34
+ process.exit(1);
35
+ }
36
+ const yamlStr = input.slice(4, secondDelim);
37
+ const afterFrontmatter = input.slice(secondDelim + 4);
38
+ const contentSection = afterFrontmatter.replace(/^\n+/, "");
39
+ const manifest = load(yamlStr);
40
+ const algorithm = manifest.mdmeld.hash_algorithm ?? "xxh64";
41
+ let changed = false;
42
+ const fence = "`".repeat(manifest.mdmeld.backtick_count);
43
+ const pattern = new RegExp(
44
+ `### (.+?)\\s*\\n\\s*${fence}([^\\n]*)\\n([\\s\\S]*?)\\n?${fence}(?=\\s|$)`,
45
+ "g"
46
+ );
47
+ for (const match of contentSection.matchAll(pattern)) {
48
+ const path = match[1].trim();
49
+ const lang = match[2].trim();
50
+ const rawContent = match[3];
51
+ const entry = manifest.mdmeld.files.find((f) => f.path === path);
52
+ if (entry && entry.hash === HASH_PLACEHOLDER) {
53
+ const isEncoded = lang === "base64" || entry.encoding === "base64";
54
+ const bytes = isEncoded ? decodeBase64(rawContent) : TEXT_ENCODER.encode(rawContent);
55
+ entry.hash = await computeHashBytes(bytes, algorithm);
56
+ changed = true;
57
+ }
58
+ }
59
+ if (manifest.integrity.content_hash === HASH_PLACEHOLDER) {
60
+ manifest.integrity.content_hash = await computeHash(afterFrontmatter, algorithm);
61
+ changed = true;
62
+ }
63
+ if (manifest.integrity.manifest_hash === HASH_PLACEHOLDER) {
64
+ const manifestYaml = dump({ mdmeld: manifest.mdmeld });
65
+ manifest.integrity.manifest_hash = await computeHash(manifestYaml, algorithm);
66
+ changed = true;
67
+ }
68
+ if (!changed) {
69
+ console.log("No HASH_PLACEHOLDER values found \u2014 archive already finalized");
70
+ return;
71
+ }
72
+ const outputYaml = dump(manifest);
73
+ const prefix = headerComment ? `${headerComment}
74
+ ` : "";
75
+ const output = `${prefix}---
76
+ ${outputYaml}---
77
+ ${afterFrontmatter}`;
78
+ writeFileSync(filePath, output, "utf-8");
79
+ console.log(`Finalized hashes in ${filePath}`);
80
+ }
81
+ export {
82
+ runFinalize
83
+ };
84
+ //# sourceMappingURL=finalize-NGDCBCTD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/finalize.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { decodeBase64 } from \"../../core/base64.js\";\nimport { HASH_PLACEHOLDER } from \"../../core/constants.js\";\nimport { computeHash, computeHashBytes } from \"../../core/hash.js\";\nimport type { HashAlgorithm, Manifest } from \"../../core/types.js\";\nimport * as yaml from \"../../core/yaml.js\";\n\nconst TEXT_ENCODER = new TextEncoder();\n\nexport interface FinalizeArgs {\n\tfile: string;\n}\n\n/**\n * Finalize an archive by computing real hashes for HASH_PLACEHOLDER values.\n * Useful for archives generated by AI assistants that can't compute hashes.\n */\nexport async function runFinalize(args: FinalizeArgs): Promise<void> {\n\tconst filePath = resolve(args.file);\n\tconst content = readFileSync(filePath, \"utf-8\");\n\n\t// Parse the archive\n\tlet input = content;\n\tlet headerComment = \"\";\n\n\tif (input.startsWith(\"<!--\")) {\n\t\tconst commentEnd = input.indexOf(\"-->\");\n\t\tif (commentEnd !== -1) {\n\t\t\theaderComment = input.slice(0, commentEnd + 3);\n\t\t\tinput = input.slice(commentEnd + 3).trimStart();\n\t\t}\n\t}\n\n\tif (!input.startsWith(\"---\")) {\n\t\tconsole.error(\"Invalid archive: missing YAML frontmatter\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst secondDelim = input.indexOf(\"\\n---\", 3);\n\tif (secondDelim === -1) {\n\t\tconsole.error(\"Invalid archive: missing closing --- delimiter\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst yamlStr = input.slice(4, secondDelim);\n\tconst afterFrontmatter = input.slice(secondDelim + 4);\n\tconst contentSection = afterFrontmatter.replace(/^\\n+/, \"\");\n\n\tconst manifest = yaml.load(yamlStr) as Manifest;\n\tconst algorithm: HashAlgorithm = manifest.mdmeld.hash_algorithm ?? \"xxh64\";\n\n\tlet changed = false;\n\n\t// Compute file hashes\n\tconst fence = \"`\".repeat(manifest.mdmeld.backtick_count);\n\tconst pattern = new RegExp(\n\t\t`### (.+?)\\\\s*\\\\n\\\\s*${fence}([^\\\\n]*)\\\\n([\\\\s\\\\S]*?)\\\\n?${fence}(?=\\\\s|$)`,\n\t\t\"g\",\n\t);\n\n\tfor (const match of contentSection.matchAll(pattern)) {\n\t\tconst path = match[1]!.trim();\n\t\tconst lang = match[2]!.trim();\n\t\tconst rawContent = match[3]!;\n\n\t\tconst entry = manifest.mdmeld.files.find((f) => f.path === path);\n\t\tif (entry && entry.hash === HASH_PLACEHOLDER) {\n\t\t\t// File hash is computed on original bytes (per spec)\n\t\t\tconst isEncoded = lang === \"base64\" || entry.encoding === \"base64\";\n\t\t\tconst bytes = isEncoded ? decodeBase64(rawContent) : TEXT_ENCODER.encode(rawContent);\n\t\t\tentry.hash = await computeHashBytes(bytes, algorithm);\n\t\t\tchanged = true;\n\t\t}\n\t}\n\n\t// Compute content hash (on raw content after closing ---, per spec)\n\tif (manifest.integrity.content_hash === HASH_PLACEHOLDER) {\n\t\tmanifest.integrity.content_hash = await computeHash(afterFrontmatter, algorithm);\n\t\tchanged = true;\n\t}\n\n\t// Compute manifest hash (must be last, after all other manifest changes)\n\tif (manifest.integrity.manifest_hash === HASH_PLACEHOLDER) {\n\t\t// Manifest hash includes the `mdmeld:` key line per spec\n\t\tconst manifestYaml = yaml.dump({ mdmeld: manifest.mdmeld });\n\t\tmanifest.integrity.manifest_hash = await computeHash(manifestYaml, algorithm);\n\t\tchanged = true;\n\t}\n\n\tif (!changed) {\n\t\tconsole.log(\"No HASH_PLACEHOLDER values found — archive already finalized\");\n\t\treturn;\n\t}\n\n\t// Re-serialize\n\tconst outputYaml = yaml.dump(manifest);\n\tconst prefix = headerComment ? `${headerComment}\\n` : \"\";\n\tconst output = `${prefix}---\\n${outputYaml}---\\n${afterFrontmatter}`;\n\n\twriteFileSync(filePath, output, \"utf-8\");\n\tconsole.log(`Finalized hashes in ${filePath}`);\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,eAAe;AAOxB,IAAM,eAAe,IAAI,YAAY;AAUrC,eAAsB,YAAY,MAAmC;AACpE,QAAM,WAAW,QAAQ,KAAK,IAAI;AAClC,QAAM,UAAU,aAAa,UAAU,OAAO;AAG9C,MAAI,QAAQ;AACZ,MAAI,gBAAgB;AAEpB,MAAI,MAAM,WAAW,MAAM,GAAG;AAC7B,UAAM,aAAa,MAAM,QAAQ,KAAK;AACtC,QAAI,eAAe,IAAI;AACtB,sBAAgB,MAAM,MAAM,GAAG,aAAa,CAAC;AAC7C,cAAQ,MAAM,MAAM,aAAa,CAAC,EAAE,UAAU;AAAA,IAC/C;AAAA,EACD;AAEA,MAAI,CAAC,MAAM,WAAW,KAAK,GAAG;AAC7B,YAAQ,MAAM,2CAA2C;AACzD,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,cAAc,MAAM,QAAQ,SAAS,CAAC;AAC5C,MAAI,gBAAgB,IAAI;AACvB,YAAQ,MAAM,gDAAgD;AAC9D,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,UAAU,MAAM,MAAM,GAAG,WAAW;AAC1C,QAAM,mBAAmB,MAAM,MAAM,cAAc,CAAC;AACpD,QAAM,iBAAiB,iBAAiB,QAAQ,QAAQ,EAAE;AAE1D,QAAM,WAAgB,KAAK,OAAO;AAClC,QAAM,YAA2B,SAAS,OAAO,kBAAkB;AAEnE,MAAI,UAAU;AAGd,QAAM,QAAQ,IAAI,OAAO,SAAS,OAAO,cAAc;AACvD,QAAM,UAAU,IAAI;AAAA,IACnB,uBAAuB,KAAK,+BAA+B,KAAK;AAAA,IAChE;AAAA,EACD;AAEA,aAAW,SAAS,eAAe,SAAS,OAAO,GAAG;AACrD,UAAM,OAAO,MAAM,CAAC,EAAG,KAAK;AAC5B,UAAM,OAAO,MAAM,CAAC,EAAG,KAAK;AAC5B,UAAM,aAAa,MAAM,CAAC;AAE1B,UAAM,QAAQ,SAAS,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC/D,QAAI,SAAS,MAAM,SAAS,kBAAkB;AAE7C,YAAM,YAAY,SAAS,YAAY,MAAM,aAAa;AAC1D,YAAM,QAAQ,YAAY,aAAa,UAAU,IAAI,aAAa,OAAO,UAAU;AACnF,YAAM,OAAO,MAAM,iBAAiB,OAAO,SAAS;AACpD,gBAAU;AAAA,IACX;AAAA,EACD;AAGA,MAAI,SAAS,UAAU,iBAAiB,kBAAkB;AACzD,aAAS,UAAU,eAAe,MAAM,YAAY,kBAAkB,SAAS;AAC/E,cAAU;AAAA,EACX;AAGA,MAAI,SAAS,UAAU,kBAAkB,kBAAkB;AAE1D,UAAM,eAAoB,KAAK,EAAE,QAAQ,SAAS,OAAO,CAAC;AAC1D,aAAS,UAAU,gBAAgB,MAAM,YAAY,cAAc,SAAS;AAC5E,cAAU;AAAA,EACX;AAEA,MAAI,CAAC,SAAS;AACb,YAAQ,IAAI,mEAA8D;AAC1E;AAAA,EACD;AAGA,QAAM,aAAkB,KAAK,QAAQ;AACrC,QAAM,SAAS,gBAAgB,GAAG,aAAa;AAAA,IAAO;AACtD,QAAM,SAAS,GAAG,MAAM;AAAA,EAAQ,UAAU;AAAA,EAAQ,gBAAgB;AAElE,gBAAc,UAAU,QAAQ,OAAO;AACvC,UAAQ,IAAI,uBAAuB,QAAQ,EAAE;AAC9C;","names":[]}
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ pack
4
+ } from "./chunk-UF532LMZ.js";
5
+ import "./chunk-UJZ43GZC.js";
6
+
7
+ // src/cli/commands/pack.ts
8
+ import { writeFileSync } from "fs";
9
+ import { resolve } from "path";
10
+
11
+ // src/cli/fs-adapter.ts
12
+ import { readdirSync, readFileSync as readFileSync2 } from "fs";
13
+ import { join as join2, relative } from "path";
14
+
15
+ // src/cli/ignore.ts
16
+ import { existsSync, readFileSync } from "fs";
17
+ import { join } from "path";
18
+ import ignore from "ignore";
19
+ function createIgnoreFilter(dir) {
20
+ const ig = ignore();
21
+ ig.add([".git", "node_modules", ".DS_Store", ".gitignore", ".mdmeldignore"]);
22
+ const gitignorePath = join(dir, ".gitignore");
23
+ if (existsSync(gitignorePath)) {
24
+ const content = readFileSync(gitignorePath, "utf-8");
25
+ ig.add(content);
26
+ }
27
+ const mdmeldignorePath = join(dir, ".mdmeldignore");
28
+ if (existsSync(mdmeldignorePath)) {
29
+ const content = readFileSync(mdmeldignorePath, "utf-8");
30
+ ig.add(content);
31
+ }
32
+ return (path) => ig.ignores(path);
33
+ }
34
+
35
+ // src/cli/fs-adapter.ts
36
+ function walkDirectory(dir) {
37
+ const isIgnored = createIgnoreFilter(dir);
38
+ const files = [];
39
+ function walk(currentDir) {
40
+ const entries = readdirSync(currentDir, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ const fullPath = join2(currentDir, entry.name);
43
+ const relPath = relative(dir, fullPath);
44
+ const posixPath = relPath.split("\\").join("/");
45
+ if (isIgnored(posixPath)) continue;
46
+ if (entry.isDirectory()) {
47
+ walk(fullPath);
48
+ } else if (entry.isFile()) {
49
+ const content = readFileSync2(fullPath);
50
+ files.push({
51
+ path: posixPath,
52
+ content: new Uint8Array(content)
53
+ });
54
+ }
55
+ }
56
+ }
57
+ walk(dir);
58
+ files.sort((a, b) => a.path.localeCompare(b.path));
59
+ return files;
60
+ }
61
+
62
+ // src/cli/commands/pack.ts
63
+ async function runPack(args) {
64
+ const dir = resolve(args.dir);
65
+ const files = walkDirectory(dir);
66
+ if (files.length === 0) {
67
+ console.error("No files found in directory (check .gitignore/.mdmeldignore)");
68
+ process.exit(1);
69
+ }
70
+ const archive = await pack(files, {
71
+ hashAlgorithm: args.hash ?? "xxh64"
72
+ });
73
+ if (args.output) {
74
+ const outPath = resolve(args.output);
75
+ writeFileSync(outPath, archive, "utf-8");
76
+ console.log(`Packed ${files.length} files \u2192 ${outPath}`);
77
+ } else {
78
+ process.stdout.write(archive);
79
+ }
80
+ }
81
+ export {
82
+ runPack
83
+ };
84
+ //# sourceMappingURL=pack-ODWNG5OH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/pack.ts","../src/cli/fs-adapter.ts","../src/cli/ignore.ts"],"sourcesContent":["import { writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { pack } from \"../../core/index.js\";\nimport type { HashAlgorithm } from \"../../core/types.js\";\nimport { walkDirectory } from \"../fs-adapter.js\";\n\nexport interface PackArgs {\n\tdir: string;\n\toutput?: string;\n\thash?: HashAlgorithm;\n}\n\nexport async function runPack(args: PackArgs): Promise<void> {\n\tconst dir = resolve(args.dir);\n\tconst files = walkDirectory(dir);\n\n\tif (files.length === 0) {\n\t\tconsole.error(\"No files found in directory (check .gitignore/.mdmeldignore)\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst archive = await pack(files, {\n\t\thashAlgorithm: args.hash ?? \"xxh64\",\n\t});\n\n\tif (args.output) {\n\t\tconst outPath = resolve(args.output);\n\t\twriteFileSync(outPath, archive, \"utf-8\");\n\t\tconsole.log(`Packed ${files.length} files → ${outPath}`);\n\t} else {\n\t\tprocess.stdout.write(archive);\n\t}\n}\n","import { readdirSync, readFileSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport type { VirtualFile } from \"../core/types.js\";\nimport { createIgnoreFilter } from \"./ignore.js\";\n\n/**\n * Walk a directory and return all files as VirtualFile[].\n * Respects .gitignore and .mdmeldignore patterns.\n */\nexport function walkDirectory(dir: string): VirtualFile[] {\n\tconst isIgnored = createIgnoreFilter(dir);\n\tconst files: VirtualFile[] = [];\n\n\tfunction walk(currentDir: string) {\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tconst relPath = relative(dir, fullPath);\n\t\t\t// Convert to POSIX path for ignore matching and storage\n\t\t\tconst posixPath = relPath.split(\"\\\\\").join(\"/\");\n\n\t\t\tif (isIgnored(posixPath)) continue;\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\twalk(fullPath);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\tconst content = readFileSync(fullPath);\n\t\t\t\tfiles.push({\n\t\t\t\t\tpath: posixPath,\n\t\t\t\t\tcontent: new Uint8Array(content),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\twalk(dir);\n\n\t// Sort by path for deterministic output\n\tfiles.sort((a, b) => a.path.localeCompare(b.path));\n\treturn files;\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ignore from \"ignore\";\n\n/**\n * Create an ignore filter from .gitignore and .mdmeldignore files in a directory.\n * Returns a function that tests whether a relative path should be ignored.\n */\nexport function createIgnoreFilter(dir: string): (path: string) => boolean {\n\tconst ig = ignore();\n\n\t// Always ignore these\n\tig.add([\".git\", \"node_modules\", \".DS_Store\", \".gitignore\", \".mdmeldignore\"]);\n\n\t// Load .gitignore\n\tconst gitignorePath = join(dir, \".gitignore\");\n\tif (existsSync(gitignorePath)) {\n\t\tconst content = readFileSync(gitignorePath, \"utf-8\");\n\t\tig.add(content);\n\t}\n\n\t// Load .mdmeldignore (takes precedence)\n\tconst mdmeldignorePath = join(dir, \".mdmeldignore\");\n\tif (existsSync(mdmeldignorePath)) {\n\t\tconst content = readFileSync(mdmeldignorePath, \"utf-8\");\n\t\tig.add(content);\n\t}\n\n\treturn (path: string) => ig.ignores(path);\n}\n"],"mappings":";;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;;;ACDxB,SAAS,aAAa,gBAAAA,qBAAoB;AAC1C,SAAS,QAAAC,OAAM,gBAAgB;;;ACD/B,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AACrB,OAAO,YAAY;AAMZ,SAAS,mBAAmB,KAAwC;AAC1E,QAAM,KAAK,OAAO;AAGlB,KAAG,IAAI,CAAC,QAAQ,gBAAgB,aAAa,cAAc,eAAe,CAAC;AAG3E,QAAM,gBAAgB,KAAK,KAAK,YAAY;AAC5C,MAAI,WAAW,aAAa,GAAG;AAC9B,UAAM,UAAU,aAAa,eAAe,OAAO;AACnD,OAAG,IAAI,OAAO;AAAA,EACf;AAGA,QAAM,mBAAmB,KAAK,KAAK,eAAe;AAClD,MAAI,WAAW,gBAAgB,GAAG;AACjC,UAAM,UAAU,aAAa,kBAAkB,OAAO;AACtD,OAAG,IAAI,OAAO;AAAA,EACf;AAEA,SAAO,CAAC,SAAiB,GAAG,QAAQ,IAAI;AACzC;;;ADpBO,SAAS,cAAc,KAA4B;AACzD,QAAM,YAAY,mBAAmB,GAAG;AACxC,QAAM,QAAuB,CAAC;AAE9B,WAAS,KAAK,YAAoB;AACjC,UAAM,UAAU,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC;AAE/D,eAAW,SAAS,SAAS;AAC5B,YAAM,WAAWC,MAAK,YAAY,MAAM,IAAI;AAC5C,YAAM,UAAU,SAAS,KAAK,QAAQ;AAEtC,YAAM,YAAY,QAAQ,MAAM,IAAI,EAAE,KAAK,GAAG;AAE9C,UAAI,UAAU,SAAS,EAAG;AAE1B,UAAI,MAAM,YAAY,GAAG;AACxB,aAAK,QAAQ;AAAA,MACd,WAAW,MAAM,OAAO,GAAG;AAC1B,cAAM,UAAUC,cAAa,QAAQ;AACrC,cAAM,KAAK;AAAA,UACV,MAAM;AAAA,UACN,SAAS,IAAI,WAAW,OAAO;AAAA,QAChC,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAEA,OAAK,GAAG;AAGR,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACjD,SAAO;AACR;;;AD7BA,eAAsB,QAAQ,MAA+B;AAC5D,QAAM,MAAM,QAAQ,KAAK,GAAG;AAC5B,QAAM,QAAQ,cAAc,GAAG;AAE/B,MAAI,MAAM,WAAW,GAAG;AACvB,YAAQ,MAAM,8DAA8D;AAC5E,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,UAAU,MAAM,KAAK,OAAO;AAAA,IACjC,eAAe,KAAK,QAAQ;AAAA,EAC7B,CAAC;AAED,MAAI,KAAK,QAAQ;AAChB,UAAM,UAAU,QAAQ,KAAK,MAAM;AACnC,kBAAc,SAAS,SAAS,OAAO;AACvC,YAAQ,IAAI,UAAU,MAAM,MAAM,iBAAY,OAAO,EAAE;AAAA,EACxD,OAAO;AACN,YAAQ,OAAO,MAAM,OAAO;AAAA,EAC7B;AACD;","names":["readFileSync","join","join","readFileSync"]}
@@ -0,0 +1 @@
1
+ 542ae87d7c324732ef458a5aa942d76098c33dc225bad4af4a282b6ef0f16e92 mdmeld-0.1.0.tgz
@@ -0,0 +1,16 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+
3
+ iQIzBAABCAAdFiEEHt2kvOVJotrCIL73L5+J92c+z38FAmmklt4ACgkQL5+J92c+
4
+ z3+4Lw//bAH/lTMN6lkmkN4kLmYuJ9wo5frRlG+nVLWxr0j5hsIscZUGT/NUemRI
5
+ xs81PVNbiPNFJdq7ZBwebFl1QPY6lYWPOh5dDA1WgzUARgaZalx0A51gJvUVISTR
6
+ zMbGm1EhJiNsyiLchBNr2UQNbdcrsuTqK4pPeGqS6wHTLHaXqqogzYkGE9BV29+z
7
+ RLerPyZvh7XNeXOb+Ca8AC3p1Mzx5i6BwtY1OMJTQFF8ClNywouKgf9ezyhF8VRB
8
+ mWHWIMLIHlDkqKlf+cbd5CqoDv0mw410AH6mufwc2fWcsGTkcnRe+DAh93iiT8kD
9
+ CwHsbHY4NPqtOGH+z7w/1R+N8sB+d5I4PHrMWSl3g4A6oH/HY4+YQhMLllH2HdTQ
10
+ b+StFDZYjNQoJXI5FXxL/f0njBtJnJs2yUEj8Rs31rGcI+ZCligibTJpWR/b9b+B
11
+ tmS4x0o1+9xZsWaj6oWmfVA9lfR69lweX0e9tfQZ2YCCaDRsrIuJaxznlIcoavOi
12
+ sCZEHYZDLkWdEAN1f9GK8QCOAaqifhXd+UmzFKkFMtDeQMsLvKb1thZj6X07BpfZ
13
+ fVSzLYW90nf9EKSex1DZCI8/Y+4i21B4PSZeVUGtTFU4c3LJq8KynbfJZ2aUHvNr
14
+ Wj8KeBr0bCLzu3/EsUAKbo6DpT5jW4aT33Wk8AYzURkoEyMVc/o=
15
+ =mUbK
16
+ -----END PGP SIGNATURE-----
@@ -0,0 +1,4 @@
1
+ untrusted comment: signature from minisign secret key
2
+ RUTAoUJ007VE3uOIJmESKUHLMe80f66r4JtcMtc+bFuYMq4Tl4vgnV8GnyuAL2ccy2QtruQwE88O3yQrOoiO6mby2ZIL14aGOQk=
3
+ trusted comment: mdmeld v0.1.0 2026-03-01T19:43:15Z
4
+ haxxNEUIGMMMQa34C0Gtt2Z80oyniGkqPcSOkBinB6Tu7XR5HQX2GP2FcTpMUp00cxyGsW5EjzAs1Y2nKm2IDA==
@@ -0,0 +1 @@
1
+ 0a037cde6d3ee5973f47a88894948eab9e1da105212f3dbcff461750550029f8c44fa12641e01dea36cad495d9731dac193f723bada398f89e608366a6ef4183 mdmeld-0.1.0.tgz
@@ -0,0 +1,16 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+
3
+ iQIzBAABCAAdFiEEHt2kvOVJotrCIL73L5+J92c+z38FAmmkluIACgkQL5+J92c+
4
+ z38UzhAA1mS3XY46CHJ22g4HS8oUH9vMJaZU8SE2bc6NO0avygfDtHAjpVefXsUw
5
+ /OmRnBmo7S4yG9niePGPHATtYaUjhWeDbcs3W7zvkSuz35uLtqxa9XQmcl5Ekcyg
6
+ 1Ti49l0m0EN3Q0JtF8I6Dqv0ZEkeN4BZxgyJBWdjR7nN+KrYOK0zv8EGz5byO4jP
7
+ 6wWpjDa6HzXl2KnmWgWLW4nBBR1+eqT36WFcjdvfxehPPCnKpbRo7lUINudt/xPf
8
+ hGQfkVzeVKULkjxnZxUEK6Yvs+HM6/cDarrUb4xwnZ9tPQdpivWwxT/3wOWKD9QT
9
+ AkGWnazub6399iMr9hjbqaVDFiqHYtZ5pldJ43BwpweSe//Xhtw552HuFRLWPraC
10
+ n+nEi91dXp/iQ5+kwpItfPIpeiVviP8tBa2uP85Jg81JFqiScPn74Txy5kOl0VBb
11
+ rF2ZOJMfiMmEh+o3SQNuSEZEA1d1mRHBPXv4RPWPQGAE4NPcxx7h/IkoJwvbyARB
12
+ JsKo9Vj16DfeES6EXgFN0o4JlNK1odNrDBpsozpQfIjSfjGFBWIcH4B6mzkf7i9Z
13
+ IIG/rQSAg74l2Wxpn9gzl1RwA37pO+dEDPyaYEYVLQ9FU1olTz4JOkkFIzcPdzzU
14
+ 9tyBgnV6tD5dH+eiVnspoi03jnawuND1GvT5cMTsJZ9nBF2X9Xs=
15
+ =pgM/
16
+ -----END PGP SIGNATURE-----
@@ -0,0 +1,4 @@
1
+ untrusted comment: signature from minisign secret key
2
+ RUTAoUJ007VE3iYGRj5A7OsDwuEsL9FaIR++xdAbMGDp32tSvWaG7ZzBLx6M8qmRko1SSLrxDXtBjkwOBfQov/F6QG14XXC16gc=
3
+ trusted comment: mdmeld v0.1.0 2026-03-01T19:43:23Z
4
+ Bi2V0sxiiqXDQ3nbTg04qqmQoqbSugXUvsAQ71RKa4FyoEVVFZArf70khPVulpDnsaB77hCmLEASoSeQ0QlGBQ==
Binary file
@@ -0,0 +1,2 @@
1
+ untrusted comment: minisign public key DE44B5D37442A1C0
2
+ RWTAoUJ007VE3h8tbHlBCyk2+y0nn7kyA4QP34LTzdtk8M6A2sryQtZC
@@ -0,0 +1,64 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+
3
+ mQINBGi6McYBEADagDID4A5BInViwzyMuOsL8jz+bRnrmdX+hfq4E0Xg/MRSX8t7
4
+ Mp0hzP+6xP/PJwta0AbKlRtZPii6HV4+xQnduy7dr08AtaAKiWhIefrp5fZCgdNL
5
+ 1umlW6vB4QEu+0p4q5L97zxqrlsc/XFMH4YHTd5h2N/pZYENtJan1EWbldyCvOkf
6
+ e/6EpfZfnlOb0+HCeu2wC5wip5T8bJTN89n1hLTRKoN+xo7Bgj8o28fHfhEvbrrY
7
+ zSvUZ0Tax9VlnL/25bLTkseQcJx4Z4NHPUkb2mlFlXS7J2uRj9VUjDLojcYMQeWR
8
+ hved1rD+jr27gLGdSIK6RDkNSmYO2ncCi0qDDEE4qVK6i6LC0Bp7OZaJiOdcMcm1
9
+ FEhVaJkbnI6ujuDXEn9umsK3kdHdREwnFKGkv8VV97vQQ2J+R9ODJgjRDafGrcvr
10
+ yHdQLFH2VU5xOfh1R1IJ48ry6r060KZZTpF62MA8a5c3RI8ld4uv47j1SBXrtZRW
11
+ BOz/WWr0Y/Rp7RpShvifyt+czepdWc96jYPVG8d9qt7QGspb8dQQJi1yQwtk85hi
12
+ KTvyYJC9zBqPI4SlTu+fJcv+iBWvBOn7eK7pX0YDX+/nETROyxiSoPogHnLFN9Lp
13
+ aeOQl1AHvUsjN5gWiZLE6wOOcg+vSoz7i79LWgijdkI6fIMgFu3ff+mDkwARAQAB
14
+ tEgzIExlYXBzIEluZm9zZWMgVGVhbSAoUHVibGljL09TUyBDb21tdW5pY2F0aW9u
15
+ IEtleSkgPGluZm9zZWNAM2xlYXBzLm5ldD6JAlIEEwEIADwWIQSUu3gR1K1JsjEO
16
+ DAj6BlHekbgo7QUCaLoxxgIbAwUJA8JnAAQLCQgHBBUKCQgFFgIDAQACHgUCF4AA
17
+ CgkQ+gZR3pG4KO2IKQ/6A3QBVPi1QM1yWoLwxG/Z88wTJlzaK7OZ06OIiSbPsMC2
18
+ UGHp3Fla7dXiNoaL8vZ0l/gnB2uidHAb5ea+4oanUPn+BjGLzZ9rG5bVMN7pwMT9
19
+ H9HeiBTaM+Ssq88QwrcP7urioBbCK4ctMMnyVyThEETFeelPb9O9dINl99yO7mKG
20
+ vq3j65tPNyKeRLoVElANRPIDlU5kQdbMg8vpN+o2E2Msw88wvILSKvwvp40V5PW3
21
+ ZylNBOJ4VGN+DVAmaL7j3SfNnHWDSQoWmzLAAv94/ea3DoIVYAXgbMtwPeON5ifx
22
+ 6zYbJFc55jaOHmI31RkrLGX+SlgFvgquD9OXH88G7/OyhWHMdMg336D0uPUhdvyR
23
+ Ao6GceXV6F3NytUiPVUHglUn+eB2chLrABqUOIjsXCFy8ryDMs4IegEP0juYLN3Z
24
+ SVq2y3arO3TGAUvXkaI78jxNdbcm+nlKInMoQVaJOeLAjBe7FZFZiCsoEzewnpF+
25
+ miFEoAioYH1gTGvv5Oukzq2d3gV7x45moAwtf0hwQ8zBYzl7+itQOhuAyaOC4Gd6
26
+ rkwIEdUVY0trGYrmJ2iAFZROf9BjIvSoFoHZsaw6H1seoLnwU8S+chNfsV6E3xDJ
27
+ GEq3zniVdm54wiKG576ENi96GjhkrAC4CVY7Ev26lVXc/sF+XrTVwGbhEY7ovNC5
28
+ Ag0EaLo1WwEQAO/A3XAcuQExlt67PkmLfDix4uy6aTcFtFRvzbotksQv4ByVSXyF
29
+ qkm1QPm6cac5guWBec5HuNK+GOMnpfobAJgE0gfVPDH9MCMGG/vD5U/T2cEsDJk8
30
+ poCTZ/afIVV7Fo+gzOjmAv8HuuUfpnmZXLRYBjnEM92dVaBLqhVqeKTLfjRhtL8k
31
+ dci/IPsIroGqa7iyG+eCLoV7Qf1OSniSbAwWWa/pSVKhnhzR7qo6McaZxNDFcX8f
32
+ VIlKvDe5rZoLMtuVyXhaMU9BGdCdrVas+Rx0WDq4a86wZj5Oh+11mZBYG+W++dwo
33
+ CYX2HELyRiHIkxTt6/mErWSnp1ses7JeERBhGYuf5Wc6oT79RV0rBOg1bcl/EGjD
34
+ +M+11mlMtfDFhH+XEwejkYlltImBrf1xr5SkMyUDG2l58iweAYz3UjLO576SuATt
35
+ jp9qS1jd6Nl6YpwHlIT0kO6qJSlNnWZ17EoyRDE+agZW3PjyFVRTVB2kssGAWOKJ
36
+ DkOStCtiBd2e+c42et5YBbRR7+EHEVXPWjOEN/gs1pzKrzgL3RBXJXCmdaMI+y1Q
37
+ syX3+o4TRPTcDGK1gkNHPyTSgIgO06tWk07tFlx8H4n7TMC0io63pFUrDsoic7Mv
38
+ nEck5kZUA9m3oNcVE7DNBQuzZ7TEh5HCRlG61v2FZM+jjFEGQ7Y/8sCxABEBAAGJ
39
+ BHIEGAEIACYWIQSUu3gR1K1JsjEODAj6BlHekbgo7QUCaLo1WwIbAgUJAeEzgAJA
40
+ CRD6BlHekbgo7cF0IAQZAQgAHRYhBB7dpLzlSaLawiC+9y+fifdnPs9/BQJoujVb
41
+ AAoJEC+fifdnPs9/5oAP+wVxHjHppJ5EQHijHHfwQJz4B3euyykaSe5yGsEF6ARy
42
+ kn1+lvw8Kt9wNROE7YOXEPaGnSEHQ/OGtIH82R9bM/VlkiA8V5DmQaotk8HhDbsM
43
+ atWvag6GcN0ewNhHxOwNcvaKvRM635m3I/2nouLJnRLbQI4+a8o8cs+TBQw2EK+m
44
+ bRsRpPUrGqLdwBLvL/V7Owf2eDWDT/GC7FMqGvX+VbkZCEdXjjaMoy/6+YqQyNCg
45
+ mCep+l6722mtFDjvf52g2l/hSvxTpVDHSvfXD0pxeQqRftYlPEthcHNSvjT9jOXv
46
+ uRYXxTIERdM4Yn3+e3vTexX6irok8Ato64KY4GUt3HAPCOybbtnh4CxC23hIZ66X
47
+ q9qyyyVM0by50QOKUDi2Gj3iBLUmm9cXl1ZRrMqKPR2yUrFH7AZNbhnOC/fQ67D0
48
+ X9tGCyAXLPJY71blJq/AfH2iVgsMn+pfZEi8rDpee1Kcjr9t3jIfV2Pdsq2zqsfB
49
+ mPiwr3sPqU22HE+HUBC4Cby7/xAzSs4fWTjeZPFnbavqRHAJnX19kwxjzZUlYHhN
50
+ HaJKrY21XBa8DhzcVYtyAcZbWCvCEamVJCiO5wibR3ycNxKCHcP+9k7HQx7OoJ3M
51
+ MmELWNKScan4yjpv3m8vywdB3sTfbjZRSAmF7KSvn1JJPhUCse7+UZ4CfZp88Lno
52
+ bGQQAKzlYkuZ1/jO1a9FpE9aFrm99oNX94YpgyRpEd6nciliYofKAahMtKrC5bXr
53
+ AljFZwYD4DMeEelBs7cefjQccV3kfvQz8l0iAFhCQsJgiKChm/ifdSdBkZmqqDaH
54
+ /XxIF7/BwAU4rphsEoOtzF8pVcQbvHqSUQEgFoBZp4D3IOBnX5R5Lp1quDfqaoQf
55
+ IDyJs0xJTGMO/TV0dZ+VEe0DnXnipxCUTT4qYdTCL0F49JQuk3/ocTRki0Q9ZXqd
56
+ +uS+KYP6MevJEDPZoxH3WZFWQ5s40FRfM1uhw1KtrHUvBZw60xIneQMiOYTzoYmn
57
+ q8tEgVqHnYZwMxXtrCpVMMZ08uUj73b7KMjLWbMNpUByP/vO520zVZR29m9uKttZ
58
+ LjdSlBQM54qjgSaGgtmLPAKUzBhk89ri7MqFsyhUNGwyF9A+jaj+uj5lKopYUica
59
+ KNeLvK7nXiqee8UtyfbfU5aoW0Sirc3pCxqbytuJQPdYT+a420pGbJsWicPnRKjX
60
+ 9lnkv4KHqd6amS1VdMdZlBo4mEcVHg6XdDNeFcItUfdTLqJEyzon17SoK0nbbT8D
61
+ P8wRyMrGvliSZkT8ow+lBaoJx6NWxH0RwjB9iRqJLB3xg8184ns8hjbARObM1hYN
62
+ 3sSoKWhSR6seFSzFKM+IhxElJxOVGr1VMaOPVbCKpArT0eTn
63
+ =Ghyf
64
+ -----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,29 @@
1
+ ## Summary
2
+
3
+ First publishable release of mdmeld — a single npm package providing a programmatic API, CLI, and static web tool for packing directory trees into markdown archives designed for AI assistant consumption.
4
+
5
+ ## Highlights
6
+
7
+ ### Core library
8
+
9
+ Browser-safe, zero Node.js dependencies. Three functions: `pack()`, `unpack()`, `check()`. Dual exports (ESM + CJS) with TypeScript declarations. Works identically in Node.js and the browser.
10
+
11
+ ### CLI
12
+
13
+ Three commands: `pack` (directory to archive), `unpack` (archive to directory), `finalize` (compute hashes for AI-generated archives). Supports `.gitignore` and `.mdmeldignore` patterns.
14
+
15
+ ### Web tool
16
+
17
+ Static single-page app with drag-and-drop folder upload, copy-to-clipboard, and download. Dark terminal-aesthetic theme. Built with Vite (61KB JS, 4KB CSS).
18
+
19
+ ### Archive format v1.0
20
+
21
+ Human-readable markdown with YAML frontmatter. Position metadata enables AI assistants to navigate directly to specific files. Integrity hashes (xxh64 or sha256) for verification. Backtick fence width auto-resolution (4-8).
22
+
23
+ ## Breaking changes
24
+
25
+ None. Initial release.
26
+
27
+ ## Migration
28
+
29
+ N/A — initial release.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ unpack
4
+ } from "./chunk-UF532LMZ.js";
5
+ import "./chunk-UJZ43GZC.js";
6
+
7
+ // src/cli/commands/unpack.ts
8
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { dirname, join, resolve } from "path";
10
+ async function runUnpack(args) {
11
+ const filePath = resolve(args.file);
12
+ const content = readFileSync(filePath, "utf-8");
13
+ const outputDir = resolve(args.output ?? ".");
14
+ const result = await unpack(content);
15
+ for (const file of result.files) {
16
+ const outPath = join(outputDir, file.path);
17
+ mkdirSync(dirname(outPath), { recursive: true });
18
+ writeFileSync(outPath, file.content);
19
+ }
20
+ console.log(`Unpacked ${result.files.length} files \u2192 ${outputDir}`);
21
+ }
22
+ export {
23
+ runUnpack
24
+ };
25
+ //# sourceMappingURL=unpack-2BBZMGLH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/unpack.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { unpack } from \"../../core/index.js\";\n\nexport interface UnpackArgs {\n\tfile: string;\n\toutput?: string;\n}\n\nexport async function runUnpack(args: UnpackArgs): Promise<void> {\n\tconst filePath = resolve(args.file);\n\tconst content = readFileSync(filePath, \"utf-8\");\n\tconst outputDir = resolve(args.output ?? \".\");\n\n\tconst result = await unpack(content);\n\n\tfor (const file of result.files) {\n\t\tconst outPath = join(outputDir, file.path);\n\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\twriteFileSync(outPath, file.content);\n\t}\n\n\tconsole.log(`Unpacked ${result.files.length} files → ${outputDir}`);\n}\n"],"mappings":";;;;;;;AAAA,SAAS,WAAW,cAAc,qBAAqB;AACvD,SAAS,SAAS,MAAM,eAAe;AAQvC,eAAsB,UAAU,MAAiC;AAChE,QAAM,WAAW,QAAQ,KAAK,IAAI;AAClC,QAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,QAAM,YAAY,QAAQ,KAAK,UAAU,GAAG;AAE5C,QAAM,SAAS,MAAM,OAAO,OAAO;AAEnC,aAAW,QAAQ,OAAO,OAAO;AAChC,UAAM,UAAU,KAAK,WAAW,KAAK,IAAI;AACzC,cAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,kBAAc,SAAS,KAAK,OAAO;AAAA,EACpC;AAEA,UAAQ,IAAI,YAAY,OAAO,MAAM,MAAM,iBAAY,SAAS,EAAE;AACnE;","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "mdmeld",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Pack directory trees into a single markdown file for sharing with AI assistants",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/3leaps/mdmeld.git"
10
+ },
11
+ "keywords": [
12
+ "mdmeld",
13
+ "markdown",
14
+ "archive",
15
+ "pack",
16
+ "ai",
17
+ "collaboration"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "bin": {
23
+ "mdmeld": "./dist/cli/index.js"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/core/index.d.ts",
28
+ "import": "./dist/core/index.js",
29
+ "require": "./dist/core/index.cjs"
30
+ }
31
+ },
32
+ "main": "./dist/core/index.cjs",
33
+ "module": "./dist/core/index.js",
34
+ "types": "./dist/core/index.d.ts",
35
+ "files": [
36
+ "dist/",
37
+ "LICENSE",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "dev": "vite web",
43
+ "build:web": "vite build web",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "lint": "biome check src/ test/",
47
+ "lint:fix": "biome check --write src/ test/",
48
+ "check-types": "tsc --noEmit",
49
+ "check-all": "npm run check-types && npm run lint && npm run test"
50
+ },
51
+ "dependencies": {
52
+ "ignore": "^7.0.4",
53
+ "js-yaml": "^4.1.0",
54
+ "xxhash-wasm": "^1.1.0"
55
+ },
56
+ "devDependencies": {
57
+ "@biomejs/biome": "^2.2.5",
58
+ "@types/js-yaml": "^4.0.9",
59
+ "@types/node": "^22.0.0",
60
+ "tsup": "^8.3.0",
61
+ "typescript": "^5.7.2",
62
+ "vite": "^6.0.0",
63
+ "vitest": "^4.0.18"
64
+ }
65
+ }