mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
feat(amy): list an author's napplets / nsites
Add `amy napplet list <author>` and `amy nsite list <author>`: fetch the author's root + named manifests (15129/35129 for napplets, 15128/35128 for nsites), keep the latest per identifier, and emit a summary of each (kind, d, title, description, path count, servers, requires/aggregate, event id, created_at). Thin assembly over ctx.drain + the quartz manifest accessors. Harness README notes `amy napplet list` for enumerating what you've published. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
This commit is contained in:
@@ -57,14 +57,66 @@ object NappletCommands {
|
||||
route(
|
||||
"napplet",
|
||||
tail,
|
||||
"napplet <fetch|publish|serve> …",
|
||||
"napplet <fetch|publish|serve|list> …",
|
||||
mapOf(
|
||||
"fetch" to { rest -> fetch(dataDir, rest) },
|
||||
"publish" to { rest -> publish(dataDir, rest) },
|
||||
"serve" to { rest -> serve(dataDir, rest) },
|
||||
"list" to { rest -> list(dataDir, rest) },
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* `amy napplet list <author> [--relay R] [--timeout S]` — enumerate an author's published
|
||||
* napplets: the root (kind 15129) and every named one (kind 35129), latest per identifier.
|
||||
*/
|
||||
private suspend fun list(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
val args = Args(rest)
|
||||
val author = args.positionalOrNull(0) ?: return Output.error("bad_args", "napplet list <author> [--relay R] [--timeout S]")
|
||||
val extraRelays = StaticSiteFetch.commaList(args.flag("relay"))
|
||||
val timeoutSecs = args.longFlag("timeout", 8L)
|
||||
|
||||
Context.open(dataDir).use { ctx ->
|
||||
ctx.prepare()
|
||||
val authorHex = ctx.requireUserHex(author)
|
||||
val relays =
|
||||
extraRelays
|
||||
.mapNotNull { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
.toSet()
|
||||
.ifEmpty { ctx.bootstrapRelays() }
|
||||
val filter = Filter(kinds = listOf(RootNappletEvent.KIND, NamedNappletEvent.KIND), authors = listOf(authorHex))
|
||||
val items =
|
||||
ctx
|
||||
.drain(relays.associateWith { listOf(filter) }, timeoutSecs * 1000)
|
||||
.map { (_, ev) -> ev }
|
||||
.filter { it.pubKey == authorHex && it is NappletManifest }
|
||||
.groupBy { it.kind to (it as? NamedNappletEvent)?.identifier() }
|
||||
.mapNotNull { (_, dupes) -> dupes.maxByOrNull { it.createdAt } }
|
||||
.sortedByDescending { it.createdAt }
|
||||
.map { ev ->
|
||||
val m = ev as NappletManifest
|
||||
mapOf(
|
||||
"kind" to ev.kind,
|
||||
"d" to (ev as? NamedNappletEvent)?.identifier(),
|
||||
"title" to m.title(),
|
||||
"description" to m.description(),
|
||||
"paths" to m.paths().size,
|
||||
"servers" to m.servers(),
|
||||
"requires" to m.requires(),
|
||||
"aggregate_sha256" to m.declaredAggregateHash(),
|
||||
"aggregate_verified" to m.verifyAggregate(),
|
||||
"event_id" to ev.id,
|
||||
"created_at" to ev.createdAt,
|
||||
)
|
||||
}
|
||||
Output.emit(mapOf("pubkey" to authorHex, "count" to items.size, "napplets" to items))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `amy napplet serve <author> [--d ID] [--port N]` — fetch + aggregate-verify the manifest and
|
||||
* serve its static content over a local HTTP server. NOTE: the napplet's window.napplet.* runtime
|
||||
|
||||
@@ -57,14 +57,78 @@ object NsiteCommands {
|
||||
route(
|
||||
"nsite",
|
||||
tail,
|
||||
"nsite <fetch|publish|serve> …",
|
||||
"nsite <fetch|publish|serve|list> …",
|
||||
mapOf(
|
||||
"fetch" to { rest -> fetch(dataDir, rest) },
|
||||
"publish" to { rest -> publish(dataDir, rest) },
|
||||
"serve" to { rest -> serve(dataDir, rest) },
|
||||
"list" to { rest -> list(dataDir, rest) },
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* `amy nsite list <author> [--relay R] [--timeout S]` — enumerate an author's static websites:
|
||||
* the root (kind 15128) and every named one (kind 35128), latest per identifier.
|
||||
*/
|
||||
private suspend fun list(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
val args = Args(rest)
|
||||
val author = args.positionalOrNull(0) ?: return Output.error("bad_args", "nsite list <author> [--relay R] [--timeout S]")
|
||||
val extraRelays = StaticSiteFetch.commaList(args.flag("relay"))
|
||||
val timeoutSecs = args.longFlag("timeout", 8L)
|
||||
|
||||
Context.open(dataDir).use { ctx ->
|
||||
ctx.prepare()
|
||||
val authorHex = ctx.requireUserHex(author)
|
||||
val relays =
|
||||
extraRelays
|
||||
.mapNotNull { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
.toSet()
|
||||
.ifEmpty { ctx.bootstrapRelays() }
|
||||
val filter = Filter(kinds = listOf(RootSiteEvent.KIND, NamedSiteEvent.KIND), authors = listOf(authorHex))
|
||||
val items =
|
||||
ctx
|
||||
.drain(relays.associateWith { listOf(filter) }, timeoutSecs * 1000)
|
||||
.map { (_, ev) -> ev }
|
||||
.filter { it.pubKey == authorHex && (it is RootSiteEvent || it is NamedSiteEvent) }
|
||||
.groupBy { it.kind to (it as? NamedSiteEvent)?.identifier() }
|
||||
.mapNotNull { (_, dupes) -> dupes.maxByOrNull { it.createdAt } }
|
||||
.sortedByDescending { it.createdAt }
|
||||
.map { ev -> describe(ev) }
|
||||
Output.emit(mapOf("pubkey" to authorHex, "count" to items.size, "nsites" to items))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun describe(ev: Event): Map<String, Any?> =
|
||||
when (ev) {
|
||||
is NamedSiteEvent ->
|
||||
mapOf(
|
||||
"kind" to ev.kind,
|
||||
"d" to ev.identifier(),
|
||||
"title" to ev.title(),
|
||||
"description" to ev.description(),
|
||||
"paths" to ev.paths().size,
|
||||
"servers" to ev.servers(),
|
||||
"event_id" to ev.id,
|
||||
"created_at" to ev.createdAt,
|
||||
)
|
||||
is RootSiteEvent ->
|
||||
mapOf(
|
||||
"kind" to ev.kind,
|
||||
"d" to null,
|
||||
"title" to ev.title(),
|
||||
"description" to ev.description(),
|
||||
"paths" to ev.paths().size,
|
||||
"servers" to ev.servers(),
|
||||
"event_id" to ev.id,
|
||||
"created_at" to ev.createdAt,
|
||||
)
|
||||
else -> mapOf("kind" to ev.kind, "event_id" to ev.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* `amy nsite serve <author> [--d ID] [--port N]` — fetch the manifest and serve it over a local
|
||||
* HTTP server (sha256-verified per request) so you can open it in a browser.
|
||||
|
||||
@@ -28,6 +28,7 @@ amy napplet publish tools/napplet-test \
|
||||
the identity reads (`getProfile`, `getFollows`, …) have data.
|
||||
|
||||
Verify it resolves (optional): `amy napplet fetch <your-pubkey-hex> --d napplet-test`.
|
||||
List everything you've published: `amy napplet list <your-pubkey-hex>` (or `amy nsite list <pubkey>`).
|
||||
|
||||
## Preview in a browser (optional)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user