New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@dmthepm/commune

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dmthepm/commune

Maintain a personal wiki from markdown notes, dictated thoughts and everyday work with authoring tools, a shared link graph and portable Astro publishing.

latest
Source
npmnpm
Version
0.13.0
Version published
Weekly downloads
1.8K
355.53%
Maintainers
1
Weekly downloads
 
Created
Source

Commune

Keep your thinking connected.

Commune turns a folder of markdown notes into a personal wiki. Linked notes open side by side in sliding panes, each page lists what links to it, and every page has a markdown twin at the same address ending in .md. A CLI queries the link graph while you write. Optional agent skills turn a dictated dump into a revision you review. MIT. It runs devon.md.

Following links on devon.md. Hovering a link shows a preview, and clicking opens the note in a pane beside the last one.

Start a wiki in one paste. You need Node 22.12 or newer, npm and Git. Node 22.19 or newer avoids an npm EBADENGINE warning from one of Astro's dependencies.

git clone --depth 1 https://github.com/dmthepm/commune-wiki.git && node commune-wiki/scripts/create-wiki.mjs my-wiki && cd my-wiki && npm install && npm run dev

Open the address Astro prints. If the release that the clone pins is not on npm yet, the copy uses the latest published version and says so. The clone is only the source of the copy, and you can delete it afterward. To run the paste again, delete commune-wiki and my-wiki first, since both commands refuse to overwrite a folder. Run by a coding agent on macOS or Linux, Astro 7 starts the dev server in the background and prints its address as JSON. npx astro dev stop stops it. If you stop or have to guess, tell me where it broke. A failed attempt is useful.

Start a wiki

The starter is a separate project on the published package. Step by step, with a build and a check of the sample content before the dev server.

git clone https://github.com/dmthepm/commune-wiki.git
cd commune-wiki
node scripts/create-wiki.mjs ../my-wiki
cd ../my-wiki
npm install
npm run build
npm run verify
npm run dev

Open the local URL Astro prints. The starter instructions walk through editing a sample note, opening connected notes in panes, and checking backlinks and markdown twins. The copy command installs nothing. npm install runs in your new wiki, and the engine repository itself needs no install.

If you stop or have to guess, tell me where. Include the step, your versions and what happened. No private vault needed. An existing Astro 7 project can skip the starter and use the integration below. The agent skills install separately into a wiki you already have and do not create a site.

Vision and mission

Vision. People can keep their thinking connected, current and in their own hands, and share it in public as it develops.

Mission. Commune turns markdown notes, dictated thoughts and the residue of everyday work into a maintained personal wiki through a shared link graph, authoring tools and portable publishing.

Commune grew out of devon.md. Devon built his own site and recognised the structure afterward, then extracted its tools so someone else could build on them. It is an MIT project with no company behind it and no plans for monetization.

Install

You need an Astro 7 project on Node 22.12 or newer.

pnpm add @dmthepm/commune @astrojs/markdown-remark

This minimal integration produces plain HTML notes. For the assembled wiki interface, use the starter. Add three files, and your notes. astro.config.mjs, in full:

import { defineConfig } from 'astro/config';
import commune from '@dmthepm/commune/astro';
import { communeMarkdown } from '@dmthepm/commune/markdown';

const site = 'https://example.com';

export default defineConfig({
  site,
  markdown: { processor: communeMarkdown({ site }) },
  integrations: [commune()],
});

Astro 7 renders markdown with Sätteri and no longer installs the unified pipeline, so markdown.processor is where the wikilink plugins have to go. site is a parameter because the engine has no host of its own — it decides what counts as an external link against your origin, which you already declare once.

Commune reads markdown off disk, but Astro will not render a page for it until the collection is registered and something routes it — so copy these two files. src/content.config.ts:

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

export const collections = {
  notes: defineCollection({
    loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/notes' }),
    schema: z.object({
      title: z.string(),
      visibility: z.enum(['public', 'private', 'draft']).default('private'),
    }),
  }),
};

And src/pages/notes/[...slug].astro:

---
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const notes = await getCollection('notes', (note) => note.data.visibility === 'public');
  return notes.map((note) => ({ params: { slug: note.id }, props: { note } }));
}

const { note } = Astro.props;
const { Content } = await render(note);
---

