
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@echoes-io/utils
Advanced tools
Utilities and types for Echoes - Multi-POV storytelling platform. Provides shared timeline configuration, content parsing, word counting, and validation schemas.
Utilities and types for Echoes - a multi-POV digital storytelling platform.
This package provides shared utilities used across the Echoes ecosystem:
npm install @echoes-io/utils
Parse markdown files with YAML frontmatter to extract chapter metadata and content.
import { parseMarkdown, stripMarkdown } from '@echoes-io/utils';
const markdown = `---
pov: "alice"
title: "First Meeting"
date: "2024-01-01"
timeline: "main"
arc: "introduction"
episode: 1
part: 1
chapter: 1
summary: "Alice meets Bob for the first time"
location: "coffee shop"
outfit: "red dress"
kink: "slow burn"
---
# Chapter 1
Alice walked into the coffee shop...`;
const { metadata, content } = parseMarkdown(markdown);
console.log(metadata.pov); // "alice"
console.log(content); // "# Chapter 1\n\nAlice walked..."
// Remove markdown syntax
const plainText = stripMarkdown(content);
console.log(plainText); // "Chapter 1\n\nAlice walked..."
Functions:
parseMarkdown() - Extract frontmatter and content from markdownstripMarkdown() - Remove markdown syntax from textMetadata Fields:
pov - Point of view charactertitle - Chapter titledate - Chapter datetimeline - Timeline identifierarc - Story arcepisode, part, chapter - Numeric identifierssummary - Brief descriptionlocation - Setting locationoutfit - Character outfit (optional)kink - Content tags (optional)Calculate word count and reading statistics from markdown content.
import { getTextStats } from '@echoes-io/utils';
const markdown = `
# Chapter Title
This is **bold** and *italic* text with [a link](https://example.com).
`;
const stats = getTextStats(markdown);
console.log(stats);
// {
// words: 9,
// characters: 52,
// charactersNoSpaces: 43,
// paragraphs: 1,
// sentences: 1,
// readingTimeMinutes: 1
// }
Features:
stripMarkdown()Generate chapter files following Echoes naming conventions.
import { generateChapterFile } from '@echoes-io/utils';
const metadata = {
pov: 'Alice',
title: 'First Meeting',
date: '2024-01-01',
timeline: 'main',
arc: 'Introduction Arc',
episode: 1,
part: 1,
chapter: 5,
summary: 'Alice meets Bob for the first time',
location: 'Coffee Shop',
outfit: 'Red dress',
kink: 'Slow burn'
};
const file = generateChapterFile(metadata, 'Alice walked into the coffee shop...');
console.log(file.path);
// → content/introduction-arc/ep01-first-meeting/ep01-ch005-alice-first-meeting.md
console.log(file.content);
// → ---
// pov: "Alice"
// title: "First Meeting"
// episode: 1
// chapter: 5
// ...
// ---
//
// Alice walked into the coffee shop...
File Structure Convention:
content/
├── <arc-name>/
│ └── <ep01-episode-title>/
│ └── <ep01-ch001-pov-title>.md
Features:
This repository provides a reusable GitHub Actions workflow for processing and publishing timeline content.
.github/workflows/publish.yml:name: Publish Timeline Content
on:
push:
branches: [main]
paths: ['content/**/*.md']
workflow_dispatch:
jobs:
publish:
uses: echoes-io/utils/.github/workflows/publish-content.yml@main
with:
timeline-name: 'main' # Change this for each timeline
content-path: 'content/'
web-app-url: 'https://your-web-app.com'
secrets:
WEB_APP_TOKEN: ${{ secrets.WEB_APP_TOKEN }}
Add secret in your timeline repo:
WEB_APP_TOKEN with your web app authentication tokenOrganize content in your timeline repo:
timeline-repo/
├── content/
│ ├── arc1/
│ │ ├── chapter1.md
│ │ └── chapter2.md
│ └── arc2/
│ └── chapter3.md
└── .github/workflows/publish.yml
.md files in the specified directoryparseMarkdown()getTextStats()The workflow sends processed content as JSON:
[
{
"file": "content/arc1/chapter1.md",
"metadata": {
"pov": "alice",
"title": "First Meeting",
"timeline": "main",
"arc": "introduction",
"episode": 1,
"part": 1,
"chapter": 1
},
"content": "# Chapter 1\n\nContent here...",
"stats": {
"words": 150,
"readingTimeMinutes": 1
},
"lastModified": "2024-01-01T12:00:00.000Z"
}
]
utils/
├── lib/ # Source code
│ ├── index.ts # Public API exports
│ ├── types.ts # TypeScript interfaces
│ ├── markdown-parser.ts # Markdown parsing & stripping
│ ├── text-stats.ts # Text statistics calculation
│ └── path-utils.ts # Chapter file generation
├── test/ # Tests
│ ├── index.test.ts
│ ├── markdown-parser.test.ts
│ ├── text-stats.test.ts
│ └── path-utils.test.ts
└── package.json
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint code
npm run lint
# Fix linting issues
npm run lint:format
# Release (automated via GitHub Actions)
npm run release
This project uses Conventional Commits for automated versioning:
feat: - New features (minor version bump)fix: - Bug fixes (patch version bump)feat!: or BREAKING CHANGE: - Breaking changes (major version bump)docs:, style:, refactor:, test:, chore: - No version bumpExamples:
git commit -m "feat: add stripMarkdown function"
git commit -m "fix: handle empty frontmatter correctly"
git commit -m "feat!: change parseMarkdown return type"
lib/test/index.tsnpm testnpm run lint:formatgray-matter - Parse YAML frontmatter from markdownremove-markdown - Strip markdown syntax for text analysistypescript - Type checking and compilationvitest - Testing framework@biomejs/biome - Linting and formattinghusky - Git hookslint-staged - Pre-commit lintingMIT
FAQs
Utilities and types for Echoes - Multi-POV storytelling platform. Provides shared timeline configuration, content parsing, word counting, and validation schemas.
We found that @echoes-io/utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.