diff --git a/.gitignore b/.gitignore
index 1e09c80..9f82d74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 nak
+mnt
diff --git a/fs.go b/fs.go
new file mode 100644
index 0000000..b5845e4
--- /dev/null
+++ b/fs.go
@@ -0,0 +1,222 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/signal"
+	"strings"
+	"sync/atomic"
+	"syscall"
+	"time"
+
+	"github.com/colduction/nocopy"
+	"github.com/fatih/color"
+	"github.com/hanwen/go-fuse/v2/fs"
+	"github.com/hanwen/go-fuse/v2/fuse"
+	"github.com/nbd-wtf/go-nostr"
+	"github.com/nbd-wtf/go-nostr/nip19"
+	"github.com/urfave/cli/v3"
+)
+
+var fsCmd = &cli.Command{
+	Name:        "fs",
+	Usage:       "mount a FUSE filesystem that exposes Nostr events as files.",
+	Description: `(experimental)`,
+	ArgsUsage:   "<mountpoint>",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "pubkey",
+			Usage: "public key from where to to prepopulate directories",
+			Validator: func(pk string) error {
+				if nostr.IsValidPublicKey(pk) {
+					return nil
+				}
+				return fmt.Errorf("invalid public key '%s'", pk)
+			},
+		},
+	},
+	DisableSliceFlagSeparator: true,
+	Action: func(ctx context.Context, c *cli.Command) error {
+		mountpoint := c.Args().First()
+		if mountpoint == "" {
+			return fmt.Errorf("must be called with a directory path to serve as the mountpoint as an argument")
+		}
+
+		root := &NostrRoot{ctx: ctx, rootPubKey: c.String("pubkey")}
+
+		// create the server
+		log("- mounting at %s... ", color.HiCyanString(mountpoint))
+		timeout := time.Second * 120
+		server, err := fs.Mount(mountpoint, root, &fs.Options{
+			MountOptions: fuse.MountOptions{
+				Debug: isVerbose,
+				Name:  "nak",
+			},
+			AttrTimeout:  &timeout,
+			EntryTimeout: &timeout,
+			Logger:       nostr.DebugLogger,
+		})
+		if err != nil {
+			return fmt.Errorf("mount failed: %w", err)
+		}
+		log("ok\n")
+
+		// setup signal handling for clean unmount
+		ch := make(chan os.Signal, 1)
+		chErr := make(chan error)
+		signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
+		go func() {
+			<-ch
+			log("- unmounting... ")
+			err := server.Unmount()
+			if err != nil {
+				chErr <- fmt.Errorf("unmount failed: %w", err)
+			} else {
+				log("ok\n")
+				chErr <- nil
+			}
+		}()
+
+		// serve the filesystem until unmounted
+		server.Wait()
+		return <-chErr
+	},
+}
+
+type NostrRoot struct {
+	fs.Inode
+	rootPubKey string
+	ctx        context.Context
+}
+
+var _ = (fs.NodeOnAdder)((*NostrRoot)(nil))
+
+func (r *NostrRoot) OnAdd(context.Context) {
+	if r.rootPubKey == "" {
+		return
+	}
+
+	fl := sys.FetchFollowList(r.ctx, r.rootPubKey)
+
+	for _, f := range fl.Items {
+		h := r.NewPersistentInode(
+			r.ctx,
+			&NpubDir{pointer: nostr.ProfilePointer{PublicKey: f.Pubkey, Relays: []string{f.Relay}}},
+			fs.StableAttr{Mode: syscall.S_IFDIR},
+		)
+		npub, _ := nip19.EncodePublicKey(f.Pubkey)
+		r.AddChild(npub, h, true)
+	}
+}
+
+func (r *NostrRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
+	// check if we already have this npub
+	child := r.GetChild(name)
+	if child != nil {
+		return child, fs.OK
+	}
+
+	// if the name starts with "npub1" or "nprofile1", create a new npub directory
+	if strings.HasPrefix(name, "npub1") || strings.HasPrefix(name, "nprofile1") {
+		npubdir, err := NewNpubDir(name)
+		if err != nil {
+			return nil, syscall.ENOENT
+		}
+
+		return r.NewPersistentInode(
+			ctx,
+			npubdir,
+			fs.StableAttr{Mode: syscall.S_IFDIR},
+		), 0
+	}
+
+	return nil, syscall.ENOENT
+}
+
+type NpubDir struct {
+	fs.Inode
+	pointer nostr.ProfilePointer
+	ctx     context.Context
+	fetched atomic.Bool
+}
+
+func NewNpubDir(npub string) (*NpubDir, error) {
+	pointer, err := nip19.ToPointer(npub)
+	if err != nil {
+		return nil, err
+	}
+
+	pp, ok := pointer.(nostr.ProfilePointer)
+	if !ok {
+		return nil, fmt.Errorf("directory must be npub or nprofile")
+	}
+
+	return &NpubDir{pointer: pp}, nil
+}
+
+var _ = (fs.NodeOpendirer)((*NpubDir)(nil))
+
+func (n *NpubDir) Opendir(ctx context.Context) syscall.Errno {
+	if n.fetched.CompareAndSwap(true, true) {
+		return fs.OK
+	}
+
+	for ie := range sys.Pool.FetchMany(ctx, sys.FetchOutboxRelays(ctx, n.pointer.PublicKey, 2), nostr.Filter{
+		Kinds:   []int{1},
+		Authors: []string{n.pointer.PublicKey},
+	}, nostr.WithLabel("nak-fs-feed")) {
+		h := n.NewPersistentInode(
+			ctx,
+			&EventFile{ctx: ctx, evt: *ie.Event},
+			fs.StableAttr{
+				Mode: syscall.S_IFREG,
+				Ino:  hexToUint64(ie.Event.ID),
+			},
+		)
+		n.AddChild(ie.Event.ID, h, true)
+	}
+
+	return fs.OK
+}
+
+type EventFile struct {
+	fs.Inode
+	ctx context.Context
+	evt nostr.Event
+}
+
+var (
+	_ = (fs.NodeOpener)((*EventFile)(nil))
+	_ = (fs.NodeGetattrer)((*EventFile)(nil))
+)
+
+func (c *EventFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
+	out.Mode = 0444
+	out.Size = uint64(len(c.evt.String()))
+	ts := uint64(c.evt.CreatedAt)
+	out.Atime = ts
+	out.Mtime = ts
+	out.Ctime = ts
+
+	return fs.OK
+}
+
+func (c *EventFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
+	return nil, fuse.FOPEN_KEEP_CACHE, 0
+}
+
+func (c *EventFile) Read(
+	ctx context.Context,
+	fh fs.FileHandle,
+	dest []byte,
+	off int64,
+) (fuse.ReadResult, syscall.Errno) {
+	buf := c.evt.String()
+
+	end := int(off) + len(dest)
+	if end > len(buf) {
+		end = len(c.evt.Content)
+	}
+	return fuse.ReadResultData(nocopy.StringToByteSlice(c.evt.Content[off:end])), fs.OK
+}
diff --git a/go.mod b/go.mod
index 04a3fb1..a2f633d 100644
--- a/go.mod
+++ b/go.mod
@@ -8,15 +8,17 @@ require (
 	github.com/bep/debounce v1.2.1
 	github.com/btcsuite/btcd/btcec/v2 v2.3.4
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
+	github.com/colduction/nocopy v0.2.0
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
 	github.com/fatih/color v1.16.0
 	github.com/fiatjaf/eventstore v0.15.0
 	github.com/fiatjaf/khatru v0.16.0
+	github.com/hanwen/go-fuse/v2 v2.7.2
 	github.com/json-iterator/go v1.1.12
 	github.com/mailru/easyjson v0.9.0
 	github.com/mark3labs/mcp-go v0.8.3
 	github.com/markusmobius/go-dateparser v1.2.3
-	github.com/nbd-wtf/go-nostr v0.50.3
+	github.com/nbd-wtf/go-nostr v0.50.5
 	github.com/urfave/cli/v3 v3.0.0-beta1
 	golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
 )
