codex-relay 1.2.3 → 1.2.4

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.
Files changed (2) hide show
  1. package/dist/src.js +76 -7
  2. package/package.json +1 -1
package/dist/src.js CHANGED
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
4
4
  import qrcode from "qrcode-terminal";
5
5
  import { execFile, spawn } from "node:child_process";
6
6
  import fs, { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
7
- import { access, appendFile, copyFile, mkdir, open, readFile, readdir, stat, writeFile } from "node:fs/promises";
7
+ import { access, appendFile, copyFile, mkdir, open, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
8
8
  import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { z } from "zod";
@@ -728,8 +728,17 @@ async function listAvailableSkills(options) {
728
728
  }
729
729
  async function findSkillFiles(root) {
730
730
  const found = [];
731
+ const visitedDirectories = /* @__PURE__ */ new Set();
731
732
  async function walk(path, depth) {
732
733
  if (depth < 0) return;
734
+ let realDirectoryPath;
735
+ try {
736
+ realDirectoryPath = await realpath(path);
737
+ } catch {
738
+ return;
739
+ }
740
+ if (visitedDirectories.has(realDirectoryPath)) return;
741
+ visitedDirectories.add(realDirectoryPath);
733
742
  let entries;
734
743
  try {
735
744
  entries = await readdir(path, { withFileTypes: true });
@@ -738,20 +747,34 @@ async function findSkillFiles(root) {
738
747
  }
739
748
  for (const entry of entries) {
740
749
  const entryPath = join(path, entry.name);
741
- if (entry.isFile() && entry.name === "SKILL.md") {
750
+ const kind = await directoryEntryKind(entry, entryPath);
751
+ if (kind === "file" && entry.name === "SKILL.md") {
742
752
  found.push({
743
753
  ...root,
744
754
  path: entryPath
745
755
  });
746
756
  continue;
747
757
  }
748
- if (!entry.isDirectory() || entry.name === "node_modules") continue;
758
+ if (kind !== "directory" || entry.name === "node_modules") continue;
749
759
  await walk(entryPath, depth - 1);
750
760
  }
751
761
  }
752
762
  await walk(root.path, root.maxDepth);
753
763
  return found;
754
764
  }
765
+ async function directoryEntryKind(entry, entryPath) {
766
+ if (entry.isFile()) return "file";
767
+ if (entry.isDirectory()) return "directory";
768
+ if (!entry.isSymbolicLink()) return null;
769
+ try {
770
+ const target = await stat(entryPath);
771
+ if (target.isFile()) return "file";
772
+ if (target.isDirectory()) return "directory";
773
+ } catch {
774
+ return null;
775
+ }
776
+ return null;
777
+ }
755
778
  async function readSkill(entry) {
756
779
  let markdown;
757
780
  try {
@@ -777,7 +800,7 @@ async function readSkill(entry) {
777
800
  }
778
801
  function parseSkillMarkdown(markdown) {
779
802
  const frontmatter = parseFrontmatter(markdown);
780
- const heading = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim();
803
+ const heading = firstMarkdownHeading(markdownBody(markdown));
781
804
  return {
782
805
  description: frontmatter.description,
783
806
  displayName: heading ? cleanHeading(heading) : void 0,
@@ -789,13 +812,59 @@ function parseFrontmatter(markdown) {
789
812
  const end = markdown.indexOf("\n---", 4);
790
813
  if (end < 0) return {};
791
814
  const fields = {};
792
- for (const line of markdown.slice(4, end).split("\n")) {
793
- const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
815
+ const lines = markdown.slice(4, end).split("\n");
816
+ for (let index = 0; index < lines.length; index += 1) {
817
+ const match = lines[index].match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
794
818
  if (!match) continue;
795
- fields[match[1]] = unquoteYamlScalar(match[2].trim());
819
+ const key = match[1];
820
+ const value = match[2].trim();
821
+ if (isBlockScalar(value)) {
822
+ const blockLines = [];
823
+ while (index + 1 < lines.length && (lines[index + 1].trim() === "" || /^\s/.test(lines[index + 1]))) {
824
+ index += 1;
825
+ blockLines.push(lines[index].replace(/^\s+/, ""));
826
+ }
827
+ fields[key] = formatBlockScalar(value, blockLines);
828
+ continue;
829
+ }
830
+ fields[key] = unquoteYamlScalar(value);
796
831
  }
797
832
  return fields;
798
833
  }
834
+ function isBlockScalar(value) {
835
+ return [
836
+ "|",
837
+ "|-",
838
+ "|+",
839
+ ">",
840
+ ">-",
841
+ ">+"
842
+ ].includes(value);
843
+ }
844
+ function formatBlockScalar(marker, lines) {
845
+ const trimmedLines = lines.map((line) => line.trimEnd());
846
+ if (marker.startsWith("|")) return trimmedLines.join("\n").trim();
847
+ return trimmedLines.map((line) => line.trim()).filter(Boolean).join(" ");
848
+ }
849
+ function markdownBody(markdown) {
850
+ if (!markdown.startsWith("---\n")) return markdown;
851
+ const end = markdown.indexOf("\n---", 4);
852
+ if (end < 0) return markdown;
853
+ return markdown.slice(end + 4).replace(/^\r?\n/, "");
854
+ }
855
+ function firstMarkdownHeading(markdown) {
856
+ let fence;
857
+ for (const line of markdown.split("\n")) {
858
+ const fenceMatch = line.match(/^\s*(```|~~~)/);
859
+ if (fenceMatch) {
860
+ fence = fence ? void 0 : fenceMatch[1];
861
+ continue;
862
+ }
863
+ if (fence) continue;
864
+ const heading = line.match(/^#\s+(.+)$/)?.[1]?.trim();
865
+ if (heading) return heading;
866
+ }
867
+ }
799
868
  function unquoteYamlScalar(value) {
800
869
  if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
801
870
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-relay",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {