Skip to main content
ClaudeWave

Address any node in your Markdown. A structured read/write engine for Markdown documents.

SubagentsOfficial Registry9 stars1 forks● TypeScriptMITUpdated 7d ago
ClaudeWave Trust Score
95/100
✓ Verified
Passed
  • ✓Open-source license (MIT)
  • ✓Actively maintained (<30d)
  • ✓Clear description
  • ✓Topics declared
  • ✓Documented (README)
Last scanned: 10/12/2026
Install as a Claude Code subagent
Method: Clone
Terminal
git clone https://github.com/Synexiom-Labs/nodiom && cp nodiom/*.md ~/.claude/agents/
1. Clone the repository and copy the agent .md definitions into ~/.claude/agents (or .claude/agents inside a project).
2. Start a new Claude Code session to load the agents.
3. Delegate work to them with the Task/Agent tool or by name.
Use cases

Subagents overview

# nodiom

**Address any node in your Markdown.**

[![npm version](https://img.shields.io/npm/v/@synexiom-labs%2Fnodiom)](https://www.npmjs.com/package/@synexiom-labs/nodiom)
[![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
[![test](https://img.shields.io/github/actions/workflow/status/Synexiom-Labs/nodiom/test.yml?label=tests)](https://github.com/Synexiom-Labs/nodiom/actions)

Markdown has structure. Every tool ignores it. Nodiom doesn't.

---

## The Problem

When an AI agent or any program needs to modify a Markdown document, it loads the whole file as a string, uses fragile regex to find the right section, splices in new content, and hopes the formatting didn't break.

Nodiom solves this by treating Markdown as what it actually is: a tree. Every heading creates a branch, every list item is a node. You address them with path-like selectors — the same way you'd query a DOM node or a database row.

---

## Quick Example

```typescript
import { Nodiom } from '@synexiom-labs/nodiom';

const doc = await Nodiom.fromFile('./project-wiki.md');

// Read everything under a heading
const tasks = doc.read('# Project Alpha > ## Active Tasks');

// Add a new task
doc.append('# Project Alpha > ## Active Tasks', '- [ ] Deploy to staging');

// Remove a completed item
doc.delete('# Project Alpha > ## Completed > li[0]');

// Save back to disk
await doc.save();
```

---

## Install

```bash
npm install @synexiom-labs/nodiom
```

**Requirements:** Node.js 18+

---

## Core Concepts: Selectors

A selector is a `" > "`-separated path of segments, each matching a node in the document's structure.

### Heading segments

```
"# Project Alpha"              → The H1 "Project Alpha" and all its content
"## Tasks"                     → Any top-level H2 named "Tasks"
"# Project Alpha > ## Tasks"   → The H2 "Tasks" nested under H1 "Project Alpha"
```

The depth of the `#` characters must match the heading depth in the document.

**Duplicate headings.** If the same heading appears more than once in a scope,
a bare segment is ambiguous and throws `AmbiguousSelectorError` — nodiom never
silently picks the first. Qualify it with its parent heading, or address one by
index, zero-based and negative-capable like `li[n]`:

```
"# Doc > ## Notes[0]"    → The first "## Notes" under "# Doc"
"# Doc > ## Notes[1]"    → The second
"# Doc > ## Notes[-1]"   → The last
```

The index must sit directly against the text. `"## References [1]"`, with a
space, is matched as a literal heading, and a heading that genuinely reads
`Tasks[1]` is always tried literally first.

**Frontmatter.** A YAML (`---`) or TOML (`+++`) block at the top of a document
is recognised as frontmatter. It never appears in `tree()`, can't be addressed
by a selector, and is left byte-identical by every edit.

### Element segments

```
"## Tasks > li[0]"     → First list item under ## Tasks
"## Tasks > li[-1]"    → Last list item (negative indexing)
"## Notes > p[0]"      → First paragraph under ## Notes
"## Arch > table[0]"   → First table under ## Architecture
"## Arch > code[0]"    → First code block under ## Architecture
```

Valid element types: `p`, `li`, `code`, `blockquote`, `table`, `hr`

### Content segments

An index is a position, not an identity. If another writer appends to the same
section between the moment you compute `li[2]` and the moment you write to it,
that index now points at a different node — and the write lands on the wrong
one, silently.

Address by content instead whenever more than one writer touches a document:

```
'## In Progress > li[contains="c_7f3a"]'   → The list item whose text contains "c_7f3a"
"## Notes > p[contains='TODO']"            → The paragraph containing "TODO"
```

The substring is matched against the node's own source text, and must match
**exactly one** node in scope. Matching several throws `AmbiguousSelectorError`
rather than picking the first — silently editing one of several candidates is
the failure this exists to prevent.

```js
import { AmbiguousSelectorError } from '@synexiom-labs/nodiom';

try {
  doc.delete('## In Progress > li[contains="c_7f3a"]');
} catch (e) {
  if (e instanceof AmbiguousSelectorError) {
    // Use a longer or more distinctive substring.
  }
}
```

### Heading scope semantics

"Content under `## Tasks`" means all nodes that appear after the `## Tasks` heading until the next heading of equal or lesser depth. This matches how you naturally read a Markdown document.

---

## API Reference

### Initialization

```typescript
// From a file
const doc = await Nodiom.fromFile('./wiki.md');

// From a file with advisory locking (for concurrent access)
const doc = await Nodiom.fromFile('./wiki.md', { lock: true, lockTimeout: 5000 });

// From a string (no filesystem — safe for serverless)
const doc = Nodiom.fromString(markdownContent);
```

### Reading

```typescript
// Returns the Markdown string of everything under the selector
const content: string = doc.read('# Project > ## Summary');

// Returns an array of list item strings
const items: string[] = doc.readList('# Project > ## Tasks');

// Returns metadata about the location
const info = doc.query('# Project > ## Tasks');
// { exists: true, type: 'heading', depth: 2, childCount: 7, index: 3 }

// Returns the structural outline of the document
const outline = doc.tree();
// [{ heading: 'Project', depth: 1, children: [{ heading: 'Tasks', depth: 2, children: [] }] }]
```

### Mutating

All mutation methods return `this`, so they can be chained.

```typescript
// Replace all content within a section (heading is preserved)
doc.write('# Project > ## Summary', 'The project is on track for Q3.');

// Append content after the last item in a section
doc.append('# Project > ## Tasks', '- [ ] Final review');

// Remove a node
doc.delete('# Project > ## Completed > li[0]');

// Chaining
doc
  .append('# Project > ## Tasks', '- [ ] Task A')
  .append('# Project > ## Tasks', '- [ ] Task B')
  .delete('# Project > ## Completed > li[-1]');
```

### Serialization & File I/O

```typescript
// Get the current document as a Markdown string
const output: string = doc.toString();

// Write back to the original file (releases lock if held)
await doc.save();

// Write to a new file
await doc.saveAs('./output.md');

// Explicitly release a lock
await doc.unlock();
```

### Error Handling

```typescript
import { SelectorNotFoundError, SelectorParseError, LockError } from '@synexiom-labs/nodiom';

try {
  doc.read('# Project > ## Taks'); // typo
} catch (e) {
  if (e instanceof SelectorNotFoundError) {
    console.log(e.selector);     // "# Project > ## Taks"
    console.log(e.suggestions);  // ["Tasks"] — fuzzy match
  }
}
```

---

## AI Agent Example

A LangChain-style agent maintaining a project wiki:

```typescript
import { Nodiom } from '@synexiom-labs/nodiom';

async function agentUpdateWiki(wikiPath: string, taskDescription: string) {
  // Load with lock — safe for concurrent agent access on the same machine
  const doc = await Nodiom.fromFile(wikiPath, { lock: true });

  // Add a new active task
  doc.append('## Tasks > ### Active', `- [ ] ${taskDescription}`);

  // Log the current task count
  const activeTasks = doc.readList('## Tasks > ### Active');
  console.log(`Active tasks: ${activeTasks.length}`);

  // Save and release the lock
  await doc.save();
}
```

---

## Concurrency

Nodiom includes advisory file locking via [`proper-lockfile`](https://github.com/moxystudio/node-proper-lockfile).

```typescript
const doc = await Nodiom.fromFile('./shared-wiki.md', { lock: true });
// Other Nodiom processes trying to lock this file will wait or throw LockError

doc.append('## Log', `- ${new Date().toISOString()} Agent B completed task`);
await doc.save(); // writes and releases the lock
```

**Scope of the concurrency guarantee:** Advisory locking prevents corruption from multiple Node.js processes on the **same machine**. For distributed multi-agent environments (multiple machines or serverless functions), use Nodiom Cloud (coming soon) which provides true distributed concurrency with structural merge semantics.

---

## Comparison

| Approach | Structured reads? | Structural writes? | Roundtrip fidelity | Serverless | Concurrent |
|---|---|---|---|---|---|
| `fs.readFile` + regex | No | Fragile | Depends on regex | No | No |
| `gray-matter` | Frontmatter only | Frontmatter only | Yes | Yes | No |
| Databases | No | No | No (loses Markdown) | Yes | Yes |
| Vector DBs | Semantic only | No | No | Yes | Yes |
| **Nodiom** | **Yes** | **Yes** | **Yes** | **Yes** | **Single-machine** |

---

## Roadmap

- **v0.2.0** — `insert()` with positional control (`before` / `after` a sibling), `diff()` structural diffing, glob selectors (`## *Tasks*`)
- **v0.3.0** — LangChain tool wrapper
- **v1.0.0** — Stable selector syntax, production-ready API
- **Nodiom Cloud** — Hosted HTTP API for serverless and distributed multi-agent use. No local setup required. [Join the waitlist →](https://nodiom.md#cloud)

---

## Contributing

Contributions welcome. Please open an issue before submitting a PR for non-trivial changes.

```bash
git clone https://github.com/synexiom-labs/nodiom
cd nodiom
npm install
npm test
```

---

## License

MIT — [Synexiom Labs Inc.](https://synexiomlabs.com)

*Born from the state layer powering Cortexiom's reasoning architecture.*
ai-agentastllmmarkdownmcpmdastnodejsremarkstructured-datatypescript

What people ask about nodiom

What is Synexiom-Labs/nodiom?

+

Synexiom-Labs/nodiom is subagents for the Claude AI ecosystem. Address any node in your Markdown. A structured read/write engine for Markdown documents. It has 9 GitHub stars and its last recorded update is dated 2026-10-04.

How do I install nodiom?

+

You can install nodiom by cloning the repository (https://github.com/Synexiom-Labs/nodiom) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is Synexiom-Labs/nodiom safe to use?

+

Our security agent has analyzed Synexiom-Labs/nodiom and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains Synexiom-Labs/nodiom?

+

Synexiom-Labs/nodiom is maintained by Synexiom-Labs. The last recorded GitHub activity is dated 2026-10-04, with 1 open issues.

Are there alternatives to nodiom?

+

Yes. On ClaudeWave you can browse similar subagents at /categories/agents, sorted by popularity or recent activity.

Deploy nodiom to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: Synexiom-Labs/nodiom
[![Featured on ClaudeWave](https://claudewave.com/api/badge/synexiom-labs-nodiom)](https://claudewave.com/repo/synexiom-labs-nodiom)
<a href="https://claudewave.com/repo/synexiom-labs-nodiom"><img src="https://claudewave.com/api/badge/synexiom-labs-nodiom" alt="Featured on ClaudeWave: Synexiom-Labs/nodiom" width="320" height="64" /></a>

More Subagents

nodiom alternatives