@@ -28,8 +30,8 @@ require (
 	github.com/btcsuite/btcd v0.24.2 // indirect
 	github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
-	github.com/bytedance/sonic v1.12.10 // indirect
-	github.com/bytedance/sonic/loader v0.2.3 // indirect
+	github.com/bytedance/sonic v1.13.1 // indirect
+	github.com/bytedance/sonic/loader v0.2.4 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/chzyer/logex v1.1.10 // indirect
 	github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
@@ -50,10 +52,10 @@ require (
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.10 // indirect
+	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/magefile/mage v1.14.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
-	github.com/minio/simdjson-go v0.4.5 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index 29fc05b..0fe7b5f 100644
--- a/go.sum
+++ b/go.sum
@@ -33,11 +33,11 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
 github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
 github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
-github.com/bytedance/sonic v1.12.10 h1:uVCQr6oS5669E9ZVW0HyksTLfNS7Q/9hV6IVS4nEMsI=
-github.com/bytedance/sonic v1.12.10/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
+github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
+github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
-github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
-github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
@@ -51,6 +51,8 @@ github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
 github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
+github.com/colduction/nocopy v0.2.0 h1:9jMLCmIP/wnAWO0FfSXJ4h5HBRe6cBqIqacWw/5sRXY=
+github.com/colduction/nocopy v0.2.0/go.mod h1:MO+QBkEnsZYE7QukMAcAq4b0rHpSxOTlVqD3fI34YJs=
 github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -106,6 +108,8 @@ github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnm
 github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
 github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
 github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
+github.com/hanwen/go-fuse/v2 v2.7.2 h1:SbJP1sUP+n1UF8NXBA14BuojmTez+mDgOk0bC057HQw=
+github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
 github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
@@ -123,6 +127,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
 github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
 github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
 github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
 github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
@@ -136,15 +142,15 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/minio/simdjson-go v0.4.5 h1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=
-github.com/minio/simdjson-go v0.4.5/go.mod h1:eoNz0DcLQRyEDeaPr4Ru6JpjlZPzbA0IodxVJk8lO8E=
+github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
+github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/nbd-wtf/go-nostr v0.50.3 h1:mRUJLOkCqnNTAwvjtSRogJyN3SUv1lze1UgnmqUBN0Q=
-github.com/nbd-wtf/go-nostr v0.50.3/go.mod h1:XJyV09CfSZCtuf1ApdQFc+3RuEYzt4E/pbXn+doA8tQ=
+github.com/nbd-wtf/go-nostr v0.50.5 h1:JOLrozw6nzWMD7CKhEGB5Ys7zXrTV82YjItBBnI5nw8=
+github.com/nbd-wtf/go-nostr v0.50.5/go.mod h1:s7XMBrnFUTX+ylEekIAmdvkGxMjllLZeic93TmAi6hU=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
diff --git a/helpers.go b/helpers.go
index 106b8d7..63f43f6 100644
--- a/helpers.go
+++ b/helpers.go
@@ -11,14 +11,15 @@ import (
 	"net/url"
 	"os"
 	"slices"
+	"strconv"
 	"strings"
 	"time"
 
 	"github.com/fatih/color"
-	"github.com/urfave/cli/v3"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/sdk"
+	"github.com/urfave/cli/v3"
 )
 
 var sys *sdk.System
@@ -234,6 +235,11 @@ func leftPadKey(k string) string {
 	return strings.Repeat("0", 64-len(k)) + k
 }
 
+func hexToUint64(hexStr string) uint64 {
+	v, _ := strconv.ParseUint(hexStr[0:16], 16, 64)
+	return v
+}
+
 var colors = struct {
 	reset   func(...any) (int, error)
 	italic  func(...any) string
diff --git a/main.go b/main.go
index 96c4b47..018ed01 100644
--- a/main.go
+++ b/main.go
@@ -13,7 +13,10 @@ import (
 	"github.com/urfave/cli/v3"
 )
 
-var version string = "debug"
+var (
+	version   string = "debug"
+	isVerbose bool   = false
+)
 
 var app = &cli.Command{
 	Name:                      "nak",
@@ -41,6 +44,7 @@ var app = &cli.Command{
 		mcpServer,
 		curl,
 		dvm,
+		fsCmd,
 	},
 	Version: version,
 	Flags: []cli.Flag{
@@ -71,6 +75,7 @@ var app = &cli.Command{
 				v := c.Count("verbose")
 				if v >= 1 {
 					logverbose = log
+					isVerbose = true
 				}
 				return nil
 			},