chore: refactored codebase to use node and fs instead of bun e3118b54
Steve · 2026-01-31 06:48 12 file(s) · +154 −101
bun.lock +38 −10
24 24
    },
25 25
    "packages/cli": {
26 26
      "name": "sequoia-cli",
27 -
      "version": "0.0.6",
27 +
      "version": "0.1.0",
28 28
      "bin": {
29 -
        "sequoia": "dist/sequoia",
29 +
        "sequoia": "dist/index.js",
30 30
      },
31 31
      "dependencies": {
32 32
        "@atproto/api": "^0.18.17",
33 33
        "@clack/prompts": "^1.0.0",
34 34
        "cmd-ts": "^0.14.3",
35 +
        "glob": "^13.0.0",
36 +
        "mime-types": "^2.1.35",
37 +
        "minimatch": "^10.1.1",
35 38
      },
36 39
      "devDependencies": {
37 -
        "@types/bun": "latest",
40 +
        "@types/mime-types": "^3.0.1",
41 +
        "@types/node": "^20",
38 42
      },
39 43
      "peerDependencies": {
40 44
        "typescript": "^5",
188 192
189 193
    "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
190 194
195 +
    "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
196 +
197 +
    "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
198 +
191 199
    "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
192 200
193 201
    "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
524 532
525 533
    "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
526 534
535 +
    "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="],
536 +
527 537
    "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
528 538
529 -
    "@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
539 +
    "@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="],
530 540
531 541
    "@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
532 542
834 844
835 845
    "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
836 846
847 +
    "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
848 +
837 849
    "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
838 850
839 851
    "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
1094 1106
1095 1107
    "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
1096 1108
1097 -
    "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1109 +
    "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
1110 +
1111 +
    "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
1098 1112
1099 1113
    "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
1100 1114
1101 1115
    "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
1102 1116
1117 +
    "minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
1118 +
1119 +
    "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
1120 +
1103 1121
    "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="],
1104 1122
1105 1123
    "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
1149 1167
    "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
1150 1168
1151 1169
    "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
1170 +
1171 +
    "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
1152 1172
1153 1173
    "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
1154 1174
1336 1356
1337 1357
    "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
1338 1358
1339 -
    "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1359 +
    "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
1340 1360
1341 1361
    "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="],
1342 1362
1442 1462
1443 1463
    "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
1444 1464
1465 +
    "bun-types/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1466 +
1445 1467
    "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
1468 +
1469 +
    "compressible/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1446 1470
1447 1471
    "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1448 1472
1456 1480
1457 1481
    "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
1458 1482
1483 +
    "eval/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1484 +
1459 1485
    "hast-util-from-dom/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
1460 1486
1461 1487
    "hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
1474 1500
1475 1501
    "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
1476 1502
1503 +
    "path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
1504 +
1477 1505
    "radix-ui/@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
1478 1506
1479 1507
    "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1480 1508
1481 1509
    "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1482 -
1483 -
    "sequoia-cli/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
1484 1510
1485 1511
    "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1486 1512
1488 1514
1489 1515
    "@shikijs/twoslash/twoslash/twoslash-protocol": ["twoslash-protocol@0.2.12", "", {}, "sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg=="],
1490 1516
1517 +
    "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1518 +
1491 1519
    "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1492 1520
1493 1521
    "create-vocs/@clack/prompts/@clack/core": ["@clack/core@0.3.5", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ=="],
1498 1526
1499 1527
    "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
1500 1528
1529 +
    "eval/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1530 +
1501 1531
    "hast-util-from-dom/hastscript/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
1502 1532
1503 1533
    "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
1504 1534
1505 1535
    "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1506 -
1507 -
    "sequoia-cli/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
1508 1536
  }
