tangled
alpha
login
or
join now
stevedylan.dev
/
sequoia
A CLI for publishing standard.site documents to ATProto
sequoia.pub
standard
site
lexicon
cli
publishing
21
fork
atom
overview
issues
3
pulls
1
pipelines
Compare changes
Choose any two refs to compare.
base:
main
chore/refactor-build-target
chore/frontmatter-config-updates
v0.2.0
v0.1.1
v0.1.0
compare:
main
chore/refactor-build-target
chore/frontmatter-config-updates
v0.2.0
v0.1.1
v0.1.0
go
+154
-101
12 changed files
expand all
collapse all
unified
split
bun.lock
packages
cli
package.json
src
commands
init.ts
inject.ts
publish.ts
sync.ts
index.ts
lib
atproto.ts
config.ts
credentials.ts
markdown.ts
tsconfig.json
+38
-10
bun.lock
···
24
24
},
25
25
"packages/cli": {
26
26
"name": "sequoia-cli",
27
27
-
"version": "0.0.6",
27
27
+
"version": "0.1.0",
28
28
"bin": {
29
29
-
"sequoia": "dist/sequoia",
29
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
35
+
"glob": "^13.0.0",
36
36
+
"mime-types": "^2.1.35",
37
37
+
"minimatch": "^10.1.1",
35
38
},
36
39
"devDependencies": {
37
37
-
"@types/bun": "latest",
40
40
+
"@types/mime-types": "^3.0.1",
41
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
195
+
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
196
196
+
197
197
+
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
198
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
535
+
"@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="],
536
536
+
527
537
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
528
538
529
529
-
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
539
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
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
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
1097
-
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1109
1109
+
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
1110
1110
+
1111
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
1117
+
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
1118
1118
+
1119
1119
+
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
1120
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=="],
···
1150
1168
1151
1169
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
1152
1170
1171
1171
+
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
1172
1172
+
1153
1173
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
1154
1174
1155
1175
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
···
1336
1356
1337
1357
"uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
1338
1358
1339
1339
-
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1359
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
1465
+
"bun-types/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1466
1466
+
1445
1467
"chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
1446
1468
1469
1469
+
"compressible/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1470
1470
+
1447
1471
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1448
1472
1449
1473
"create-vocs/@clack/prompts": ["@clack/prompts@0.7.0", "", { "dependencies": { "@clack/core": "^0.3.3", "is-unicode-supported": "*", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA=="],
···
1456
1480
1457
1481
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
1458
1482
1483
1483
+
"eval/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
1484
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
1503
+
"path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
1504
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
1510
1483
1483
-
"sequoia-cli/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
1484
1484
-
1485
1511
"vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1486
1512
1487
1513
"@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
1488
1514
1489
1515
"@shikijs/twoslash/twoslash/twoslash-protocol": ["twoslash-protocol@0.2.12", "", {}, "sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg=="],
1490
1516
1517
1517
+
"bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1518
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
1529
+
"eval/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1530
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
1506
-
1507
1507
-
"sequoia-cli/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
1508
1536
}
1509
1537
}
+10
-7
packages/cli/package.json
···
1
1
{
2
2
"name": "sequoia-cli",
3
3
"version": "0.1.0",
4
4
-
"module": "dist/index.js",
5
4
"type": "module",
6
5
"bin": {
7
7
-
"sequoia": "dist/sequoia"
6
6
+
"sequoia": "dist/index.js"
8
7
},
9
8
"files": [
10
9
"dist",
11
10
"README.md"
12
11
],
13
13
-
"main": "./dist/sequoia",
12
12
+
"main": "./dist/index.js",
14
13
"exports": {
15
15
-
".": "./dist/sequoia"
14
14
+
".": "./dist/index.js"
16
15
},
17
16
"scripts": {
18
18
-
"build": "bun build src/index.ts --compile --outfile dist/sequoia",
17
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
23
-
"@types/bun": "latest"
22
22
+
"@types/mime-types": "^3.0.1",
23
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
30
+
"@clack/prompts": "^1.0.0",
30
31
"cmd-ts": "^0.14.3",
31
31
-
"@clack/prompts": "^1.0.0"
32
32
+
"glob": "^13.0.0",
33
33
+
"mime-types": "^2.1.35",
34
34
+
"minimatch": "^10.1.1"
32
35
}
33
36
}
+18
-8
packages/cli/src/commands/init.ts
···
1
1
+
import * as fs from "fs/promises";
1
2
import { command } from "cmd-ts";
2
3
import {
3
4
intro,
···
16
17
import { createAgent, createPublication } from "../lib/atproto";
17
18
import type { FrontmatterMapping } from "../lib/types";
18
19
20
20
+
async function fileExists(filePath: string): Promise<boolean> {
21
21
+
try {
22
22
+
await fs.access(filePath);
23
23
+
return true;
24
24
+
} catch {
25
25
+
return false;
26
26
+
}
27
27
+
}
28
28
+
19
29
const onCancel = () => {
20
30
outro("Setup cancelled");
21
31
process.exit(0);
···
270
280
});
271
281
272
282
const configPath = path.join(process.cwd(), "sequoia.json");
273
273
-
await Bun.write(configPath, configContent);
283
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
286
-
await Bun.write(path.join(wellKnownDir, ".gitkeep"), "");
287
287
-
await Bun.write(wellKnownPath, publicationUri);
296
296
+
await fs.mkdir(wellKnownDir, { recursive: true });
297
297
+
await fs.writeFile(path.join(wellKnownDir, ".gitkeep"), "");
298
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
293
-
const gitignoreFile = Bun.file(gitignorePath);
294
304
const stateFilename = ".sequoia-state.json";
295
305
296
296
-
if (await gitignoreFile.exists()) {
297
297
-
const gitignoreContent = await gitignoreFile.text();
306
306
+
if (await fileExists(gitignorePath)) {
307
307
+
const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
298
308
if (!gitignoreContent.includes(stateFilename)) {
299
299
-
await Bun.write(
309
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
306
-
await Bun.write(gitignorePath, `${stateFilename}\n`);
316
316
+
await fs.writeFile(gitignorePath, `${stateFilename}\n`);
307
317
log.info(`Created .gitignore with ${stateFilename}`);
308
318
}
309
319
+11
-12
packages/cli/src/commands/inject.ts
···
1
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
4
-
import { Glob } from "bun";
5
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
100
-
const glob = new Glob("**/*.html");
101
101
-
const htmlFiles: string[] = [];
102
102
-
103
103
-
for await (const file of glob.scan(resolvedOutputDir)) {
104
104
-
htmlFiles.push(path.join(resolvedOutputDir, file));
105
105
-
}
101
101
+
const htmlFiles = await glob("**/*.html", {
102
102
+
cwd: resolvedOutputDir,
103
103
+
absolute: false,
104
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
118
-
for (const htmlPath of htmlFiles) {
117
117
+
for (const file of htmlFiles) {
118
118
+
const htmlPath = path.join(resolvedOutputDir, file);
119
119
// Try to match this HTML file to a published post
120
120
-
const relativePath = path.relative(resolvedOutputDir, htmlPath);
120
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
155
-
const file = Bun.file(htmlPath);
156
156
-
let content = await file.text();
155
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
187
-
await Bun.write(htmlPath, content);
186
186
+
await fs.writeFile(htmlPath, content);
188
187
log.success(` Injected into: ${relativePath}`);
189
188
injectedCount++;
190
189
}
+3
-2
packages/cli/src/commands/publish.ts
···
1
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
167
-
const imagePath = resolveImagePath(
168
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
194
-
await Bun.write(post.filePath, updatedContent);
195
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
+3
-3
packages/cli/src/commands/sync.ts
···
1
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
172
-
const file = Bun.file(filePath);
173
173
-
const content = await file.text();
173
173
+
const content = await fs.readFile(filePath, "utf-8");
174
174
const updated = updateFrontmatterWithAtUri(content, atUri);
175
175
-
await Bun.write(filePath, updated);
175
175
+
await fs.writeFile(filePath, updated);
176
176
log.message(` Updated: ${path.basename(filePath)}`);
177
177
}
178
178
s.stop("Frontmatter updated");
+1
-1
packages/cli/src/index.ts
···
1
1
-
#!/usr/bin/env bun
1
1
+
#!/usr/bin/env node
2
2
3
3
import { run, subcommands } from "cmd-ts";
4
4
import { authCommand } from "./commands/auth";
+20
-15
packages/cli/src/lib/atproto.ts
···
1
1
import { AtpAgent } from "@atproto/api";
2
2
+
import * as fs from "fs/promises";
2
3
import * as path from "path";
4
4
+
import * as mimeTypes from "mime-types";
3
5
import type { Credentials, BlogPost, BlobObject, PublisherConfig } from "./types";
4
6
import { stripMarkdownForText } from "./markdown";
5
7
8
8
+
async function fileExists(filePath: string): Promise<boolean> {
9
9
+
try {
10
10
+
await fs.access(filePath);
11
11
+
return true;
12
12
+
} catch {
13
13
+
return false;
14
14
+
}
15
15
+
}
16
16
+
6
17
export async function resolveHandleToPDS(handle: string): Promise<string> {
7
18
// First, resolve the handle to a DID
8
19
let did: string;
···
87
98
agent: AtpAgent,
88
99
imagePath: string
89
100
): Promise<BlobObject | undefined> {
90
90
-
const file = Bun.file(imagePath);
91
91
-
92
92
-
if (!(await file.exists())) {
101
101
+
if (!(await fileExists(imagePath))) {
93
102
return undefined;
94
103
}
95
104
96
105
try {
97
97
-
const imageBuffer = await file.arrayBuffer();
98
98
-
const mimeType = file.type || "application/octet-stream";
106
106
+
const imageBuffer = await fs.readFile(imagePath);
107
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
121
-
export function resolveImagePath(
130
130
+
export async function resolveImagePath(
122
131
ogImage: string,
123
132
imagesDir: string | undefined,
124
133
contentDir: string
125
125
-
): string | null {
134
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
132
-
try {
133
133
-
const stat = Bun.file(imagePath);
141
141
+
if (await fileExists(imagePath)) {
142
142
+
const stat = await fs.stat(imagePath);
134
143
if (stat.size > 0) {
135
144
return imagePath;
136
145
}
137
137
-
} catch {
138
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
149
-
try {
150
150
-
const stat = Bun.file(contentRelative);
156
156
+
if (await fileExists(contentRelative)) {
157
157
+
const stat = await fs.stat(contentRelative);
151
158
if (stat.size > 0) {
152
159
return contentRelative;
153
160
}
154
154
-
} catch {
155
155
-
// File doesn't exist
156
161
}
157
162
158
163
return null;
+15
-8
packages/cli/src/lib/config.ts
···
1
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
8
+
async function fileExists(filePath: string): Promise<boolean> {
9
9
+
try {
10
10
+
await fs.access(filePath);
11
11
+
return true;
12
12
+
} catch {
13
13
+
return false;
14
14
+
}
15
15
+
}
16
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
14
-
const file = Bun.file(configPath);
15
24
16
16
-
if (await file.exists()) {
25
25
+
if (await fileExists(configPath)) {
17
26
return configPath;
18
27
}
19
28
···
38
47
}
39
48
40
49
try {
41
41
-
const file = Bun.file(resolvedPath);
42
42
-
const content = await file.text();
50
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
112
-
const file = Bun.file(statePath);
113
120
114
114
-
if (!(await file.exists())) {
121
121
+
if (!(await fileExists(statePath))) {
115
122
return { posts: {} };
116
123
}
117
124
118
125
try {
119
119
-
const content = await file.text();
126
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
131
-
await Bun.write(statePath, JSON.stringify(state, null, 2));
138
138
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
132
139
}
133
140
134
141
export function getStatePath(configDir: string): string {
+15
-6
packages/cli/src/lib/credentials.ts
···
1
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
12
+
async function fileExists(filePath: string): Promise<boolean> {
13
13
+
try {
14
14
+
await fs.access(filePath);
15
15
+
return true;
16
16
+
} catch {
17
17
+
return false;
18
18
+
}
19
19
+
}
20
20
+
11
21
/**
12
22
* Load all stored credentials
13
23
*/
14
24
async function loadCredentialsStore(): Promise<CredentialsStore> {
15
15
-
const file = Bun.file(CREDENTIALS_FILE);
16
16
-
if (!(await file.exists())) {
25
25
+
if (!(await fileExists(CREDENTIALS_FILE))) {
17
26
return {};
18
27
}
19
28
20
29
try {
21
21
-
const content = await file.text();
30
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
40
-
await Bun.$`mkdir -p ${CONFIG_DIR}`;
41
41
-
await Bun.write(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
42
42
-
await Bun.$`chmod 600 ${CREDENTIALS_FILE}`;
49
49
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
50
50
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(store, null, 2));
51
51
+
await fs.chmod(CREDENTIALS_FILE, 0o600);
43
52
}
44
53
45
54
/**
+9
-9
packages/cli/src/lib/markdown.ts
···
1
1
+
import * as fs from "fs/promises";
1
2
import * as path from "path";
2
2
-
import { Glob } from "bun";
3
3
+
import { glob } from "glob";
4
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
123
-
const glob = new Glob(pattern);
124
124
-
if (glob.match(relativePath)) {
125
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
140
-
const glob = new Glob(pattern);
141
141
-
142
142
-
for await (const relativePath of glob.scan({
141
141
+
const files = await glob(pattern, {
143
142
cwd: contentDir,
144
143
absolute: false,
145
145
-
})) {
144
144
+
});
145
145
+
146
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
152
-
const file = Bun.file(filePath);
153
153
-
const rawContent = await file.text();
153
153
+
const rawContent = await fs.readFile(filePath, "utf-8");
154
154
155
155
try {
156
156
const { frontmatter, body } = parseFrontmatter(rawContent, frontmatterMapping);
+11
-20
packages/cli/tsconfig.json
···
1
1
{
2
2
"compilerOptions": {
3
3
-
// Environment setup & latest features
4
4
-
"lib": ["ESNext"],
5
5
-
"target": "ESNext",
6
6
-
"module": "Preserve",
7
7
-
"moduleDetection": "force",
8
8
-
"jsx": "react-jsx",
9
9
-
"allowJs": true,
10
10
-
11
11
-
// Bundler mode
3
3
+
"lib": ["ES2022"],
4
4
+
"target": "ES2022",
5
5
+
"module": "ESNext",
12
6
"moduleResolution": "bundler",
13
13
-
"allowImportingTsExtensions": true,
14
14
-
"verbatimModuleSyntax": true,
15
15
-
"noEmit": true,
16
16
-
17
17
-
// Best practices
7
7
+
"outDir": "./dist",
8
8
+
"rootDir": "./src",
9
9
+
"declaration": true,
10
10
+
"sourceMap": true,
18
11
"strict": true,
19
12
"skipLibCheck": true,
13
13
+
"esModuleInterop": true,
14
14
+
"resolveJsonModule": true,
15
15
+
"forceConsistentCasingInFileNames": true,
20
16
"noFallthroughCasesInSwitch": true,
21
17
"noUncheckedIndexedAccess": true,
22
22
-
"noImplicitOverride": true,
23
23
-
24
24
-
// Some stricter flags (disabled by default)
25
18
"noUnusedLocals": false,
26
26
-
"noUnusedParameters": false,
27
27
-
"noPropertyAccessFromIndexSignature": false,
28
28
-
"composite": true
19
19
+
"noUnusedParameters": false
29
20
},
30
21
"include": ["src"]
31
22
}