@dogsbay/commands 0.2.0-beta.153 → 0.2.0-beta.155

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/README.md CHANGED
@@ -18,6 +18,10 @@ Storage (injected I/O) NodeStorage | MemoryStorage | (Electron IPC …)
18
18
 
19
19
  - **Storage** is the only I/O boundary. Commands never touch `node:fs`.
20
20
  All paths are workspace-root-relative posix paths.
21
+ - **Browser-safe at import**: nothing reaches a Node built-in until a
22
+ `NodeStorage` is used (it loads `node:fs` then). With `MemoryStorage`
23
+ or a host's own `Storage`, the package bundles for a browser or Worker;
24
+ `tests/browser-bundle.test.ts` checks it.
21
25
  - **Indexes** are built in one pass: per-doc table (frontmatter,
22
26
  headings, anchors, links, includes, `key:` uses), topic-map table,
23
27
  key-definition table (from `*.keys.yaml`), and the inverted
@@ -14,9 +14,9 @@
14
14
  * H1) is refused with an issue, never guessed.
15
15
  */
16
16
  import { z } from "zod";
17
- import { createHash } from "node:crypto";
17
+ import { sha256Hex } from "../sha256.js";
18
18
  import { dump as dumpYaml, load as loadYaml } from "js-yaml";
19
- import { containerOpenBefore, fenceOpenBefore, slugifyHeadingText } from "@dogsbay/format-dogsbay-md";
19
+ import { containerOpenBefore, fenceOpenBefore, slugifyHeadingText } from "@dogsbay/format-dogsbay-md/browser";
20
20
  import { normalizeStoragePath } from "../storage.js";
21
21
  import { splitFrontmatter } from "../edits.js";
22
22
  import { defineCommand } from "../engine.js";
@@ -101,7 +101,7 @@ const isOutput = (x) => typeof x === "object" && x !== null && "data" in x;
101
101
  * the target changed in between (a human typing, another agent), the verb
102
102
  * refuses with the current hash instead of writing over the newer text.
103
103
  */
104
- export const contentHash = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
104
+ export const contentHash = (text) => sha256Hex(text).slice(0, 16);
105
105
  const sectionHash = (doc, s) => contentHash(doc.lines.slice(s.line, s.end).join("\n"));
106
106
  const fileHash = (doc) => contentHash(doc.frontmatter + doc.lines.join("\n"));
107
107
  const STALE_PREVIEW_LINES = 40;
@@ -8,7 +8,7 @@
8
8
  * changed since, like every other content verb.
9
9
  */
10
10
  import { z } from "zod";
11
- import { createHash } from "node:crypto";
11
+ import { sha256Hex } from "../sha256.js";
12
12
  import { includeDirectiveRe } from "@dogsbay/minja/browser";
13
13
  import { defineCommand } from "../engine.js";
14
14
  import { normalizeStoragePath } from "../storage.js";
@@ -18,7 +18,7 @@ const fail = (code, message, file, extra = {}) => ({
18
18
  issues: [{ severity: "error", code, message, ...(file ? { file } : {}) }],
19
19
  summary: message,
20
20
  });
21
- const hashOf = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
21
+ const hashOf = (text) => sha256Hex(text).slice(0, 16);
22
22
  /** Find destination tokens equal to `from` (exact, decoded or encoded twin) in the masked source. */
23
23
  const destinationOffsets = (masked, from) => {
24
24
  const variants = new Set([from]);
@@ -5,7 +5,7 @@
5
5
  * rewritten in the same run (see edits.ts).
6
6
  */
7
7
  import { z } from "zod";
8
- import { fenceOpenBefore } from "@dogsbay/format-dogsbay-md";
8
+ import { fenceOpenBefore } from "@dogsbay/format-dogsbay-md/browser";
9
9
  import { dirname } from "@dogsbay/format-topicmap";
10
10
  import { normalizeStoragePath } from "../storage.js";
11
11
  import { relativeHref, replaceLinkHrefs, replaceIncludePaths, replaceMapHref, rewriteInboundRefs, splitFrontmatter, } from "../edits.js";
@@ -7,7 +7,7 @@
7
7
  * cannot be repaired mechanically is issued, never silently dropped.
8
8
  */
9
9
  import { z } from "zod";
10
- import { fenceOpenBefore, slugifyHeadingText, transformProseLines } from "@dogsbay/format-dogsbay-md";
10
+ import { fenceOpenBefore, slugifyHeadingText, transformProseLines } from "@dogsbay/format-dogsbay-md/browser";
11
11
  import { dirname } from "@dogsbay/format-topicmap";
12
12
  import { normalizeStoragePath } from "../storage.js";
13
13
  import { relativeHref, replaceLinkHrefs, replaceIncludePaths, rewriteInboundRefs, splitFrontmatter, } from "../edits.js";
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { z } from "zod";
14
14
  import { defineCommand } from "../engine.js";
15
- import { dirname, join, normalize } from "node:path/posix";
15
+ import { dirname, join, normalize } from "../posix-path.js";
16
16
  const MAX_MARKDOWN = 2_000_000;
17
17
  /** Embedded image bytes per page, in total (the page may go into an agent's context). */
18
18
  const IMAGE_BUDGET = 4_000_000;
@@ -13,7 +13,7 @@ import { dirname as dirnameOf, joinPath } from "@dogsbay/format-topicmap";
13
13
  import { defineCommand } from "../engine.js";
14
14
  import { anchorAliases, anchorAliasTargets } from "../indexes.js";
15
15
  import { checkFrontmatter, frontmatterPolicyFromConfig, requiredFieldsFor, } from "@dogsbay/types";
16
- import { dirname as dirOf, relative as relPath } from "node:path/posix";
16
+ import { dirname as dirOf, relative as relPath } from "../posix-path.js";
17
17
  import { load as loadYaml } from "js-yaml";
18
18
  const input = z.object({}).strict();
19
19
  async function siteLayout(storage) {
package/dist/edits.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * whole documents — authored formatting survives.
8
8
  */
9
9
  import { includeDirectiveRe } from "@dogsbay/minja/browser";
10
- import { transformProseSegments } from "@dogsbay/format-dogsbay-md";
10
+ import { transformProseSegments } from "@dogsbay/format-dogsbay-md/browser";
11
11
  /**
12
12
  * Run a text edit over the PROSE of a markdown document only: the
13
13
  * frontmatter block, fenced code, and inline code spans pass through
package/dist/indexes.js CHANGED
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Everything here is pure over `Storage` — no node:fs, browser-safe.
11
11
  */
12
- import { dogsbayMdToTree, extractHeadings, isMinjaProcessedFence, transformCodeRegions } from "@dogsbay/format-dogsbay-md";
12
+ import { dogsbayMdToTree, extractHeadings, isMinjaProcessedFence, transformCodeRegions } from "@dogsbay/format-dogsbay-md/browser";
13
13
  import { includeDirectiveRe } from "@dogsbay/minja/browser";
14
14
  import { parseTopicMap, isTopicMapPath, isExternalHref, normalizePath, dirname, joinPath, } from "@dogsbay/format-topicmap";
15
15
  import { load as loadYaml } from "js-yaml";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The four node:path/posix functions the verbs use, without Node (so the package bundles for a
3
+ * browser or Worker). Same results as node:path/posix for workspace paths; `relative` takes two
4
+ * RELATIVE paths (Node would resolve them against the process's folder first).
5
+ * Checked against node:path/posix in tests/posix-path.test.ts.
6
+ */
7
+ export declare function normalize(path: string): string;
8
+ export declare function join(...parts: string[]): string;
9
+ /** Node's own algorithm, duplicate slashes and all (`a//b` → `a/`). */
10
+ export declare function dirname(path: string): string;
11
+ export declare function relative(from: string, to: string): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The four node:path/posix functions the verbs use, without Node (so the package bundles for a
3
+ * browser or Worker). Same results as node:path/posix for workspace paths; `relative` takes two
4
+ * RELATIVE paths (Node would resolve them against the process's folder first).
5
+ * Checked against node:path/posix in tests/posix-path.test.ts.
6
+ */
7
+ export function normalize(path) {
8
+ if (path === "")
9
+ return ".";
10
+ const absolute = path.startsWith("/");
11
+ const trailing = path.endsWith("/");
12
+ const out = [];
13
+ for (const seg of path.split("/")) {
14
+ if (seg === "" || seg === ".")
15
+ continue;
16
+ if (seg === "..") {
17
+ if (out.length && out[out.length - 1] !== "..")
18
+ out.pop();
19
+ else if (!absolute)
20
+ out.push("..");
21
+ }
22
+ else
23
+ out.push(seg);
24
+ }
25
+ let joined = out.join("/");
26
+ if (!joined && !absolute)
27
+ joined = ".";
28
+ if (joined && trailing)
29
+ joined += "/";
30
+ return absolute ? `/${joined}` : joined;
31
+ }
32
+ export function join(...parts) {
33
+ const given = parts.filter((p) => p !== "");
34
+ return given.length ? normalize(given.join("/")) : ".";
35
+ }
36
+ /** Node's own algorithm, duplicate slashes and all (`a//b` → `a/`). */
37
+ export function dirname(path) {
38
+ if (path === "")
39
+ return ".";
40
+ const hasRoot = path[0] === "/";
41
+ let end = -1;
42
+ let matchedSlash = true;
43
+ for (let i = path.length - 1; i >= 1; --i) {
44
+ if (path[i] === "/") {
45
+ if (!matchedSlash) {
46
+ end = i;
47
+ break;
48
+ }
49
+ }
50
+ else
51
+ matchedSlash = false;
52
+ }
53
+ if (end === -1)
54
+ return hasRoot ? "/" : ".";
55
+ if (hasRoot && end === 1)
56
+ return "//";
57
+ return path.slice(0, end);
58
+ }
59
+ export function relative(from, to) {
60
+ const segs = (p) => normalize(p).split("/").filter((s) => s !== "" && s !== ".");
61
+ const a = segs(from);
62
+ const b = segs(to);
63
+ let i = 0;
64
+ while (i < a.length && i < b.length && a[i] === b[i])
65
+ i++;
66
+ return [...a.slice(i).map(() => ".."), ...b.slice(i)].join("/");
67
+ }
@@ -0,0 +1 @@
1
+ export declare function sha256Hex(text: string): string;
package/dist/sha256.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SHA-256 of a string's UTF-8 bytes, as hex, synchronously and without Node: the content hashes
3
+ * the verbs compare (`contentHash`, link-retarget's) must be the same in a browser, a Worker and
4
+ * Node, and Web Crypto's digest is async. Checked against node:crypto in tests/sha256.test.ts.
5
+ */
6
+ const K = new Uint32Array([
7
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
8
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
9
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
10
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
11
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
12
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
13
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
14
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
15
+ ]);
16
+ const rotr = (x, n) => (x >>> n) | (x << (32 - n));
17
+ export function sha256Hex(text) {
18
+ const data = new TextEncoder().encode(text);
19
+ const bitLen = data.length * 8;
20
+ // Message + 0x80 + zeros + 64-bit length, to a multiple of 64 bytes
21
+ const padded = new Uint8Array((((data.length + 9 + 63) >> 6) << 6));
22
+ padded.set(data);
23
+ padded[data.length] = 0x80;
24
+ const view = new DataView(padded.buffer);
25
+ view.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000));
26
+ view.setUint32(padded.length - 4, bitLen >>> 0);
27
+ const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
28
+ const w = new Uint32Array(64);
29
+ for (let off = 0; off < padded.length; off += 64) {
30
+ for (let i = 0; i < 16; i++)
31
+ w[i] = view.getUint32(off + i * 4);
32
+ for (let i = 16; i < 64; i++) {
33
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
34
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
35
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
36
+ }
37
+ let [a, b, c, d, e, f, g, hh] = h;
38
+ for (let i = 0; i < 64; i++) {
39
+ const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;
40
+ const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
41
+ hh = g;
42
+ g = f;
43
+ f = e;
44
+ e = (d + t1) >>> 0;
45
+ d = c;
46
+ c = b;
47
+ b = a;
48
+ a = (t1 + t2) >>> 0;
49
+ }
50
+ h[0] += a;
51
+ h[1] += b;
52
+ h[2] += c;
53
+ h[3] += d;
54
+ h[4] += e;
55
+ h[5] += f;
56
+ h[6] += g;
57
+ h[7] += hh;
58
+ }
59
+ return Array.from(h, (x) => x.toString(16).padStart(8, "0")).join("");
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/commands",
3
- "version": "0.2.0-beta.153",
3
+ "version": "0.2.0-beta.155",
4
4
  "description": "Typed, headless-first command engine for the Dogsbay platform — one zod-defined command yields CLI, JSON-RPC, and MCP surfaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,17 +18,18 @@
18
18
  "devDependencies": {
19
19
  "@types/js-yaml": "^4.0.9",
20
20
  "@types/node": "^25.9.5",
21
+ "esbuild": "0.28.1",
21
22
  "typescript": "^5.9.3",
22
23
  "vitest": "^4.1.10"
23
24
  },
24
25
  "dependencies": {
25
26
  "js-yaml": "^5.2.1",
26
27
  "zod": "^4.4.3",
27
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.153",
28
- "@dogsbay/format-topicmap": "0.2.0-beta.153",
29
- "@dogsbay/minja": "0.2.0-beta.153",
30
- "@dogsbay/types": "0.2.0-beta.153",
31
- "@dogsbay/render": "0.2.0-beta.153"
28
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.155",
29
+ "@dogsbay/format-topicmap": "0.2.0-beta.155",
30
+ "@dogsbay/render": "0.2.0-beta.155",
31
+ "@dogsbay/types": "0.2.0-beta.155",
32
+ "@dogsbay/minja": "0.2.0-beta.155"
32
33
  },
33
34
  "license": "MIT",
34
35
  "repository": {