1509 1537
}
packages/cli/package.json +10 −7
1 1
{
2 2
	"name": "sequoia-cli",
3 3
	"version": "0.1.0",
4 -
	"module": "dist/index.js",
5 4
	"type": "module",
6 5
	"bin": {
7 -
		"sequoia": "dist/sequoia"
6 +
		"sequoia": "dist/index.js"
8 7
	},
9 8
	"files": [
10 9
		"dist",
11 10
		"README.md"
12 11
	],
13 -
	"main": "./dist/sequoia",
12 +
	"main": "./dist/index.js",
14 13
	"exports": {
15 -
		".": "./dist/sequoia"
14 +
		".": "./dist/index.js"
16 15
	},
17 16
	"scripts": {
18 -
		"build": "bun build src/index.ts --compile --outfile dist/sequoia",
17 +
		"build": "bun build src/index.ts --target node --outdir dist",
19 18
		"dev": "bun run build && bun link",
20 19
		"deploy": "bun run build && bun publish"
21 20
	},
22 21
	"devDependencies": {
23 -
		"@types/bun": "latest"
22 +
		"@types/mime-types": "^3.0.1",
23 +
		"@types/node": "^20"
24 24
	},
25 25
	"peerDependencies": {
26 26
		"typescript": "^5"
27 27
	},
28 28
	"dependencies": {
29 29
		"@atproto/api": "^0.18.17",
30 +
		"@clack/prompts": "^1.0.0",
30 31
		"cmd-ts": "^0.14.3",
31 -
		"@clack/prompts": "^1.0.0"
32 +
		"glob": "^13.0.0",
33 +
		"mime-types": "^2.1.35",
34 +
		"minimatch": "^10.1.1"
32 35
	}
33 36
}
packages/cli/src/commands/init.ts +18 −8
1 +
import * as fs from "fs/promises";
1 2
import { command } from "cmd-ts";
2 3
import {
3 4
	intro,
15 16
import { loadCredentials } from "../lib/credentials";
16 17
import { createAgent, createPublication } from "../lib/atproto";
17 18
import type { FrontmatterMapping } from "../lib/types";
19 +
20 +
async function fileExists(filePath: string): Promise<boolean> {
21 +
	try {
22 +
		await fs.access(filePath);
23 +
		return true;
24 +
	} catch {
25 +
		return false;
26 +
	}
27 +
}
18 28
19 29
const onCancel = () => {
20 30
	outro("Setup cancelled");
270 280
		});
271 281
272 282
		const configPath = path.join(process.cwd(), "sequoia.json");
273 -
		await Bun.write(configPath, configContent);
283 +
		await fs.writeFile(configPath, configContent);
274 284
275 285
		log.success(`Configuration saved to ${configPath}`);
276 286
283 293
		const wellKnownPath = path.join(wellKnownDir, "site.standard.publication");
284 294
285 295
		// Ensure .well-known directory exists
286 -
		await Bun.write(path.join(wellKnownDir, ".gitkeep"), "");
287 -
		await Bun.write(wellKnownPath, publicationUri);
296 +
		await fs.mkdir(wellKnownDir, { recursive: true });
297 +
		await fs.writeFile(path.join(wellKnownDir, ".gitkeep"), "");
298 +
		await fs.writeFile(wellKnownPath, publicationUri);
288 299
289 300
		log.success(`Created ${wellKnownPath}`);
290 301
291 302
		// Update .gitignore
292 303
		const gitignorePath = path.join(process.cwd(), ".gitignore");
293 -
		const gitignoreFile = Bun.file(gitignorePath);
294 304
		const stateFilename = ".sequoia-state.json";
