npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@projectpac/source-conversation-notes

v0.6.0

Published

Part of PAC: @projectpac/source-conversation-notes.

Readme

@projectpac/source-conversation-notes

The principal's regular Claude conversations, as sources this adapter keeps in its own folder -- by way of files Claude writes about itself.

@projectpac/source-agent-history ends its README naming the gap this fills: a node has no way to read claude.ai. Everything under ~/.claude is Claude Code, the CLI exposes no conversation retrieval, and the account's data export is the only route out. So the way around it is not to reach further. It is to ask the other side to write something down -- a skill, on a schedule, inside Claude -- and then to read a folder.

Why this is not a second agent-history

The two look alike and are opposite. agent-history reads raw transcripts and spends model runs turning them into a profile: ten runs over three quarters of a megabyte, $9.84 for a first pass, which is why it has no schedule and waits to be asked. Here a model already did that work, on the other side of the seam, and what lands on the machine is the conclusion. So this adapter injects no intelligence, spends nothing, and a poll is a directory listing -- which is what earns it a schedule where the other one cannot have any.

It copies an outside system's records rather than reasoning over them, in other words. The outside system is a folder.

One file per run

The skill writes one small file each time it runs, named for the moment it ran, holding nothing but topic paragraphs. It never edits or deletes one. That shape is the whole contract, and it is what this adapter used to lack.

It used to read one document with a section per day, which the skill appended to and revised in place. That document grew without bound, so it had to be split here -- along headings a model was asked to keep exactly, with a parser willing to be wrong about everything else -- and the schedule could not be finer than the heading: a day was the unit only because ## YYYY-MM-DD named one. A file per run leaves nothing to split and nothing to keep exact but a name, and the schedule is whatever the schedule is. The one document a flow might still want is assembled here, out of the files, rather than maintained over there.

What comes out

Two verbs, the same two dobj offers: inventory() answers with names and a sentence about each, and stage() copies the named ones into the calling plugin's folder and answers with relative paths. A flow learns what its principal has by asking, and gets bytes only by staging them somewhere it can point a box at.

There is no source registry to write into any more, so a source is a file in this adapter's own folder -- <dataDir>/conversation-notes/sources/ -- and these two verbs are how anything else learns it exists.

  • conversation-notes-<stamp>, one per note, for every note within keepDays of the newest. Its note says when it was written and names its topics, and quotes none of it. A note that ages out of the window stops being nameable -- a source a flow could stake that nothing refreshes is worse than none -- and its bytes here are deleted with it. The file the skill wrote is not touched.
  • conversation-notes, one document: every kept note, newest first, under a heading a person reads. Assembled here, so the skill writes and never edits, and the thing that wants one document is the thing that makes it.

Stamps name these, never topics. agent-history documents what the alternative costs: names taken from a model's prose drift between passes ("pikanet" became "Pikanet"), and the removal sweep reads a drifted name as one source vanishing and another appearing -- eleven deleted and recreated after two of two hundred and forty files had changed. A time cannot drift.

The skill

This is the half that runs inside Claude. Paste it into a Claude environment that has a filesystem and scheduled tasks -- Cowork, not plain chat, which has neither -- as daily-profile, run it once, then ask for a schedule: make a schedule to run this every hour.

---
name: daily-profile
description: Write a short note about what the user has talked about in their recent Claude conversations, as a new file in the conversation-notes plugin's own folder. Use when the user asks to update their conversation notes, profile their recent chats, or record what they have been working on.
---

# Daily Profile

Write a short note about what the user has talked about in their recent Claude
conversations, as a new file in the folder the node's `conversation-notes`
plugin reads.

## Where the notes go

The folder connected to this session, which is this plugin's own folder on the
node. Whoever asked for the skill names it when they ask -- a home is not always
`~/.pac` and the data directory under it is not always where a guess would put
it, so the path is given rather than worked out. Notes go in a `notes/`
subfolder of it; create that subfolder if it is not there yet.

Being given it is not a convenience. The run that matters is a scheduled one,
and a scheduled run happens in an isolated machine with that folder mounted and
nothing else on it: no node to ask, and no `pac` to ask with. A skill that
insists on looking the path up stops there and writes nothing, which is the one
outcome worse than writing to the wrong place, because it looks like nothing
happened.

Where there _is_ a node to ask -- a session on the same machine, run by hand --
the folder can be confirmed rather than trusted:

```bash
pac api GET /plugins
```

Take the `dir` of the row whose `plugin` is `conversation-notes`. If it
disagrees with the folder you were given, say so and write neither: notes in
two places are a log that is missing half of itself, whichever one gets read.

If no folder was named and there is no `pac` to ask, stop and say so rather than
picking somewhere -- a file in the wrong place is read by nothing.

## The format is a contract

These files are read by a program, not only by a person. Two things about them
are fixed, and getting either wrong costs the reader the note:

- The file name is the current time in UTC to the minute, with hyphens where
  the colon would be: `2026-09-07T11-16Z.md`. Nothing else in the name.
- A topic paragraph opens with its name in bold: `**The topic.** Then prose.`

Everything else is ordinary markdown and can be written however reads best. No
title, no heading, no footer: the file is its paragraphs.

## Each run is its own file

Write one new file per run and never edit, rename or delete an existing one.
If you have more to say about a topic that an earlier note covered, say it in
the new note. If nothing new has been discussed since the newest note, write no
file and say so -- an empty note is worse than none.