<html lang="en">
  <head><meta charset="utf-8" /><title>{note.data.title}</title></head>
  <body><h1>{note.data.title}</h1><Content /></body>
</html>

Both are minimal examples. examples/starter provides a separate wiki project. tests/fixtures/consumer tests integration against this repository through a local file dependency; it is a development fixture, not a portable starter.

Notes go in src/content/notes/. commune check and the graph read frontmatter with the yaml package, the way Astro 7.3.8 and newer do. On older Astro the site reads it with js-yaml, so a handful of unusual values (0b11, unknown tags) can read differently in the site and in commune check; use Astro 7.3.8 or newer.

Two frontmatter fields are load-bearing:

---
title: "Hello"
visibility: "public"
---

A link to [[World]], and one to [Astro](https://astro.build).

title is what [[Hello]] matches on. For notes, visibility defaults to private, so only public is published. Add a second public note with title: "World" before building; the link then resolves and the paragraph renders as:

A link to <a href="/notes/world/" class="wikilink">World</a>, and one to
<a href="https://astro.build" target="_blank" rel="noopener noreferrer">Astro</a>.

That route is a starting point, not an interface. The package ships the mechanism and none of src/pages/ — the markup, the layout and the URL shape are yours to change, with Commune resolving links through its shared graph.

What ships

  • WikiLinks. Connect notes with [[Title]], using the target title verbatim and rewriting the sentence around it. The renderer also resolves aliases and [[Title|Display text]], but check and gate report these noncanonical spellings. A link that resolves to nothing stays plain text instead of rendering a dead anchor.
  • Heading links. [[Title#Some heading]] lands on that heading: the link ends in #some-heading, the same id every heading on the page is given. [[#Some heading]] links within the page it is written on. Write the title verbatim, as for any link: check and gate report [[beta#Intro]], an alias, or a label on a heading link, so there is no [[Title#Heading|label]]. Block references ([[Title^id]]) are not supported: they link to the note and no further, and neither do embeds (![[Title#Heading]]). Type the heading as it reads, since a percent-encoded heading in the brackets is not decoded. Type it as it renders, too: ## Fish &copy; chips is reached by [[Beta#Fish © chips]], and backticks around code in a heading cannot be typed inside the brackets.
  • Heading links in check. A heading that ends in an image gets an id with a trailing hyphen that no typed link can reach, so check warns on a link to it. check warns with broken-anchor when [[Title#Heading]] names a note that has no such heading, and reads [[#Heading]] against its own note. It reads the ids off the page rendered by the same processor as commune render, so any heading the page gives an id counts. When two headings in a note share a name, the link reaches the first, and check treats it as found. A heading named "Main" takes the id main, which the layout's <main> already uses, so a link to it lands on the layout instead.
  • Frontmatter warnings in check. The frontmatter is read by the yaml package the way Astro reads it. yaml reports some problems without failing, such as an unknown tag: src: !include x.md reads as the plain string x.md. Astro drops those warnings, and check reports each one as frontmatter-warning, a warning naming the file, the line and the yaml message. It changes no value, so the site and check still agree, and a note that is not public is not read.
  • Backlinks. The build writes backlinks.json — every entry with its inbound and outbound edges — to dist/ and public/. Backlinks.astro renders it on a page.
  • Markdown twins. Every published content entry gets its source written beside it, so /notes/hello/ also answers at /notes/hello.md. Entries in the content directories only — a hand-written route under src/pages/ has no source file to twin. Agents and readers get the same document without scraping HTML.
  • Alias routes. An entry can also render at other URLs, such as the home note at /. See Alias routes below.
  • External links. Anything off your site origin gets target="_blank" rel="noopener noreferrer" without you marking it up.
  • The graph as a library. @dmthepm/commune/graph exports the content loader, the link resolver and the graph builder. The Astro build and the CLI both call it. Sharing the resolver reduces duplicated link logic; it does not guarantee that links never break.
  • Updates. A fourth collection, src/content/updates/, for the dated entries that say what changed. Updates.astro renders the newest few as a card. See Updates below.
  • Dates with sources. updated: in frontmatter wins where you wrote one; where you did not, the date comes from the file's last commit, and from its mtime in a tree with no history. Every entry says which, so a page can show where the date came from. See Dates below.
  • A site-wide last-updated. The build writes site.json beside backlinks.json: the newest date across the whole wiki, which entry it belongs to, and the newest commit date whatever the entries claim.
  • Components and stylesheets. @dmthepm/commune/components/*.astro and @dmthepm/commune/styles/*.css, shipped as source. These are the components off my own site rather than a theme system — take them as a starting point, not an API.

Dates

Two frontmatter fields decide a page's dates, and neither is required:

---
created: 2025-10-09
updated: 2026-01-21
---

Where they are absent the engine reads the repository instead — updated is the file's last commit date, created its first — from one git log walk per build. A tree with no history at all (a tarball, a COPY in a Dockerfile, a host with no git) falls back to file mtimes rather than failing the build.

Every entry carries updatedSource, one of frontmatter, git, mtime or none, and modifiedInGit, which is the commit date whatever updated ended up being. The pair is the point: a note whose updated: says January and whose last commit was September changed on a day nobody wrote down, and a page that shows both says so.

Shallow clones

A shallow checkout produces no derived dates at all. In a --depth 1 clone every file's only commit is the one that was fetched, so every file would date from the day of the build — one confident wrong answer on every entry at once. The engine refuses it rather than reporting it: dates come from frontmatter only, entries without one get updatedSource: "none" and no date, and the build prints this once on stderr:

git history is shallow: dates come from frontmatter only. Fetch full history (fetch-depth: 0 / unshallow) to derive dates from commits.

The same refusal applies to file mtimes anywhere inside a repository, and to a file that has never been committed. Inside a checkout an mtime is the moment the file reached that disk — on CI, the moment of the build — so it is the same falsehood wearing a different hat. mtimes are used in one place only: a project that is not in a repository at all.

The fix is to fetch the history. On GitHub Actions, actions/checkout defaults to depth 1, so set it explicitly:

- uses: actions/checkout@v7
  with:
    fetch-depth: 0

On Cloudflare Workers Builds, check the build log for the warning above — if it is there, the clone was shallow. Whether Workers Builds exposes a clone-depth setting is not documented here; if it does not, an alternative is to keep updated: in frontmatter for anything whose date matters, which wins over history anyway. A build step that runs git fetch --unshallow before the build has the same effect wherever the build has network access and credentials for the repository.

Known gap: renames. History is read without --follow, so a file's derived created is the date of the commit that gave it its current path, not the date the writing began. Renaming a note therefore resets its created and leaves updated correct. --follow is per-file by design — it cannot be asked for in the single batched walk this uses — so the fix is a created: in frontmatter, which wins over history.

commune graph query --json carries all four fields. The build writes the site-wide version to site.json:

{
  "lastUpdated": "2026-09-02",
  "lastUpdatedPath": "/about-this-wiki/",
  "lastUpdatedSource": "frontmatter",
  "lastModifiedInGit": "2026-09-03",
  "entries": 12
}

It is a sibling of backlinks.json rather than a key inside it, because every top-level key of backlinks.json is a urlPath and its readers walk it as one. Generated, not committed: a date derived from history changes on the same commit that changes it, so a committed copy would be stale exactly when it mattered. Add public/site.json to your .gitignore.

Alias routes

A note can render at more than one URL. The usual case is a home note that lives at /notes/my-working-notes/ and is also the front page at /. Declare the extra URLs in frontmatter:

---
title: "My Working Notes"
routes: ["/"]
---

The key is routes, not aliases. aliases already means the other names a [[WikiLink]] resolves to, and a name is not an address. Keeping the two apart means neither has to guess which one a value is.

Each route has to be a site-absolute path in the form every canonical URL has: / for the home page, otherwise a leading and a trailing slash, as in /start/, with only letters, digits and . _ ~ - in each segment. /start, start/, /a/../b/, a query string, a fragment, a space or a % stops the build with an error that names the file and, where there is one, the spelling that would have worked.

What the engine does with a route:

  • One node. backlinks.json and site.json keep one entry for the note, at its canonical URL. A route is a second address, not a second note.
  • A twin at each route. The build writes the source file at toMarkdownPath of every route, so routes: ["/"] gives /index.md as well as /notes/my-working-notes.md. Both are the file, byte for byte. The dev server answers them too.
  • Listed on the entry. commune graph query --json returns routes on each entry, empty when there are none.
  • Links resolve. A markdown link to a route, [start](/start/), reaches the note that renders there, and the edge lands on the one node at its canonical URL.
  • Checked. commune check reports route-collision when a route is another entry's URL, when two entries declare the same route, or when two entries' own url values write one twin, such as /about and /about/. Addresses are compared by the markdown twin they write, so / and /index/ collide, and so do /about/ and a page with url: "/about". It is an error, because two entries would write the same file: the build refuses it with a message naming both files, the dev server refuses that .md request, commune gate fails, and commune rename --move-url will not create one.
  • Warned when unrendered. The build warns about a route that has no page in the output, since the engine writes the twin and not the page.

The engine does not write the page at the route. Pages are yours, so the route is a page you write, and the one thing it has to get right is rel=canonical. Search engines treat two URLs with the same content as one page and pick a winner, and rel=canonical is how you pick it. toCanonicalUrl from @dmthepm/commune/graph builds the absolute URL from the entry's own urlPath, never from the route being served:

---
// src/pages/index.astro
import { getCollection, render } from 'astro:content';
import { toCanonicalUrl, toMarkdownHref } from '@dmthepm/commune/graph';

const notes = await getCollection('notes', (note) => note.data.visibility === 'public');
const home = notes.find((note) => note.data.routes.includes('/'));
if (!home) throw new Error('No public note declares routes: ["/"]');
const { Content } = await render(home);
---
<head>
  <link rel="canonical" href={toCanonicalUrl(`/notes/${home.id}/`, Astro.site)} />
</head>
<Content />
<a href={toMarkdownHref('/')}>source</a>

The visibility filter matters: without it a private note that declares routes: ["/"] would be published at /. The /notes/${home.id}/ URL is for the notes collection; an entry in another collection uses its own urlPath. toCanonicalUrl throws when site is not set in the Astro config, and it does not support a base path: the canonical URL is built against the origin alone.

Add routes: z.array(z.string()).default([]) to the collection schema so note.data.routes exists. tests/fixtures/consumer is this page, built in CI.

Updates

A wiki's front door has to answer "what changed" before it answers anything else. Commune's answer is content: one dated entry per batch of work, in src/content/updates/, which the graph treats as a collection like any other — twins, backlinks, check, graph query --collection updates.

---
title: "New notes and a working loop"
date: 2026-09-03
summary: "Rewrote the home note and added two notes."
links:
  - Atomic Notes
  - /notes/evergreen-notes/
---

I rewrote the home note. [[Atomic Notes]] and [[Evergreen Notes]] are new.

links: is the one place in frontmatter where a bare string is a link. Everywhere else a link has to be spelled [[like this]]; a page's own url: would otherwise become a self-edge; but links: means nothing else, so a title or a site path both resolve and both become edges. Write it or don't: [[wikilinks]] in the body work the same way, and naming a page in both places is still one edge.

Register the collection alongside your notes in src/content.config.ts:

updates: defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/updates' }),
  schema: z.object({
    title: z.string(),
    date: z.string(),
    summary: z.string(),
    aiGenerated: z.boolean().default(false),
    links: z.array(z.string()).default([]),
  }),
}),

Then render the card wherever it belongs — the home page, an index, a sidebar:

---
import Updates from '@dmthepm/commune/components/Updates.astro';
---
<Updates limit={5} heading="Recent updates" />

It reads the collection at build time and emits markup. No fetch, no client script.

A feed

The engine ships no routes, so it ships no RSS either — a feed is a route, and routes are yours. It is two lines with @astrojs/rss, in src/pages/updates/rss.xml.ts:

import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
  const updates = await getCollection('updates');
  return rss({
    title: 'Updates',
    description: 'What changed',
    site: context.site,
    items: updates
      .sort((a, b) => b.data.date.localeCompare(a.data.date))
      .map((update) => ({
        title: update.data.title,
        description: update.data.summary,
        pubDate: new Date(`${update.data.date}T12:00:00Z`),
        link: `/updates/${update.id}/`,
      })),
  });
}

The CLI

Find connections and check your notes while you write, without building the site. The commune executable reads markdown directly from disk and answers without an Astro process running.

In a wiki, the executable lives in node_modules/.bin, so run it through npx. The paths below are the starter's sample notes.

npx commune check
npx commune graph query --collection notes --orphans
npx commune graph query --recent 7d
npx commune update --recent 7d
npx commune graph related src/content/notes/welcome.md
echo "a rough dump that mentions Connected notes" | npx commune graph related -
npx commune render src/content/notes/welcome.md
echo '[[Connected notes]]' | npx commune render -
npx commune rename "src/content/notes/Old Title.md" "src/content/notes/New Title.md" --dry-run
npx commune gate
VerbWhat it answers
graph queryEvery entry with its edges and dates. Filter with --collection, --tag, --status, --orphans, --deadends, --unreferenced, --recent.
graph related <path|text|->What this connects to. It takes stdin, so you can ask about a draft before it is a note. Titles are matched across whitespace and case, so a dictated "noon tide" still finds Noontide. mentions come from prose only, not link destinations, URLs with a scheme, www. addresses, autolinks or WikiLink targets, and each carries linked: true when the text already links that note.
render <path|->The markdown as HTML, through the site's own pipeline: WikiLinks resolved, external links marked. Takes stdin, so you can see a draft before it is a page.
updateScaffold a dated update entry from what changed. Prints it; --write files it.
rename <from> <to>Move a note and rewrite every link to it, in every form the graph reads. The URL stays unless --move-url. --dry-run prints the plan.
checkBroken links, broken heading links, duplicate names, ambiguous targets, non-canonical titles.
gateRun after a build, against the built site.

--orphans and --unreferenced are different questions and it is worth knowing which one you are asking. An orphan is isolated — nothing links to it and it links nowhere — which on a wiki where most notes link out returns almost nothing. --unreferenced is zero inbound with any outbound: the note you wrote, cited three others from, and never linked back to from anywhere. updates entries are left out of it, since a dated changelog entry is expected to have nothing pointing at it; --collection updates is how you say you meant them.

--recent takes 7d, 2w or a date, and reports the day it resolved to in the summary — which is what a weekly update job needs, since 7d means a different day tomorrow. Entries with no date at all are not returned: "unchanged since Monday" and "nobody knows" are different answers.

Every verb takes --json and emits one document on stdout with everything else on stderr. The human-readable text is the fallback rendering; the JSON is the contract.

render is the site's own markdown processor with no Astro process around it — the same communeMarkdown() the config hands to markdown.processor, so the HTML is the page's HTML rather than a lookalike. It needs the site's origin to decide which links are external: --site says it, and without one the Astro config is read for a site declaration, falling back to https://example.com with a line on stderr saying so. --json adds the document's links and the names among them that resolve to nothing, which the HTML cannot tell you — an unresolved WikiLink renders as plain text, exactly as it does on the site.

update is the only verb that can write, and it only does so when asked: without --write the entry goes to stdout, and with it the command refuses to overwrite an update that already exists. summary comes out empty — summarizing a week is a judgement, and the CLI has none.

rename is the second verb that writes, and the only safe way to rename a note: a file's name is its link target, so mv, git mv, an editor or obsidian rename leave every [[Old Name]] pointing at nothing. It rewrites [[Old]], [[Old|label]], [[Old#Heading]], [[Old^block]], ![[Old]], relative file links and frontmatter links: to the new name, keeping each link's label, subpath and embed !. Links spelled through an alias still resolve and are left alone, and code is never touched. If the title was the old filename it follows the new one; otherwise it is left as it is, and the output says so. Renaming is also a URL decision. By default the URL stays: when the new name would move it, a slug: pin holding the old slug is added to the note. With --move-url the URL follows the name, any pin is removed, and /old-url/ /new-url/ 301 lines (plus the .md twin) are appended to public/_redirects, which is created if missing. It refuses if <to> exists, if <from> is not a content entry, if <to> leaves the collection, or if the new name would collide with another entry's title or alias. It plans everything first, and --dry-run stops there.

When a file was renamed outside commune rename, check still reports the old [[links]] as broken-link, and when the root is in a git work tree it adds a hint to the finding, such as "Old.md looks renamed to New.md", taken from git's own rename detection (the work tree against HEAD, then the last commit). It matches the old file name, the title its old frontmatter carried (read with one git show per rename), and, for a link written as a URL, the URL the old file had from its old path, slug or url. A name or URL that two renames both claim gets no hint. It is a hint on the existing finding, not a new rule, and without git check behaves as before. When rename changes only the case of a name inside a git work tree, it prints a ready-to-paste git -C <root> mv -f <from> <to> line on stderr, quoted for a POSIX shell after a real run, because with core.ignorecase=true git otherwise keeps the old spelling.

Exit codes report whether the command finished, never what it found — 0 finished, 1 could not finish, 2 invalid invocation. Findings live in the payload. A command that exits non-zero because it found something is indistinguishable, to a shell, from one that crashed. gate is the one deliberate exception: a gate's entire job is a yes/no and a build has to stop on it, so gate exits 1 when the build it checked is wrong.

commune --help prints the full surface. commune --version prints the installed version, which is the way to know what you have.

Author with the skills

These optional skills require an existing Commune wiki and access to Claude Code or Codex. They do not install the engine or scaffold a site. Install the four authoring skills from this repository.

npx skills add dmthepm/commune-wiki -a claude-code codex -s '*' -y

That installs all four skills into the current project for Claude Code and Codex without prompts. Without the flags the installer asks, starts with no skills selected and lists Claude Code unticked among dozens of agents. Add -g to install for every project instead. They call the wiki's existing node_modules/.bin/commune directly, so authoring and publishing use the same CLI. They require version 0.4.0 or newer, check it first and install nothing themselves.

commune-setup runs once per wiki and writes its WRITING.md rules. commune-dump saves dictated or pasted text verbatim to dumps/<slug>.md and records connection candidates and the check baseline in dumps/<slug>.connect.md. Here <slug> includes the capture date.

commune-write asks one short round of editorial questions and waits for answers in dumps/<slug>.answers.md. It then drafts into the note and renders the original and draft side by side in dumps/<slug>.review.html for the author to review.

On the author's instruction, commune-ship compares finding identities against the baseline, files an update, builds, gates and verifies each new href and destination file. It commits according to WRITING.md's dumps.commit policy, opens a PR and records the receipt in dumps/<slug>.ship.md. That needs the wiki to be a Git repository with a GitHub remote and a way to open a pull request, such as gh signed in. The starter copy is neither, so run git init in it and push it to a repository of your own first. The author approves the content. This skill never merges. That boundary governs authored content, while code maintenance follows the repository's contribution rules.

The four skills, their tests and the WRITING.md template ship today. An end to end run of the installed skills on a fresh starter wiki, from dictation to a gated commit, passed on October 1, 2026. Pushing and opening the pull request were not part of that run.

Files and publishing

Commune is not a note-taking app. It works with markdown files on disk, written in whatever editor you like, and provides no editor, sync service, account or server of its own. The files remain yours whether or not you ever run it.

Only notes with visibility: public enter the graph and publishing output. Research, pages and updates are included regardless of visibility. Handoffs in dumps/ are committed by default under the writing policy. They are outside the engine's content collections and do not automatically become site pages.

What needs testing

The launch checklist tracks first-install, installed-skills and return-edit proof, plus browsing and editing in Obsidian. Features follow what those sessions show is needed. Work is tracked in the issues.

Deploy

The build output is dist/, a static directory with no runtime, so any static host serves it — see docs/hosting.md.

Working on Commune itself

Use Node 22.18+ (the current Node 22 release selected by .nvmrc) and pnpm 10. The repository runs TypeScript source directly in its tests. The published package has the lower Node 22.12+ minimum.

pnpm install
pnpm dev        # the engine's own wiki, for developing against
pnpm build      # compile lib/, build the site, then gate it
pnpm test       # node --test

pnpm test:consumer installs tests/fixtures/consumer against the working tree and builds it. This checks the package boundary locally. It does not substitute for an independent first install.

Run pnpm test:starter from the repository root to check the copier, destination safeguards and published dependency declarations. The registry install test is skipped by default. COMMUNE_STARTER_INSTALL=1 pnpm test:starter also copies the starter into a temporary directory, installs from npm, builds and verifies its output. Neither mode proves a timed first install on another machine.

CONTRIBUTING.md has the rest. Issues and questions go to the tracker.

Thanks

Thanks to Joel (@joedef) for the Windows path fix using fileURLToPath and the single site config idea, both from PR #1.

License

MIT — see LICENSE. Use it, change it, sell it. Keep the copyright notice; that is the whole obligation.

Keywords

astro

FAQs

Package last updated on 13 Oct 2026

Related posts