295 305
296 -
		if (await gitignoreFile.exists()) {
297 -
			const gitignoreContent = await gitignoreFile.text();
306 +
		if (await fileExists(gitignorePath)) {
307 +
			const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
298 308
			if (!gitignoreContent.includes(stateFilename)) {
299 -
				await Bun.write(
309 +
				await fs.writeFile(
300 310
					gitignorePath,
301 311
					gitignoreContent + `\n${stateFilename}\n`,
302 312
				);
303 313
				log.info(`Added ${stateFilename} to .gitignore`);
304 314
			}
305 315
		} else {
306 -
			await Bun.write(gitignorePath, `${stateFilename}\n`);
316 +
			await fs.writeFile(gitignorePath, `${stateFilename}\n`);
307 317
			log.info(`Created .gitignore with ${stateFilename}`);
308 318
		}
309 319
packages/cli/src/commands/inject.ts +11 −12
1 +
import * as fs from "fs/promises";
1 2
import { command, flag, option, optional, string } from "cmd-ts";
2 3
import { log } from "@clack/prompts";
3 4
import * as path from "path";
4 -
import { Glob } from "bun";
5 +
import { glob } from "glob";
5 6
import { loadConfig, loadState, findConfig } from "../lib/config";
6 7
7 8
export const injectCommand = command({
97 98
		log.info(`Found ${pathToAtUri.size} published posts in state`);
98 99
99 100
		// Scan for HTML files
100 -
		const glob = new Glob("**/*.html");
101 -
		const htmlFiles: string[] = [];
102 -
103 -
		for await (const file of glob.scan(resolvedOutputDir)) {
104 -
			htmlFiles.push(path.join(resolvedOutputDir, file));
105 -
		}
101 +
		const htmlFiles = await glob("**/*.html", {
102 +
			cwd: resolvedOutputDir,
103 +
			absolute: false,
104 +
		});
106 105
107 106
		if (htmlFiles.length === 0) {
108 107
			log.warn(`No HTML files found in ${resolvedOutputDir}`);
115 114
		let skippedCount = 0;
116 115
		let alreadyHasCount = 0;
117 116
118 -
		for (const htmlPath of htmlFiles) {
117 +
		for (const file of htmlFiles) {
118 +
			const htmlPath = path.join(resolvedOutputDir, file);
119 119
			// Try to match this HTML file to a published post
120 -
			const relativePath = path.relative(resolvedOutputDir, htmlPath);
120 +
			const relativePath = file;
121 121
			const htmlDir = path.dirname(relativePath);
122 122
			const htmlBasename = path.basename(relativePath, ".html");
123 123
152 152
			}
153 153
154 154
			// Read the HTML file
155 -
			const file = Bun.file(htmlPath);
156 -
			let content = await file.text();
155 +
			let content = await fs.readFile(htmlPath, "utf-8");
157 156
158 157
			// Check if link tag already exists
159 158
			const linkTag = `<link rel="site.standard.document" href="${atUri}">`;
184 183
				`${indent}${linkTag}\n${indent}` +
185 184
				content.slice(headCloseIndex);
186 185
187 -
			await Bun.write(htmlPath, content);
186 +
			await fs.writeFile(htmlPath, content);
188 187
			log.success(`  Injected into: ${relativePath}`);
189 188
			injectedCount++;
190 189
		}
packages/cli/src/commands/publish.ts +3 −2
1 +
import * as fs from "fs/promises";
1 2
import { command, flag } from "cmd-ts";
2 3
import { select, spinner, log } from "@clack/prompts";
3 4
import * as path from "path";
164 165
        // Handle cover image upload
165 166
        let coverImage: BlobObject | undefined;
166 167
        if (post.frontmatter.ogImage) {
167 -
          const imagePath = resolveImagePath(
168 +
          const imagePath = await resolveImagePath(
168 169
            post.frontmatter.ogImage,
169 170
            imagesDir,
170 171
            contentDir
191 192
192 193
          // Update frontmatter with atUri
193 194
          const updatedContent = updateFrontmatterWithAtUri(post.rawContent, atUri);
194 -
          await Bun.write(post.filePath, updatedContent);
195 +
          await fs.writeFile(post.filePath, updatedContent);
195 196
          log.info(`  Updated frontmatter in ${path.basename(post.filePath)}`);
196 197
197 198
          // Use updated content (with atUri) for hash so next run sees matching hash
packages/cli/src/commands/sync.ts +3 −3
1 +
import * as fs from "fs/promises";
1 2
import { command, flag } from "cmd-ts";
2 3
import { select, spinner, log } from "@clack/prompts";
3 4
import * as path from "path";
169 170
    if (frontmatterUpdates.length > 0) {
170 171
      s.start(`Updating frontmatter in ${frontmatterUpdates.length} files...`);
171 172
      for (const { filePath, atUri } of frontmatterUpdates) {
172 -
        const file = Bun.file(filePath);
173 -
        const content = await file.text();
173 +
        const content = await fs.readFile(filePath, "utf-8");
174 174
        const updated = updateFrontmatterWithAtUri(content, atUri);
175 -
        await Bun.write(filePath, updated);
175 +
        await fs.writeFile(filePath, updated);
176 176
        log.message(`  Updated: ${path.basename(filePath)}`);
177 177
      }
178 178
      s.stop("Frontmatter updated");
packages/cli/src/index.ts +1 −1
1 -
#!/usr/bin/env bun
1 +
#!/usr/bin/env node
2 2
3 3
import { run, subcommands } from "cmd-ts";
4 4
import { authCommand } from "./commands/auth";
packages/cli/src/lib/atproto.ts +20 −15
1 1
import { AtpAgent } from "@atproto/api";
2 +
import * as fs from "fs/promises";
2 3
import * as path from "path";
4 +
import * as mimeTypes from "mime-types";
3 5
import type { Credentials, BlogPost, BlobObject, PublisherConfig } from "./types";
4 6
import { stripMarkdownForText } from "./markdown";
7 +
8 +
async function fileExists(filePath: string): Promise<boolean> {
9 +
  try {
10 +
    await fs.access(filePath);
11 +
    return true;
12 +
  } catch {
13 +
    return false;
14 +
  }
15 +
}
5 16
6 17
export async function resolveHandleToPDS(handle: string): Promise<string> {
7 18
  // First, resolve the handle to a DID
87 98
  agent: AtpAgent,
88 99
  imagePath: string
89 100
): Promise<BlobObject | undefined> {
90 -
  const file = Bun.file(imagePath);
91 -
92 -
  if (!(await file.exists())) {
101 +
  if (!(await fileExists(imagePath))) {
93 102
    return undefined;
94 103
  }
95 104
96 105
  try {
97 -
    const imageBuffer = await file.arrayBuffer();
98 -
    const mimeType = file.type || "application/octet-stream";
106 +
    const imageBuffer = await fs.readFile(imagePath);
107 +
    const mimeType = mimeTypes.lookup(imagePath) || "application/octet-stream";
99 108
100 109
    const response = await agent.com.atproto.repo.uploadBlob(
101 110
      new Uint8Array(imageBuffer),
118 127
  }
119 128
}
120 129
121 -
export function resolveImagePath(
130 +
export async function resolveImagePath(
122 131
  ogImage: string,
123 132
  imagesDir: string | undefined,
124 133
  contentDir: string
125 -
): string | null {
134 +
): Promise<string | null> {
126 135
  // Try multiple resolution strategies
127 136
  const filename = path.basename(ogImage);
128 137
129 138
  // 1. If imagesDir is specified, look there
130 139
  if (imagesDir) {
131 140
    const imagePath = path.join(imagesDir, filename);
132 -
    try {
133 -
      const stat = Bun.file(imagePath);
141 +
    if (await fileExists(imagePath)) {
142 +
      const stat = await fs.stat(imagePath);
134 143
      if (stat.size > 0) {
135 144
        return imagePath;
136 145
      }
137 -
    } catch {
138 -
      // File doesn't exist, continue
139 146
    }
140 147
  }
141 148
146 153
147 154
  // 3. Try relative to content directory
148 155
  const contentRelative = path.join(contentDir, ogImage);
149 -
  try {
150 -
    const stat = Bun.file(contentRelative);
156 +
  if (await fileExists(contentRelative)) {
157 +
    const stat = await fs.stat(contentRelative);
151 158
    if (stat.size > 0) {
152 159
      return contentRelative;
153 160
    }
154 -
  } catch {
155 -
    // File doesn't exist
156 161
  }
157 162
158 163
  return null;
packages/cli/src/lib/config.ts +15 −8
1 +
import * as fs from "fs/promises";
1 2
import * as path from "path";
2 3
import type { PublisherConfig, PublisherState, FrontmatterMapping } from "./types";
3 4
4 5
const CONFIG_FILENAME = "sequoia.json";
5 6
const STATE_FILENAME = ".sequoia-state.json";
6 7
8 +
async function fileExists(filePath: string): Promise<boolean> {
9 +
	try {
10 +
		await fs.access(filePath);
11 +
		return true;
12 +
	} catch {
13 +
		return false;
14 +
	}
15 +
}
16 +
7 17
export async function findConfig(
8 18
	startDir: string = process.cwd(),
9 19
): Promise<string | null> {
11 21
12 22
	while (true) {
13 23
		const configPath = path.join(currentDir, CONFIG_FILENAME);
14 -
		const file = Bun.file(configPath);
15 24
16 -
		if (await file.exists()) {
25 +
		if (await fileExists(configPath)) {
17 26
			return configPath;
18 27
		}
19 28
38 47
	}
39 48
40 49
	try {
41 -
		const file = Bun.file(resolvedPath);
42 -
		const content = await file.text();
50 +
		const content = await fs.readFile(resolvedPath, "utf-8");
43 51
		const config = JSON.parse(content) as PublisherConfig;
44 52
45 53
		// Validate required fields
109 117
110 118
export async function loadState(configDir: string): Promise<PublisherState> {
111 119
	const statePath = path.join(configDir, STATE_FILENAME);
112 -
	const file = Bun.file(statePath);
113 120
114 -
	if (!(await file.exists())) {
121 +
	if (!(await fileExists(statePath))) {
115 122
		return { posts: {} };
116 123
	}
117 124
118 125
	try {
119 -
		const content = await file.text();
126 +
		const content = await fs.readFile(statePath, "utf-8");
120 127
		return JSON.parse(content) as PublisherState;
121 128
	} catch {
122 129
		return { posts: {} };
128 135
	state: PublisherState,
129 136
): Promise<void> {
130 137
	const statePath = path.join(configDir, STATE_FILENAME);
131 -
	await Bun.write(statePath, JSON.stringify(state, null, 2));
138 +
	await fs.writeFile(statePath, JSON.stringify(state, null, 2));
132 139
}
133 140
134 141
export function getStatePath(configDir: string): string {
packages/cli/src/lib/credentials.ts +15 −6
1 +
import * as fs from "fs/promises";
1 2
import * as path from "path";
2 3
import * as os from "os";
3 4
import type { Credentials } from "./types";
8 9
// Stored credentials keyed by identifier
9 10
type CredentialsStore = Record<string, Credentials>;
10 11
12 +
async function fileExists(filePath: string): Promise<boolean> {
13 +
  try {
14 +
    await fs.access(filePath);
15 +
    return true;
16 +
  } catch {
17 +
    return false;
18 +
  }
19 +
}
20 +
11 21
/**
12 22
 * Load all stored credentials
13 23
 */
14 24
async function loadCredentialsStore(): Promise<CredentialsStore> {
15 -
  const file = Bun.file(CREDENTIALS_FILE);
16 -
  if (!(await file.exists())) {
25 +
  if (!(await fileExists(CREDENTIALS_FILE))) {
17 26
    return {};
18 27
  }
19 28
20 29
  try {
21 -
    const content = await file.text();
30 +
    const content = await fs.readFile(CREDENTIALS_FILE, "utf-8");
22 31
    const parsed = JSON.parse(content);
23 32
24 33
    // Handle legacy single-credential format (migrate on read)
37 46
 * Save the entire credentials store
38 47
 */
39 48
async function saveCredentialsStore(store: CredentialsStore): Promise<void> {
40 -
  await Bun.$`mkdir -p ${CONFIG_DIR}`;
41 -
  await Bun.write(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
42 -
  await Bun.$`chmod 600 ${CREDENTIALS_FILE}`;
49 +
  await fs.mkdir(CONFIG_DIR, { recursive: true });
50 +
  await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
51 +
  await fs.chmod(CREDENTIALS_FILE, 0o600);
43 52
}
44 53
45 54
/**
packages/cli/src/lib/markdown.ts +9 −9
1 +
import * as fs from "fs/promises";
1 2
import * as path from "path";
2 -
import { Glob } from "bun";
3 +
import { glob } from "glob";
4 +
import { minimatch } from "minimatch";
3 5
import type { PostFrontmatter, BlogPost, FrontmatterMapping } from "./types";
4 6
5 7
export function parseFrontmatter(content: string, mapping?: FrontmatterMapping): {
120 122
121 123
function shouldIgnore(relativePath: string, ignorePatterns: string[]): boolean {
122 124
  for (const pattern of ignorePatterns) {
123 -
    const glob = new Glob(pattern);
124 -
    if (glob.match(relativePath)) {
125 +
    if (minimatch(relativePath, pattern)) {
125 126
      return true;
126 127
    }
127 128
  }
137 138
  const posts: BlogPost[] = [];
138 139
139 140
  for (const pattern of patterns) {
140 -
    const glob = new Glob(pattern);
141 -
142 -
    for await (const relativePath of glob.scan({
141 +
    const files = await glob(pattern, {
143 142
      cwd: contentDir,
144 143
      absolute: false,
145 -
    })) {
144 +
    });
145 +
146 +
    for (const relativePath of files) {
146 147
      // Skip files matching ignore patterns
147 148
      if (shouldIgnore(relativePath, ignorePatterns)) {
148 149
        continue;
149 150
      }
150 151
151 152
      const filePath = path.join(contentDir, relativePath);
152 -
      const file = Bun.file(filePath);
153 -
      const rawContent = await file.text();
153 +
      const rawContent = await fs.readFile(filePath, "utf-8");
154 154
155 155
      try {
156 156
        const { frontmatter, body } = parseFrontmatter(rawContent, frontmatterMapping);
packages/cli/tsconfig.json +11 −20
1 1
{
2 2
  "compilerOptions": {
3 -
    // Environment setup & latest features
4 -
    "lib": ["ESNext"],
5 -
    "target": "ESNext",
6 -
    "module": "Preserve",
7 -
    "moduleDetection": "force",
8 -
    "jsx": "react-jsx",
9 -
    "allowJs": true,
10 -
11 -
    // Bundler mode
3 +
    "lib": ["ES2022"],
4 +
    "target": "ES2022",
5 +
    "module": "ESNext",
12 6
    "moduleResolution": "bundler",
13 -
    "allowImportingTsExtensions": true,
14 -
    "verbatimModuleSyntax": true,
15 -
    "noEmit": true,
16 -
17 -
    // Best practices
7 +
    "outDir": "./dist",
8 +
    "rootDir": "./src",
9 +
    "declaration": true,
10 +
    "sourceMap": true,
18 11
    "strict": true,
19 12
    "skipLibCheck": true,
13 +
    "esModuleInterop": true,
14 +
    "resolveJsonModule": true,
15 +
    "forceConsistentCasingInFileNames": true,
20 16
    "noFallthroughCasesInSwitch": true,
21 17
    "noUncheckedIndexedAccess": true,
22 -
    "noImplicitOverride": true,
23 -
24 -
    // Some stricter flags (disabled by default)
25 18
    "noUnusedLocals": false,
26 -
    "noUnusedParameters": false,
27 -
    "noPropertyAccessFromIndexSignature": false,
28 -
    "composite": true
19 +
    "noUnusedParameters": false
29 20
  },
30 21
  "include": ["src"]
31 22
}