## What to write

Group by topic. Bold the topic name, then 2-4 sentences of substance: what the
thing is, what was decided, what is still open.

Cover only conversations since the time in the newest file's name. On a first
run, when the folder is empty, cover the last 7 days.

## Rules

- Never write API keys, tokens, passwords, or private keys into a note.
  Nothing downstream strips them: this rule is the whole of what stands between
  a secret and a file that becomes readable to a peer.
- Replace the user's home directory path with `~` anywhere it appears.
- Write the file only. Do not stage, commit, or push anything.

When done, report the path written and the topics recorded.

Where it lands, and why there

This adapter's own folder: <data dir>/conversation-notes/notes/, beside the sources/ it keeps there already. A dir in the row overrides it, and an absolute one is taken as given.

It used to sit beside the node's home, and the argument for that has since expired. It was: something outside the node writes these, and a folder a plugin owns is not a drop box for a program that has never heard of the node. Two things made that true, and both changed.

A writer could not find the folder. Composing <home>/data/<id> meant hardcoding the node's layout into something outside it, and getting it wrong for a daemon started bare, where PAC_DATA_DIR defaults to the home rather than <home>/data. GET /plugins now reports each plugin's dir, so a writer can be told.

And removal deleted the folder. Months of the principal's own account of their weeks were one Remove click from gone, for a plugin that only ever read them. The host now dispatches core/removing and deletes nothing, so what is here outlives the plugin and a later install of it finds the notes again.

What is left of the old reason is worth keeping in view: the folder is still this plugin's, and a program writing into it is being trusted with somewhere that is not its own. It is told where rather than guessing, which is the whole of the difference.

Not a path under ~ outside any home, either: that is shared by every node on the machine, so two of them would each claim the other's conversations as their principal's.

This used to be the private directory, and that mattered. The sandbox was denied it both ways, which made it the one place the node could read and a model's subprocess could not -- the right side of the line for a document that is the principal's account of their own week. There is no private directory now and the sandbox denies nothing, so that property is gone rather than moved. It comes back when per-folder permissioning does; until then, the notes are as readable to a turn this node runs as anything else under the home.

Getting the files here

Deliberately not this adapter's business. Its whole contract is that files appeared in a folder -- the same contract agent-history has with ~/.claude. Whether the skill writes there directly, something syncs them down from a remote sandbox, or the principal drops them in, the adapter sees a folder whose listing moved. Anything in the folder that is not named as a note -- a draft, an editor's backup, a subfolder -- is left alone without comment.

Configuration

| field | default | why | | -------------- | ------- | ------------------------------------------------------------------------------------------------------------ | | dir | notes | in this plugin's own folder -- see above. Relative resolves against it; absolute is taken as given | | maxFileBytes | 1 MiB | a note past this is a runaway rather than a summary; it is refused, once per version, and the others survive | | keepDays | 14 | how far back a note gets a source of its own, counted from the newest note rather than from the clock |

The poll is a minute, on a timer of the adapter's own: the folder moves at most hourly, so a minute picks a run up promptly and the other fifty-nine cost a listing each. A poll that finds nothing changed reads nothing and writes nothing, and one that finds a new note reads that note and rewrites the document -- never the other notes.

keepDays is counted from the newest note rather than from the clock so that a node that was off for a month does not wake up naming nothing, and so a suite can pin it.

What it does not do

No redaction. The skill's rules above are the only control. That is a deliberate choice and not the same one agent-history made -- it reads raw transcripts, where a secret arrives whether anyone meant it to or not, so it carries a redaction table as a backstop. Here each file is a summary a model wrote to instructions that tell it not to write secrets. If that assumption ever stops holding, the backstop belongs in the sdk rather than copied out of agent-history: two copies of a security control are two things to keep in step, and the sdk is where the one copy of a path check already lives.

Topic names still come out of the files and still reach a model, because a note does. Those are held to one line with no angle brackets -- not a filter on what a name says, only on what shape it can take.

And a flow that stages a source can read it. The bytes land in the flow's own folder, which is the only place a box can be pointed at, and nothing stops the flow reading what it put there. data.apply used to be what made "names what it cannot read" true; with that gone, staging is the honest shape, and this is the property it costs. It comes back with the check.

Material, for a plugin that reasons over it

@projectpac/source-intents reads the notes, and rather than stage the document and parse it, it asks: this adapter also implements @projectpac/material, so units() lists the kept notes and read() hands one over as observations, one per topic paragraph, each marked as a summary -- an assistant's account of the conversations, not the principal's words -- at the moment the note was written. units() syncs first, so a consumer sees the folder as it is rather than as the last tick found it. A note is never edited, so a unit whose text has not changed is one nothing has to reason over again. No redaction on the way, for the reason above; what is safe to put in a prompt is the consumer's question, and it is the consumer that answers it.

It also says when the folder moved. watch(listener) fires from the minute's poll whenever the listing changed -- a note landed, one was taken away -- and answers with the unsubscribe. It does not fire from a consumer's own units(), because a consumer that just listed the folder has already seen the change, and it does not fire for the first pass after activation, which is a baseline rather than a change. The poll rather than fs.watch, because the folder often does not exist yet when this activates and the skill writes at most hourly, so a minute of latency costs nothing a watcher would save.