sequoia-cli 0.5.1 → 0.5.2

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.
@@ -161,7 +161,7 @@ function storeSubscriberDid(did) {
161
161
  const expires = new Date(
162
162
  Date.now() + 365 * 24 * 60 * 60 * 1000,
163
163
  ).toUTCString();
164
- document.cookie = `sequoia_did=${encodeURIComponent(did)}; expires=${expires}; path=/; SameSite=Lax`;
164
+ document.cookie = `sequoia_did=${encodeURIComponent(did)}; Expires=${expires}; Path=/; SameSite=Lax; Secure`;
165
165
  } catch {
166
166
  // Cookie write may fail in some embedded contexts
167
167
  }
@@ -201,7 +201,7 @@ function getStoredSubscriberDid() {
201
201
  function clearSubscriberDid() {
202
202
  try {
203
203
  document.cookie =
204
- "sequoia_did=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax";
204
+ "sequoia_did=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax; Secure";
205
205
  } catch {
206
206
  // ignore
207
207
  }
package/dist/index.js CHANGED
@@ -104691,8 +104691,131 @@ function waitForCallback() {
104691
104691
 
104692
104692
  // src/commands/publish.ts
104693
104693
  var import_cmd_ts6 = __toESM(require_cjs(), 1);
104694
+ import * as fs15 from "node:fs/promises";
104695
+ import * as path12 from "node:path";
104696
+
104697
+ // src/lib/sync.ts
104694
104698
  import * as fs14 from "node:fs/promises";
104695
104699
  import * as path11 from "node:path";
104700
+ async function syncStateFromPDS(agent, config, configDir, options = {}) {
104701
+ const { updateFrontmatter = false, dryRun = false, quiet = false } = options;
104702
+ const documents = await listDocuments(agent, config.publicationUri);
104703
+ if (documents.length === 0) {
104704
+ if (!quiet) {
104705
+ R2.info("No documents found for this publication.");
104706
+ }
104707
+ return {
104708
+ state: await loadState(configDir),
104709
+ matchedCount: 0,
104710
+ unmatchedCount: 0,
104711
+ frontmatterUpdatesApplied: 0
104712
+ };
104713
+ }
104714
+ const contentDir = path11.isAbsolute(config.contentDir) ? config.contentDir : path11.join(configDir, config.contentDir);
104715
+ const localPosts = await scanContentDirectory(contentDir, {
104716
+ frontmatterMapping: config.frontmatter,
104717
+ ignorePatterns: config.ignore,
104718
+ slugField: config.frontmatter?.slugField,
104719
+ removeIndexFromSlug: config.removeIndexFromSlug,
104720
+ stripDatePrefix: config.stripDatePrefix
104721
+ });
104722
+ const postsByPath = new Map;
104723
+ for (const post of localPosts) {
104724
+ const postPath = resolvePostPath(post, config.pathPrefix, config.pathTemplate);
104725
+ postsByPath.set(postPath, post);
104726
+ }
104727
+ const state = await loadState(configDir);
104728
+ let matchedCount = 0;
104729
+ let unmatchedCount = 0;
104730
+ let frontmatterUpdatesApplied = 0;
104731
+ const frontmatterUpdates = [];
104732
+ if (!quiet) {
104733
+ R2.message(`
104734
+ Matching documents to local files:
104735
+ `);
104736
+ }
104737
+ for (const doc of documents) {
104738
+ const docPath = doc.value.path;
104739
+ const localPost = postsByPath.get(docPath);
104740
+ if (localPost) {
104741
+ matchedCount++;
104742
+ const relativeFilePath = path11.relative(configDir, localPost.filePath);
104743
+ if (!quiet) {
104744
+ R2.message(` ✓ ${doc.value.title}`);
104745
+ R2.message(` Path: ${docPath}`);
104746
+ R2.message(` URI: ${doc.uri}`);
104747
+ R2.message(` File: ${path11.basename(localPost.filePath)}`);
104748
+ }
104749
+ const needsFrontmatterUpdate = updateFrontmatter && localPost.frontmatter.atUri !== doc.uri;
104750
+ if (needsFrontmatterUpdate) {
104751
+ frontmatterUpdates.push({
104752
+ filePath: localPost.filePath,
104753
+ atUri: doc.uri,
104754
+ relativeFilePath
104755
+ });
104756
+ if (!quiet) {
104757
+ R2.message(` → Will update frontmatter`);
104758
+ }
104759
+ }
104760
+ let contentHash;
104761
+ if (needsFrontmatterUpdate) {
104762
+ const updatedContent = updateFrontmatterWithAtUri(localPost.rawContent, doc.uri);
104763
+ contentHash = await getContentHash(updatedContent);
104764
+ } else {
104765
+ contentHash = await getContentHash(localPost.rawContent);
104766
+ }
104767
+ state.posts[relativeFilePath] = {
104768
+ contentHash,
104769
+ atUri: doc.uri,
104770
+ lastPublished: doc.value.publishedAt
104771
+ };
104772
+ } else {
104773
+ unmatchedCount++;
104774
+ if (!quiet) {
104775
+ R2.message(` ✗ ${doc.value.title} (no matching local file)`);
104776
+ R2.message(` Path: ${docPath}`);
104777
+ R2.message(` URI: ${doc.uri}`);
104778
+ }
104779
+ }
104780
+ if (!quiet) {
104781
+ R2.message("");
104782
+ }
104783
+ }
104784
+ if (!quiet) {
104785
+ R2.message("---");
104786
+ R2.info(`Matched: ${matchedCount} documents`);
104787
+ if (unmatchedCount > 0) {
104788
+ R2.warn(`Unmatched: ${unmatchedCount} documents (exist on PDS but not locally)`);
104789
+ }
104790
+ }
104791
+ if (dryRun) {
104792
+ if (!quiet) {
104793
+ R2.info(`
104794
+ Dry run complete. No changes made.`);
104795
+ }
104796
+ return {
104797
+ state,
104798
+ matchedCount,
104799
+ unmatchedCount,
104800
+ frontmatterUpdatesApplied: 0
104801
+ };
104802
+ }
104803
+ await saveState(configDir, state);
104804
+ if (frontmatterUpdates.length > 0) {
104805
+ for (const { filePath, atUri } of frontmatterUpdates) {
104806
+ const content = await fs14.readFile(filePath, "utf-8");
104807
+ const updated = updateFrontmatterWithAtUri(content, atUri);
104808
+ await fs14.writeFile(filePath, updated);
104809
+ if (!quiet) {
104810
+ R2.message(` Updated: ${path11.basename(filePath)}`);
104811
+ }
104812
+ }
104813
+ frontmatterUpdatesApplied = frontmatterUpdates.length;
104814
+ }
104815
+ return { state, matchedCount, unmatchedCount, frontmatterUpdatesApplied };
104816
+ }
104817
+
104818
+ // src/commands/publish.ts
104696
104819
  var publishCommand = import_cmd_ts6.command({
104697
104820
  name: "publish",
104698
104821
  description: "Publish content to ATProto",
@@ -104720,7 +104843,7 @@ var publishCommand = import_cmd_ts6.command({
104720
104843
  process.exit(1);
104721
104844
  }
104722
104845
  const config = await loadConfig(configPath);
104723
- const configDir = path11.dirname(configPath);
104846
+ const configDir = path12.dirname(configPath);
104724
104847
  R2.info(`Site: ${config.siteUrl}`);
104725
104848
  R2.info(`Content directory: ${config.contentDir}`);
104726
104849
  let credentials = await loadCredentials(config.identity);
@@ -104770,10 +104893,36 @@ var publishCommand = import_cmd_ts6.command({
104770
104893
  const displayId = credentials.type === "oauth" ? credentials.handle || credentials.did : credentials.identifier;
104771
104894
  R2.info(`Tip: Add "identity": "${displayId}" to sequoia.json to use this by default.`);
104772
104895
  }
104773
- const contentDir = path11.isAbsolute(config.contentDir) ? config.contentDir : path11.join(configDir, config.contentDir);
104774
- const imagesDir = config.imagesDir ? path11.isAbsolute(config.imagesDir) ? config.imagesDir : path11.join(configDir, config.imagesDir) : undefined;
104775
- const state = await loadState(configDir);
104896
+ const contentDir = path12.isAbsolute(config.contentDir) ? config.contentDir : path12.join(configDir, config.contentDir);
104897
+ const imagesDir = config.imagesDir ? path12.isAbsolute(config.imagesDir) ? config.imagesDir : path12.join(configDir, config.imagesDir) : undefined;
104898
+ let state = await loadState(configDir);
104776
104899
  const s = Ie();
104900
+ let agent;
104901
+ if (config.autoSync !== false && Object.keys(state.posts).length === 0 && !dryRun) {
104902
+ const connectingTo = credentials.type === "oauth" ? credentials.handle : credentials.pdsUrl;
104903
+ s.start(`Connecting as ${connectingTo}...`);
104904
+ try {
104905
+ agent = await createAgent(credentials);
104906
+ s.stop(`Logged in as ${agent.did}`);
104907
+ } catch (error) {
104908
+ s.stop("Failed to login");
104909
+ R2.error(`Failed to login: ${error}`);
104910
+ process.exit(1);
104911
+ }
104912
+ try {
104913
+ s.start("Auto-syncing state from PDS...");
104914
+ const syncResult = await syncStateFromPDS(agent, config, configDir, {
104915
+ updateFrontmatter: true,
104916
+ quiet: true
104917
+ });
104918
+ s.stop(`Auto-synced ${syncResult.matchedCount} posts from PDS`);
104919
+ state = syncResult.state;
104920
+ } catch (error) {
104921
+ s.stop("Auto-sync failed");
104922
+ R2.warn(`Auto-sync failed: ${error instanceof Error ? error.message : String(error)}`);
104923
+ R2.warn("Continuing with empty state. Run 'sequoia sync' manually to fix.");
104924
+ }
104925
+ }
104777
104926
  s.start("Scanning for posts...");
104778
104927
  const posts = await scanContentDirectory(contentDir, {
104779
104928
  frontmatterMapping: config.frontmatter,
@@ -104791,7 +104940,7 @@ var publishCommand = import_cmd_ts6.command({
104791
104940
  continue;
104792
104941
  }
104793
104942
  const contentHash = await getContentHash(post.rawContent);
104794
- const relativeFilePath = path11.relative(configDir, post.filePath);
104943
+ const relativeFilePath = path12.relative(configDir, post.filePath);
104795
104944
  const postState = state.posts[relativeFilePath];
104796
104945
  if (force) {
104797
104946
  postsToPublish.push({
@@ -104829,7 +104978,7 @@ ${postsToPublish.length} posts to publish:
104829
104978
  cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
104830
104979
  for (const { post, action, reason } of postsToPublish) {
104831
104980
  const icon = action === "create" ? "+" : "~";
104832
- const relativeFilePath = path11.relative(configDir, post.filePath);
104981
+ const relativeFilePath = path12.relative(configDir, post.filePath);
104833
104982
  const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
104834
104983
  let bskyNote = "";
104835
104984
  if (blueskyEnabled) {
@@ -104861,16 +105010,17 @@ Bluesky posting: enabled (max age: ${maxAgeDays} days)`);
104861
105010
  Dry run complete. No changes made.`);
104862
105011
  return;
104863
105012
  }
104864
- const connectingTo = credentials.type === "oauth" ? credentials.handle : credentials.pdsUrl;
104865
- s.start(`Connecting as ${connectingTo}...`);
104866
- let agent;
104867
- try {
104868
- agent = await createAgent(credentials);
104869
- s.stop(`Logged in as ${agent.did}`);
104870
- } catch (error) {
104871
- s.stop("Failed to login");
104872
- R2.error(`Failed to login: ${error}`);
104873
- process.exit(1);
105013
+ if (!agent) {
105014
+ const connectingTo = credentials.type === "oauth" ? credentials.handle : credentials.pdsUrl;
105015
+ s.start(`Connecting as ${connectingTo}...`);
105016
+ try {
105017
+ agent = await createAgent(credentials);
105018
+ s.stop(`Logged in as ${agent.did}`);
105019
+ } catch (error) {
105020
+ s.stop("Failed to login");
105021
+ R2.error(`Failed to login: ${error}`);
105022
+ process.exit(1);
105023
+ }
104874
105024
  }
104875
105025
  let publishedCount = 0;
104876
105026
  let updatedCount = 0;
@@ -104883,7 +105033,7 @@ Dry run complete. No changes made.`);
104883
105033
  if (post.frontmatter.ogImage) {
104884
105034
  const imagePath = await resolveImagePath(post.frontmatter.ogImage, imagesDir, contentDir);
104885
105035
  if (imagePath) {
104886
- R2.info(` Uploading cover image: ${path11.basename(imagePath)}`);
105036
+ R2.info(` Uploading cover image: ${path12.basename(imagePath)}`);
104887
105037
  coverImage = await uploadImage(agent, imagePath);
104888
105038
  if (coverImage) {
104889
105039
  R2.info(` Uploaded image blob: ${coverImage.ref.$link}`);
@@ -104895,14 +105045,14 @@ Dry run complete. No changes made.`);
104895
105045
  let atUri;
104896
105046
  let contentForHash;
104897
105047
  let bskyPostRef;
104898
- const relativeFilePath = path11.relative(configDir, post.filePath);
105048
+ const relativeFilePath = path12.relative(configDir, post.filePath);
104899
105049
  const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
104900
105050
  if (action === "create") {
104901
105051
  atUri = await createDocument(agent, post, config, coverImage);
104902
105052
  s.stop(`Created: ${atUri}`);
104903
105053
  const updatedContent = updateFrontmatterWithAtUri(post.rawContent, atUri);
104904
- await fs14.writeFile(post.filePath, updatedContent);
104905
- R2.info(` Updated frontmatter in ${path11.basename(post.filePath)}`);
105054
+ await fs15.writeFile(post.filePath, updatedContent);
105055
+ R2.info(` Updated frontmatter in ${path12.basename(post.filePath)}`);
104906
105056
  contentForHash = updatedContent;
104907
105057
  publishedCount++;
104908
105058
  } else {
@@ -104951,7 +105101,7 @@ Dry run complete. No changes made.`);
104951
105101
  };
104952
105102
  } catch (error) {
104953
105103
  const errorMessage = error instanceof Error ? error.message : String(error);
104954
- s.stop(`Error publishing "${path11.basename(post.filePath)}"`);
105104
+ s.stop(`Error publishing "${path12.basename(post.filePath)}"`);
104955
105105
  R2.error(` ${errorMessage}`);
104956
105106
  errorCount++;
104957
105107
  }
@@ -104972,8 +105122,7 @@ Dry run complete. No changes made.`);
104972
105122
 
104973
105123
  // src/commands/sync.ts
104974
105124
  var import_cmd_ts7 = __toESM(require_cjs(), 1);
104975
- import * as fs15 from "node:fs/promises";
104976
- import * as path12 from "node:path";
105125
+ import * as path13 from "node:path";
104977
105126
  var syncCommand = import_cmd_ts7.command({
104978
105127
  name: "sync",
104979
105128
  description: "Sync state from ATProto to restore .sequoia-state.json",
@@ -104996,7 +105145,7 @@ var syncCommand = import_cmd_ts7.command({
104996
105145
  process.exit(1);
104997
105146
  }
104998
105147
  const config = await loadConfig(configPath);
104999
- const configDir = path12.dirname(configPath);
105148
+ const configDir = path13.dirname(configPath);
105000
105149
  R2.info(`Site: ${config.siteUrl}`);
105001
105150
  R2.info(`Publication: ${config.publicationUri}`);
105002
105151
  let credentials = await loadCredentials(config.identity);
@@ -105056,89 +105205,19 @@ var syncCommand = import_cmd_ts7.command({
105056
105205
  process.exit(1);
105057
105206
  }
105058
105207
  s.start("Fetching documents from PDS...");
105059
- const documents = await listDocuments(agent, config.publicationUri);
105060
- s.stop(`Found ${documents.length} documents on PDS`);
105061
- if (documents.length === 0) {
105062
- R2.info("No documents found for this publication.");
105063
- return;
105064
- }
105065
- const contentDir = path12.isAbsolute(config.contentDir) ? config.contentDir : path12.join(configDir, config.contentDir);
105066
- s.start("Scanning local content...");
105067
- const localPosts = await scanContentDirectory(contentDir, {
105068
- frontmatterMapping: config.frontmatter,
105069
- ignorePatterns: config.ignore,
105070
- slugField: config.frontmatter?.slugField,
105071
- removeIndexFromSlug: config.removeIndexFromSlug,
105072
- stripDatePrefix: config.stripDatePrefix
105208
+ const result = await syncStateFromPDS(agent, config, configDir, {
105209
+ updateFrontmatter,
105210
+ dryRun,
105211
+ quiet: false
105073
105212
  });
105074
- s.stop(`Found ${localPosts.length} local posts`);
105075
- const postsByPath = new Map;
105076
- for (const post of localPosts) {
105077
- const postPath = resolvePostPath(post, config.pathPrefix, config.pathTemplate);
105078
- postsByPath.set(postPath, post);
105079
- }
105080
- const state = await loadState(configDir);
105081
- const originalPostCount = Object.keys(state.posts).length;
105082
- let matchedCount = 0;
105083
- let unmatchedCount = 0;
105084
- const frontmatterUpdates = [];
105085
- R2.message(`
105086
- Matching documents to local files:
105087
- `);
105088
- for (const doc of documents) {
105089
- const docPath = doc.value.path;
105090
- const localPost = postsByPath.get(docPath);
105091
- if (localPost) {
105092
- matchedCount++;
105093
- R2.message(` ✓ ${doc.value.title}`);
105094
- R2.message(` Path: ${docPath}`);
105095
- R2.message(` URI: ${doc.uri}`);
105096
- R2.message(` File: ${path12.basename(localPost.filePath)}`);
105097
- const contentHash = await getContentHash(localPost.rawContent);
105098
- const relativeFilePath = path12.relative(configDir, localPost.filePath);
105099
- state.posts[relativeFilePath] = {
105100
- contentHash,
105101
- atUri: doc.uri,
105102
- lastPublished: doc.value.publishedAt
105103
- };
105104
- if (updateFrontmatter && localPost.frontmatter.atUri !== doc.uri) {
105105
- frontmatterUpdates.push({
105106
- filePath: localPost.filePath,
105107
- atUri: doc.uri
105108
- });
105109
- R2.message(` → Will update frontmatter`);
105110
- }
105111
- } else {
105112
- unmatchedCount++;
105113
- R2.message(` ✗ ${doc.value.title} (no matching local file)`);
105114
- R2.message(` Path: ${docPath}`);
105115
- R2.message(` URI: ${doc.uri}`);
105116
- }
105117
- R2.message("");
105118
- }
105119
- R2.message("---");
105120
- R2.info(`Matched: ${matchedCount} documents`);
105121
- if (unmatchedCount > 0) {
105122
- R2.warn(`Unmatched: ${unmatchedCount} documents (exist on PDS but not locally)`);
105123
- }
105124
- if (dryRun) {
105125
- R2.info(`
105126
- Dry run complete. No changes made.`);
105127
- return;
105128
- }
105129
- await saveState(configDir, state);
105130
- const newPostCount = Object.keys(state.posts).length;
105131
- R2.success(`
105132
- Saved .sequoia-state.json (${originalPostCount} → ${newPostCount} entries)`);
105133
- if (frontmatterUpdates.length > 0) {
105134
- s.start(`Updating frontmatter in ${frontmatterUpdates.length} files...`);
105135
- for (const { filePath, atUri } of frontmatterUpdates) {
105136
- const content = await fs15.readFile(filePath, "utf-8");
105137
- const updated = updateFrontmatterWithAtUri(content, atUri);
105138
- await fs15.writeFile(filePath, updated);
105139
- R2.message(` Updated: ${path12.basename(filePath)}`);
105213
+ s.stop(`Found documents on PDS`);
105214
+ if (!dryRun) {
105215
+ const stateCount = Object.keys(result.state.posts).length;
105216
+ R2.success(`
105217
+ Saved .sequoia-state.json (${stateCount} entries)`);
105218
+ if (result.frontmatterUpdatesApplied > 0) {
105219
+ R2.success(`Updated frontmatter in ${result.frontmatterUpdatesApplied} files`);
105140
105220
  }
105141
- s.stop("Frontmatter updated");
105142
105221
  }
105143
105222
  R2.success(`
105144
105223
  Sync complete!`);
@@ -105605,7 +105684,7 @@ Publish evergreen content to the ATmosphere
105605
105684
 
105606
105685
  > https://tangled.org/stevedylan.dev/sequoia
105607
105686
  `,
105608
- version: "0.5.1",
105687
+ version: "0.5.2",
105609
105688
  cmds: {
105610
105689
  add: addCommand,
105611
105690
  auth: authCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequoia-cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sequoia": "dist/index.js"