fleetmesh-node 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Automated Cross-Compartment Barter Proof Bridge (node.planes.services.video.barter).
|
|
2
|
+
|
|
3
|
+
Wires verified work proofs from MeshNode's core service and storage compartments
|
|
4
|
+
into `TransitLedgerStore.credit_barter`:
|
|
5
|
+
- `blob_replication` (`blob_replicated` channel & `ReplicaStore`): 1 byte shored = 1 byte transit credit.
|
|
6
|
+
- `compute` (`job_executed` channel & `receipts_held` store): 1 compute fuel unit = 1 KB transit credit.
|
|
7
|
+
- `dvm` (`job_succeeded` channel & `recent_jobs` ring): 1 NIP-90 DVM job unit = 1 MB transit credit.
|
|
8
|
+
- `mediation` (`messages_drained` / `message_forwarded` channels): 1 mediated message = 4 KB transit credit.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Callable, Mapping, Optional, TypedDict
|
|
15
|
+
|
|
16
|
+
from .ledger import BarterCreditRow, TransitLedgerStore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BarterHookResult(TypedDict):
|
|
20
|
+
credited: bool
|
|
21
|
+
utility_kind: str
|
|
22
|
+
debtor: str
|
|
23
|
+
units: int
|
|
24
|
+
equivalent_transit_bytes: int
|
|
25
|
+
proof_ref: str
|
|
26
|
+
unsettled_bytes_after: int
|
|
27
|
+
degraded_audio_only_after: bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def normalize_peer_pubkey(raw: str) -> str:
|
|
31
|
+
"""Extract a normalized lowercase peer identifier / pubkey from `peer:<pk>`,
|
|
32
|
+
`did:nostr:<pk>`, `did:key:<id>`, or a raw hex pubkey.
|
|
33
|
+
"""
|
|
34
|
+
val = (raw or '').strip()
|
|
35
|
+
if val.startswith('peer:'):
|
|
36
|
+
val = val[5:].strip()
|
|
37
|
+
elif val.startswith('did:nostr:'):
|
|
38
|
+
val = val[10:].strip()
|
|
39
|
+
return val.lower()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BarterSettlementBridge:
|
|
43
|
+
"""Listens to `NodeEventBus` channels and reconciles compartment stores to
|
|
44
|
+
automatically credit bartered network utility against unsettled video transit debt.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
ledger: TransitLedgerStore,
|
|
50
|
+
creditor_pk: str = '',
|
|
51
|
+
blob_store: Optional[Any] = None,
|
|
52
|
+
rhio_store: Optional[Any] = None,
|
|
53
|
+
on_settled: Optional[Callable[[BarterHookResult], None]] = None,
|
|
54
|
+
clock: Optional[Callable[[], int]] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
# An injected clock, defaulting to wall time, so a suite can move time
|
|
57
|
+
# at the boundaries this code decides on. The compartment passes its own.
|
|
58
|
+
self._clock: Callable[[], int] = clock or (lambda: int(time.time()))
|
|
59
|
+
# What reconciliation could not credit, by utility kind, with the last
|
|
60
|
+
# reason for each. These were `except Exception: pass` around whole
|
|
61
|
+
# loops: a peer that had earned a credit simply never got it, and
|
|
62
|
+
# nothing anywhere said so.
|
|
63
|
+
self.skipped: dict[str, int] = {}
|
|
64
|
+
self.last_skip: dict[str, str] = {}
|
|
65
|
+
self.ledger = ledger
|
|
66
|
+
self.creditor_pk = (creditor_pk or ('b' * 64)).lower()
|
|
67
|
+
self.blob_store = blob_store
|
|
68
|
+
self.rhio_store = rhio_store
|
|
69
|
+
self.on_settled = on_settled
|
|
70
|
+
self._lock = threading.RLock()
|
|
71
|
+
self._seen_proofs: set[str] = set()
|
|
72
|
+
self._attached_bus: Optional[Any] = None
|
|
73
|
+
|
|
74
|
+
def _lookup_blob_size(self, sha256_hex: str) -> int:
|
|
75
|
+
clean = (sha256_hex or '').strip().lower()
|
|
76
|
+
if not clean:
|
|
77
|
+
return 0
|
|
78
|
+
if self.rhio_store is not None:
|
|
79
|
+
if hasattr(self.rhio_store, 'get_blob_meta'):
|
|
80
|
+
try:
|
|
81
|
+
meta = self.rhio_store.get_blob_meta(clean)
|
|
82
|
+
if meta and int(meta.get('size', 0)) > 0:
|
|
83
|
+
return int(meta['size'])
|
|
84
|
+
except Exception as e:
|
|
85
|
+
self._skipped('blob_lookup', e)
|
|
86
|
+
if hasattr(self.rhio_store, 'get_blob'):
|
|
87
|
+
try:
|
|
88
|
+
blob = self.rhio_store.get_blob(clean)
|
|
89
|
+
if blob is not None:
|
|
90
|
+
return len(blob)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self._skipped('blob_lookup', e)
|
|
93
|
+
if self.blob_store is not None:
|
|
94
|
+
if hasattr(self.blob_store, 'get'):
|
|
95
|
+
try:
|
|
96
|
+
blob = self.blob_store.get(clean)
|
|
97
|
+
if blob is not None:
|
|
98
|
+
return len(blob)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self._skipped('blob_lookup', e)
|
|
101
|
+
return 64 * 1024 # fallback 64 KB default when blob metadata is remote-only
|
|
102
|
+
|
|
103
|
+
def _apply_credit(
|
|
104
|
+
self,
|
|
105
|
+
*,
|
|
106
|
+
debtor: str,
|
|
107
|
+
utility_kind: Any,
|
|
108
|
+
units: int,
|
|
109
|
+
proof_ref: str,
|
|
110
|
+
now: Optional[int] = None,
|
|
111
|
+
) -> Optional[BarterHookResult]:
|
|
112
|
+
norm_debtor = normalize_peer_pubkey(debtor)
|
|
113
|
+
if not norm_debtor or units <= 0 or not proof_ref:
|
|
114
|
+
return None
|
|
115
|
+
with self._lock:
|
|
116
|
+
if proof_ref in self._seen_proofs:
|
|
117
|
+
return None
|
|
118
|
+
self._seen_proofs.add(proof_ref)
|
|
119
|
+
|
|
120
|
+
row: BarterCreditRow = self.ledger.credit_barter(
|
|
121
|
+
debtor=norm_debtor,
|
|
122
|
+
creditor=self.creditor_pk,
|
|
123
|
+
utility_kind=utility_kind,
|
|
124
|
+
units=int(units),
|
|
125
|
+
proof_ref=proof_ref,
|
|
126
|
+
now=now,
|
|
127
|
+
)
|
|
128
|
+
summary = self.ledger.debt_summary(norm_debtor)
|
|
129
|
+
res: BarterHookResult = {
|
|
130
|
+
'credited': True,
|
|
131
|
+
'utility_kind': str(row['utility_kind']),
|
|
132
|
+
'debtor': norm_debtor,
|
|
133
|
+
'units': int(row['units']),
|
|
134
|
+
'equivalent_transit_bytes': int(row['equivalent_transit_bytes']),
|
|
135
|
+
'proof_ref': proof_ref,
|
|
136
|
+
'unsettled_bytes_after': int(summary['unsettled_bytes']),
|
|
137
|
+
'degraded_audio_only_after': bool(summary['degraded_audio_only']),
|
|
138
|
+
}
|
|
139
|
+
if self.on_settled is not None:
|
|
140
|
+
try:
|
|
141
|
+
self.on_settled(res)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
self._skipped('on_settled', e)
|
|
144
|
+
return res
|
|
145
|
+
|
|
146
|
+
# ---- Explicit Proof Handlers --------------------------------------------
|
|
147
|
+
|
|
148
|
+
def on_blob_shored(
|
|
149
|
+
self,
|
|
150
|
+
peer: str,
|
|
151
|
+
sha256: str,
|
|
152
|
+
size_bytes: Optional[int] = None,
|
|
153
|
+
now: Optional[int] = None,
|
|
154
|
+
) -> Optional[BarterHookResult]:
|
|
155
|
+
"""Credit a peer for mirroring/shoring a content-addressed blob replica."""
|
|
156
|
+
norm_peer = normalize_peer_pubkey(peer)
|
|
157
|
+
clean_sha = (sha256 or '').strip().lower()
|
|
158
|
+
if not norm_peer or not clean_sha:
|
|
159
|
+
return None
|
|
160
|
+
byte_units = int(size_bytes) if (size_bytes is not None and size_bytes > 0) else self._lookup_blob_size(clean_sha)
|
|
161
|
+
proof_ref = f'blob_shoring:{norm_peer}:{clean_sha}'
|
|
162
|
+
return self._apply_credit(
|
|
163
|
+
debtor=norm_peer,
|
|
164
|
+
utility_kind='blob_shoring',
|
|
165
|
+
units=byte_units,
|
|
166
|
+
proof_ref=proof_ref,
|
|
167
|
+
now=now,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def on_verified_compute(
|
|
171
|
+
self,
|
|
172
|
+
peer: str,
|
|
173
|
+
job_id: str,
|
|
174
|
+
fuel_units: int = 1,
|
|
175
|
+
now: Optional[int] = None,
|
|
176
|
+
) -> Optional[BarterHookResult]:
|
|
177
|
+
"""Credit a peer for completing a verified compute execution job."""
|
|
178
|
+
norm_peer = normalize_peer_pubkey(peer)
|
|
179
|
+
clean_job = (job_id or '').strip()
|
|
180
|
+
if not norm_peer or not clean_job:
|
|
181
|
+
return None
|
|
182
|
+
units = max(1, int(fuel_units))
|
|
183
|
+
proof_ref = f'verified_compute:{norm_peer}:{clean_job}'
|
|
184
|
+
return self._apply_credit(
|
|
185
|
+
debtor=norm_peer,
|
|
186
|
+
utility_kind='verified_compute',
|
|
187
|
+
units=units,
|
|
188
|
+
proof_ref=proof_ref,
|
|
189
|
+
now=now,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def on_dvm_job_completed(
|
|
193
|
+
self,
|
|
194
|
+
peer: str,
|
|
195
|
+
job_id: str,
|
|
196
|
+
units: int = 1,
|
|
197
|
+
fuel: int = 0,
|
|
198
|
+
now: Optional[int] = None,
|
|
199
|
+
) -> Optional[BarterHookResult]:
|
|
200
|
+
"""Credit a peer for completing a NIP-90 DVM job."""
|
|
201
|
+
norm_peer = normalize_peer_pubkey(peer)
|
|
202
|
+
clean_job = (job_id or '').strip()
|
|
203
|
+
if not norm_peer or not clean_job:
|
|
204
|
+
return None
|
|
205
|
+
effective_units = max(1, int(units) + (int(fuel) // 1000))
|
|
206
|
+
proof_ref = f'dvm:{norm_peer}:{clean_job}'
|
|
207
|
+
return self._apply_credit(
|
|
208
|
+
debtor=norm_peer,
|
|
209
|
+
utility_kind='dvm',
|
|
210
|
+
units=effective_units,
|
|
211
|
+
proof_ref=proof_ref,
|
|
212
|
+
now=now,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def on_mediation_drained(
|
|
216
|
+
self,
|
|
217
|
+
peer: str,
|
|
218
|
+
count: int = 1,
|
|
219
|
+
batch_id: str = '',
|
|
220
|
+
now: Optional[int] = None,
|
|
221
|
+
) -> Optional[BarterHookResult]:
|
|
222
|
+
"""Credit a peer for DIDComm store-and-forward mediation work."""
|
|
223
|
+
norm_peer = normalize_peer_pubkey(peer)
|
|
224
|
+
if not norm_peer or count <= 0:
|
|
225
|
+
return None
|
|
226
|
+
now_val = self._clock() if now is None else int(now)
|
|
227
|
+
ref_suffix = batch_id or str(now_val)
|
|
228
|
+
proof_ref = f'mediation:{norm_peer}:{ref_suffix}'
|
|
229
|
+
return self._apply_credit(
|
|
230
|
+
debtor=norm_peer,
|
|
231
|
+
utility_kind='mediation',
|
|
232
|
+
units=int(count),
|
|
233
|
+
proof_ref=proof_ref,
|
|
234
|
+
now=now_val,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# ---- NodeEventBus Subscription & Compartment Reconciliation -------------
|
|
238
|
+
|
|
239
|
+
def attach_event_bus(self, bus: Any) -> None:
|
|
240
|
+
"""Subscribe to `NodeEventBus` topics emitted by `Replication`, `Compute`,
|
|
241
|
+
`DVMGateway`, and `Mediation` so work events settle transit debt in real time.
|
|
242
|
+
"""
|
|
243
|
+
if bus is None or not hasattr(bus, 'subscribe') or self._attached_bus is bus:
|
|
244
|
+
return
|
|
245
|
+
self._attached_bus = bus
|
|
246
|
+
|
|
247
|
+
def _on_blob_replicated(**kw: Any) -> None:
|
|
248
|
+
server = str(kw.get('server') or kw.get('peer') or '')
|
|
249
|
+
sha256 = str(kw.get('sha256') or '')
|
|
250
|
+
size = kw.get('size')
|
|
251
|
+
size_int = int(size) if isinstance(size, int) and size > 0 else None
|
|
252
|
+
if server and sha256:
|
|
253
|
+
self.on_blob_shored(peer=server, sha256=sha256, size_bytes=size_int)
|
|
254
|
+
|
|
255
|
+
def _on_job_executed(**kw: Any) -> None:
|
|
256
|
+
peer = str(kw.get('provider') or kw.get('worker') or kw.get('buyer') or kw.get('peer') or '')
|
|
257
|
+
job_id = str(kw.get('job_id') or kw.get('id') or '')
|
|
258
|
+
fuel = int(kw.get('fuel') or kw.get('units') or 1)
|
|
259
|
+
if peer and job_id:
|
|
260
|
+
self.on_verified_compute(peer=peer, job_id=job_id, fuel_units=max(1, fuel))
|
|
261
|
+
|
|
262
|
+
def _on_dvm_succeeded(**kw: Any) -> None:
|
|
263
|
+
peer = str(kw.get('provider') or kw.get('worker') or kw.get('peer') or kw.get('pubkey') or '')
|
|
264
|
+
job_id = str(kw.get('job_id') or '')
|
|
265
|
+
fuel = int(kw.get('fuel') or 0)
|
|
266
|
+
if peer and job_id:
|
|
267
|
+
self.on_dvm_job_completed(peer=peer, job_id=job_id, units=1, fuel=fuel)
|
|
268
|
+
|
|
269
|
+
def _on_messages_drained(**kw: Any) -> None:
|
|
270
|
+
peer = str(kw.get('mediator') or kw.get('peer') or kw.get('leaf') or '')
|
|
271
|
+
count = int(kw.get('count') or 1)
|
|
272
|
+
batch_id = str(kw.get('batch_id') or f'drain-{count}-{self._clock()}')
|
|
273
|
+
if peer and count > 0:
|
|
274
|
+
self.on_mediation_drained(peer=peer, count=count, batch_id=batch_id)
|
|
275
|
+
|
|
276
|
+
bus.subscribe('blob_replicated', _on_blob_replicated)
|
|
277
|
+
bus.subscribe('job_executed', _on_job_executed)
|
|
278
|
+
bus.subscribe('job_succeeded', _on_dvm_succeeded)
|
|
279
|
+
bus.subscribe('messages_drained', _on_messages_drained)
|
|
280
|
+
|
|
281
|
+
def _skipped(self, kind: str, exc: BaseException) -> None:
|
|
282
|
+
"""Record a credit this pass could not apply, and why."""
|
|
283
|
+
self.skipped[kind] = self.skipped.get(kind, 0) + 1
|
|
284
|
+
self.last_skip[kind] = '%s: %s' % (type(exc).__name__, exc)
|
|
285
|
+
|
|
286
|
+
def reconcile_compartments(
|
|
287
|
+
self,
|
|
288
|
+
*,
|
|
289
|
+
blob_replication: Any = None,
|
|
290
|
+
compute: Any = None,
|
|
291
|
+
dvm: Any = None,
|
|
292
|
+
now: Optional[int] = None,
|
|
293
|
+
) -> list[BarterHookResult]:
|
|
294
|
+
"""Inspect active MeshNode compartments (`blob_replication`, `compute`, `dvm`)
|
|
295
|
+
and credit any uncredited work proofs for known peers.
|
|
296
|
+
"""
|
|
297
|
+
results: list[BarterHookResult] = []
|
|
298
|
+
|
|
299
|
+
# 1. Reconcile held blob replicas in Replication.db
|
|
300
|
+
if blob_replication is not None and hasattr(blob_replication, 'db'):
|
|
301
|
+
try:
|
|
302
|
+
wants = blob_replication.db.wants()
|
|
303
|
+
except Exception as e:
|
|
304
|
+
wants = []
|
|
305
|
+
self._skipped('blob_shoring', e)
|
|
306
|
+
for sha256_hex, _copies in wants:
|
|
307
|
+
# Per item, not per pass: this whole loop used to sit inside
|
|
308
|
+
# one `except Exception: pass`, so a single malformed replica
|
|
309
|
+
# row abandoned every credit after it - silently, and the
|
|
310
|
+
# peers that had earned them were simply never paid.
|
|
311
|
+
try:
|
|
312
|
+
for rep in blob_replication.db.replicas(sha256_hex):
|
|
313
|
+
if rep.get('state') == 'held':
|
|
314
|
+
srv = str(rep.get('server') or '')
|
|
315
|
+
if srv.startswith('peer:') or len(srv) == 64:
|
|
316
|
+
r = self.on_blob_shored(peer=srv, sha256=sha256_hex, now=now)
|
|
317
|
+
if r is not None:
|
|
318
|
+
results.append(r)
|
|
319
|
+
except Exception as e:
|
|
320
|
+
self._skipped('blob_shoring', e)
|
|
321
|
+
|
|
322
|
+
# 2. Reconcile signed compute receipts in Compute.receipts_held
|
|
323
|
+
if compute is not None and hasattr(compute, 'receipts_held'):
|
|
324
|
+
for rec in list(compute.receipts_held):
|
|
325
|
+
try:
|
|
326
|
+
if isinstance(rec, Mapping):
|
|
327
|
+
raw_ev = rec.get('receipt')
|
|
328
|
+
ev_doc: Mapping[str, Any] = raw_ev if isinstance(raw_ev, Mapping) else {}
|
|
329
|
+
raw_payload = rec.get('payload')
|
|
330
|
+
payload_doc: Mapping[str, Any] = raw_payload if isinstance(raw_payload, Mapping) else {}
|
|
331
|
+
peer = str(
|
|
332
|
+
rec.get('pubkey')
|
|
333
|
+
or rec.get('provider')
|
|
334
|
+
or rec.get('buyer')
|
|
335
|
+
or ev_doc.get('pubkey')
|
|
336
|
+
or ''
|
|
337
|
+
)
|
|
338
|
+
if not peer and isinstance(ev_doc.get('tags'), list):
|
|
339
|
+
for tag in ev_doc['tags']:
|
|
340
|
+
if isinstance(tag, (list, tuple)) and len(tag) >= 2 and tag[0] == 'p':
|
|
341
|
+
peer = str(tag[1])
|
|
342
|
+
break
|
|
343
|
+
job_id = str(
|
|
344
|
+
rec.get('id')
|
|
345
|
+
or rec.get('job_id')
|
|
346
|
+
or payload_doc.get('ask')
|
|
347
|
+
or ev_doc.get('id')
|
|
348
|
+
or ''
|
|
349
|
+
)
|
|
350
|
+
fuel = int(
|
|
351
|
+
rec.get('fuel')
|
|
352
|
+
or rec.get('units')
|
|
353
|
+
or payload_doc.get('fuel_used')
|
|
354
|
+
or payload_doc.get('fuel')
|
|
355
|
+
or 1
|
|
356
|
+
)
|
|
357
|
+
if peer and job_id:
|
|
358
|
+
r = self.on_verified_compute(
|
|
359
|
+
peer=peer, job_id=job_id, fuel_units=max(1, fuel), now=now
|
|
360
|
+
)
|
|
361
|
+
if r is not None:
|
|
362
|
+
results.append(r)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
self._skipped('verified_compute', e)
|
|
365
|
+
|
|
366
|
+
# 3. Reconcile DVM job completions in DVMGateway.recent_jobs
|
|
367
|
+
if dvm is not None and hasattr(dvm, 'recent_jobs'):
|
|
368
|
+
for item in list(dvm.recent_jobs):
|
|
369
|
+
try:
|
|
370
|
+
if isinstance(item, tuple) and len(item) >= 3:
|
|
371
|
+
first_str = str(item[0])
|
|
372
|
+
if first_str in ('ok', 'success', 'succeeded', 'completed'):
|
|
373
|
+
status = first_str
|
|
374
|
+
job_id = str(item[1])
|
|
375
|
+
peer = str(item[2])
|
|
376
|
+
fuel = int(item[3]) if len(item) >= 4 and isinstance(item[3], int) else 0
|
|
377
|
+
else:
|
|
378
|
+
job_id = first_str
|
|
379
|
+
status = str(item[1])
|
|
380
|
+
if len(item) >= 4:
|
|
381
|
+
if isinstance(item[2], int):
|
|
382
|
+
fuel = int(item[2])
|
|
383
|
+
peer = str(item[3])
|
|
384
|
+
else:
|
|
385
|
+
peer = str(item[2])
|
|
386
|
+
fuel = int(item[3]) if isinstance(item[3], int) else 0
|
|
387
|
+
elif isinstance(item[2], int):
|
|
388
|
+
fuel = int(item[2])
|
|
389
|
+
peer = ''
|
|
390
|
+
else:
|
|
391
|
+
fuel = 0
|
|
392
|
+
peer = str(item[2])
|
|
393
|
+
if status in ('ok', 'success', 'succeeded', 'completed') and peer and job_id:
|
|
394
|
+
r = self.on_dvm_job_completed(peer=peer, job_id=job_id, units=1, fuel=fuel, now=now)
|
|
395
|
+
if r is not None:
|
|
396
|
+
results.append(r)
|
|
397
|
+
except Exception as e:
|
|
398
|
+
self._skipped('dvm', e)
|
|
399
|
+
|
|
400
|
+
return results
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
__all__ = [
|
|
404
|
+
'BarterHookResult',
|
|
405
|
+
'normalize_peer_pubkey',
|
|
406
|
+
'BarterSettlementBridge',
|
|
407
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""CLI commands for video streaming and transit settlement (Phase 5).
|
|
2
|
+
|
|
3
|
+
Provides the `stream` CLI verb (via plugin CLI hook or standalone provider)
|
|
4
|
+
supporting:
|
|
5
|
+
- `stream start`: create a new stream and genesis manifest in P2PandaLogStore
|
|
6
|
+
- `stream ingest`: package a segment into RhioBlobSync + P2PandaLogStore
|
|
7
|
+
- `stream status`: inspect active streams, manifest heads, and transit IOU debt
|
|
8
|
+
- `stream settle`: credit bartered network utility against unsettled transit debt
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from typing import Optional, Sequence, cast
|
|
16
|
+
|
|
17
|
+
from .ledger import TransitLedgerStore, UtilityKind
|
|
18
|
+
from .packager import VideoStreamPackager
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _open_stores(node_dir: str) -> tuple[VideoStreamPackager, TransitLedgerStore]:
|
|
22
|
+
os.makedirs(node_dir, exist_ok=True)
|
|
23
|
+
from node.protocols.p2panda.store import P2PandaLogStore
|
|
24
|
+
from node.protocols.rhio.store import RhioBlobSync
|
|
25
|
+
|
|
26
|
+
p2p_db = os.path.join(node_dir, 'p2panda_v1.db')
|
|
27
|
+
rhio_db = os.path.join(node_dir, 'rhio_v1.db')
|
|
28
|
+
rhio_cas = os.path.join(node_dir, 'rhio_cas')
|
|
29
|
+
ledger_db = os.path.join(node_dir, 'video_transit_v1.db')
|
|
30
|
+
|
|
31
|
+
p2p_store = P2PandaLogStore(p2p_db)
|
|
32
|
+
rhio_store = RhioBlobSync(db_path=rhio_db, cas_dir=rhio_cas)
|
|
33
|
+
packager = VideoStreamPackager(p2panda_store=p2p_store, rhio_store=rhio_store)
|
|
34
|
+
ledger = TransitLedgerStore(ledger_db)
|
|
35
|
+
return packager, ledger
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_stream(args: argparse.Namespace) -> int:
|
|
39
|
+
node_dir = str(getattr(args, 'dir', '.'))
|
|
40
|
+
action = str(getattr(args, 'stream_action', 'status'))
|
|
41
|
+
packager, ledger = _open_stores(node_dir)
|
|
42
|
+
try:
|
|
43
|
+
if action == 'start':
|
|
44
|
+
broadcaster = str(getattr(args, 'broadcaster', '') or ('a' * 64))
|
|
45
|
+
genesis, op = packager.create_stream(
|
|
46
|
+
stream_id=str(args.stream_id),
|
|
47
|
+
broadcaster=broadcaster,
|
|
48
|
+
title=str(getattr(args, 'title', 'Live Stream')),
|
|
49
|
+
price_msat_per_mb=int(getattr(args, 'price_msat_per_mb', 0)),
|
|
50
|
+
account_fee_msat=int(getattr(args, 'account_fee_msat', 0)),
|
|
51
|
+
)
|
|
52
|
+
out = {
|
|
53
|
+
'ok': True,
|
|
54
|
+
'action': 'start',
|
|
55
|
+
'stream_id': genesis.stream_id,
|
|
56
|
+
'broadcaster': genesis.broadcaster,
|
|
57
|
+
'genesis_op_id': op.id,
|
|
58
|
+
'seq_num': op.seq_num,
|
|
59
|
+
}
|
|
60
|
+
print(json.dumps(out, indent=2))
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
elif action == 'status':
|
|
64
|
+
debtor = getattr(args, 'debtor', None)
|
|
65
|
+
if debtor:
|
|
66
|
+
summary = ledger.debt_summary(str(debtor))
|
|
67
|
+
credits = ledger.list_barter_credits(str(debtor))
|
|
68
|
+
print(json.dumps({'debt': summary, 'barter_credits': credits}, indent=2))
|
|
69
|
+
else:
|
|
70
|
+
print(json.dumps({
|
|
71
|
+
'p2panda_operations': packager.p2panda_store.count_operations(),
|
|
72
|
+
'rhio_blobs': packager.rhio_store.count_blobs(),
|
|
73
|
+
}, indent=2))
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
elif action == 'settle':
|
|
77
|
+
debtor = str(args.debtor)
|
|
78
|
+
creditor = str(getattr(args, 'creditor', '') or ('b' * 64))
|
|
79
|
+
utility = cast(UtilityKind, str(args.utility))
|
|
80
|
+
units = int(args.units)
|
|
81
|
+
proof_ref = str(args.proof)
|
|
82
|
+
row = ledger.credit_barter(
|
|
83
|
+
debtor=debtor,
|
|
84
|
+
creditor=creditor,
|
|
85
|
+
utility_kind=utility,
|
|
86
|
+
units=units,
|
|
87
|
+
proof_ref=proof_ref,
|
|
88
|
+
)
|
|
89
|
+
summary = ledger.debt_summary(debtor)
|
|
90
|
+
print(json.dumps({'credited': row, 'debt_summary': summary}, indent=2))
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
return 1
|
|
94
|
+
finally:
|
|
95
|
+
ledger.close()
|
|
96
|
+
packager.p2panda_store.close()
|
|
97
|
+
packager.rhio_store.close()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def register_stream_cli(subparsers: argparse._SubParsersAction) -> None:
|
|
101
|
+
"""Register the `stream` command on an argparse subparsers action."""
|
|
102
|
+
p = subparsers.add_parser(
|
|
103
|
+
'stream',
|
|
104
|
+
help='manage live video streams, transit handovers, and bartered IOU settlement',
|
|
105
|
+
)
|
|
106
|
+
p.add_argument('dir', metavar='DIR', help='node directory')
|
|
107
|
+
p.add_argument(
|
|
108
|
+
'stream_action',
|
|
109
|
+
choices=('start', 'status', 'settle'),
|
|
110
|
+
help='stream operation to execute',
|
|
111
|
+
)
|
|
112
|
+
p.add_argument('--stream-id', default='live-default', help='stream identifier')
|
|
113
|
+
p.add_argument('--broadcaster', default='', help='64-char hex broadcaster pubkey')
|
|
114
|
+
p.add_argument('--title', default='Live Stream', help='human-readable stream title')
|
|
115
|
+
p.add_argument('--price-msat-per-mb', type=int, default=0, help='operator egress price in msat/MB')
|
|
116
|
+
p.add_argument('--account-fee-msat', type=int, default=0, help='creator fee in msat per segment')
|
|
117
|
+
p.add_argument('--debtor', default='', help='debtor pubkey for status or barter settlement')
|
|
118
|
+
p.add_argument('--creditor', default='', help='creditor operator pubkey for barter settlement')
|
|
119
|
+
p.add_argument(
|
|
120
|
+
'--utility',
|
|
121
|
+
choices=('blob_shoring', 'verified_compute', 'dvm', 'mediation'),
|
|
122
|
+
default='blob_shoring',
|
|
123
|
+
help='bartered network utility type',
|
|
124
|
+
)
|
|
125
|
+
p.add_argument('--units', type=int, default=0, help='utility units to credit')
|
|
126
|
+
p.add_argument('--proof', default='', help='cryptographic proof or receipt reference')
|
|
127
|
+
p.set_defaults(fn=cmd_stream)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class VideoStreamCommandProvider:
|
|
131
|
+
"""CommandProvider for video streaming CLI commands."""
|
|
132
|
+
|
|
133
|
+
cli = (('stream', register_stream_cli),)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def register(cls, subparsers: argparse._SubParsersAction) -> None:
|
|
137
|
+
register_stream_cli(subparsers)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def run_stream_cli(argv: Optional[Sequence[str]] = None) -> int:
|
|
141
|
+
"""Standalone CLI entrypoint for invoking `stream` commands in tests or scripts."""
|
|
142
|
+
parser = argparse.ArgumentParser(prog='meshnode')
|
|
143
|
+
sub = parser.add_subparsers(dest='cmd')
|
|
144
|
+
register_stream_cli(sub)
|
|
145
|
+
ns = parser.parse_args(list(argv) if argv is not None else None)
|
|
146
|
+
fn = getattr(ns, 'fn', None)
|
|
147
|
+
if fn is None:
|
|
148
|
+
parser.print_help()
|
|
149
|
+
return 1
|
|
150
|
+
return int(fn(ns))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = [
|
|
154
|
+
'cmd_stream',
|
|
155
|
+
'register_stream_cli',
|
|
156
|
+
'VideoStreamCommandProvider',
|
|
157
|
+
'run_stream_cli',
|
|
158
|
+
]
|