@uxnan/shared 0.0.18-alpha.20260926 → 0.0.19-alpha.20260926

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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ![TypeScript](https://img.shields.io/badge/TypeScript-ESM-3178C6?style=for-the-badge&logo=typescript&logoColor=white)
4
4
  ![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)
5
5
  ![JSON Schema](https://img.shields.io/badge/validation-Ajv-000000?style=for-the-badge&logo=json&logoColor=white)
6
- ![Contracts](https://img.shields.io/badge/81_methods_%7C_21_notifications-blue?style=for-the-badge)
6
+ ![Contracts](https://img.shields.io/badge/82_methods_%7C_22_notifications-blue?style=for-the-badge)
7
7
 
8
8
  Shared JSON-RPC and E2EE contracts for the [Uxnan](../README.md) ecosystem — the
9
9
  single source of truth every component agrees on. Consumed as a local workspace
@@ -13,7 +13,7 @@ equivalents (see
13
13
  [`architecture/02b-contracts-and-requirements.md`](../architecture/02b-contracts-and-requirements.md)
14
14
  §1 for the canonical contract list).
15
15
 
16
- > **Status:** implemented and stable — **81 JSON-RPC methods** + **21 streaming
16
+ > **Status:** implemented and stable — **82 JSON-RPC methods** + **22 streaming
17
17
  > notifications**, kept lock-step at build time with the `METHOD_NAMES` array and
18
18
  > the `StreamNotification` enum (a compile-time assertion in
19
19
  > `src/jsonrpc/method-registry.ts` fails the build on any drift). Changes are
@@ -3,5 +3,5 @@
3
3
  * compile-time {@link JsonRpcMethodRegistry} via the assertion below.
4
4
  */
5
5
  import type { JsonRpcMethodName } from './methods.js';
6
- export declare const METHOD_NAMES: readonly ["thread/list", "thread/read", "thread/start", "thread/resume", "thread/fork", "thread/setModel", "thread/rename", "thread/setAccessMode", "thread/archive", "thread/unarchive", "thread/delete", "turn/list", "turn/read", "turn/send", "turn/cancel", "queue/resume", "queue/clear", "git/status", "git/diff", "git/commit", "git/push", "git/pull", "git/checkout", "git/createBranch", "git/createWorktree", "git/stage", "git/unstage", "git/discard", "git/createPr", "git/undoCommit", "git/branches", "git/switchBranch", "git/revert", "git/deleteBranch", "git/removeWorktree", "git/worktrees", "git/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/resolveFileLink", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "project/add", "project/remove", "project/rename", "sync/changes", "settings/get", "settings/set", "device/describe", "device/rename", "agent/list", "agent/models", "agent/commands", "agent/usageStats", "agent/doctor", "metrics/get", "metrics/export", "metrics/import", "auth/status", "auth/login", "auth/logout", "notifications/register", "notifications/update", "notifications/unregister", "bridge/status", "bridge/generatePairingQr", "bridge/connectedPhones", "bridge/disconnectPhone", "bridge/trustedDevices", "bridge/removeTrustedDevice", "desktop/attach", "desktop/detach"];
6
+ export declare const METHOD_NAMES: readonly ["thread/list", "thread/read", "thread/start", "thread/resume", "thread/fork", "thread/setModel", "thread/rename", "thread/setAccessMode", "thread/archive", "thread/unarchive", "thread/delete", "turn/list", "turn/read", "turn/send", "turn/cancel", "queue/resume", "queue/clear", "git/status", "git/diff", "git/commit", "git/push", "git/pull", "git/checkout", "git/createBranch", "git/createWorktree", "git/stage", "git/unstage", "git/discard", "git/createPr", "git/undoCommit", "git/branches", "git/switchBranch", "git/revert", "git/deleteBranch", "git/removeWorktree", "git/worktrees", "git/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/resolveFileLink", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "project/add", "project/remove", "project/rename", "sync/changes", "settings/get", "settings/set", "device/describe", "device/rename", "agent/list", "agent/models", "agent/commands", "agent/usageStats", "agent/doctor", "metrics/get", "metrics/export", "metrics/import", "auth/status", "auth/login", "auth/logout", "notifications/register", "notifications/update", "notifications/unregister", "bridge/status", "bridge/generatePairingQr", "bridge/connectedPhones", "bridge/disconnectPhone", "bridge/trustedDevices", "bridge/removeTrustedDevice", "bridge/update", "desktop/attach", "desktop/detach"];
7
7
  export declare function isKnownMethod(method: string): method is JsonRpcMethodName;
@@ -89,6 +89,7 @@ export const METHOD_NAMES = [
89
89
  'bridge/disconnectPhone',
90
90
  'bridge/trustedDevices',
91
91
  'bridge/removeTrustedDevice',
92
+ 'bridge/update',
92
93
  // Desktop tools for bridge-run agents (local control channel only)
93
94
  'desktop/attach',
94
95
  'desktop/detach',
@@ -1 +1 @@
1
- {"version":3,"file":"method-registry.js","sourceRoot":"","sources":["../../../src/jsonrpc/method-registry.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,kBAAkB;IAClB,eAAe;IACf,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,UAAU;IACV,UAAU;IACV,cAAc;IACd,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,aAAa;IACb,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,kBAAkB;IAClB,oBAAoB;IACpB,eAAe;IACf,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,2BAA2B;IAC3B,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,mCAAmC;IACnC,cAAc;IACd,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,kBAAkB;IAClB,cAAc;IACd,yEAAyE;IACzE,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,OAAO;IACP,aAAa;IACb,YAAY;IACZ,aAAa;IACb,uBAAuB;IACvB,wBAAwB;IACxB,sBAAsB;IACtB,0BAA0B;IAC1B,iBAAiB;IACjB,eAAe;IACf,0BAA0B;IAC1B,wBAAwB;IACxB,wBAAwB;IACxB,uBAAuB;IACvB,4BAA4B;IAC5B,mEAAmE;IACnE,gBAAgB;IAChB,gBAAgB;CACR,CAAC;AAQX,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,KAAK,sBAAsB,CAAC;AAC5B,KAAK,sBAAsB,CAAC;AAE5B,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"method-registry.js","sourceRoot":"","sources":["../../../src/jsonrpc/method-registry.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,kBAAkB;IAClB,eAAe;IACf,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,UAAU;IACV,UAAU;IACV,cAAc;IACd,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,aAAa;IACb,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,kBAAkB;IAClB,oBAAoB;IACpB,eAAe;IACf,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,2BAA2B;IAC3B,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,mCAAmC;IACnC,cAAc;IACd,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,kBAAkB;IAClB,cAAc;IACd,yEAAyE;IACzE,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,OAAO;IACP,aAAa;IACb,YAAY;IACZ,aAAa;IACb,uBAAuB;IACvB,wBAAwB;IACxB,sBAAsB;IACtB,0BAA0B;IAC1B,iBAAiB;IACjB,eAAe;IACf,0BAA0B;IAC1B,wBAAwB;IACxB,wBAAwB;IACxB,uBAAuB;IACvB,4BAA4B;IAC5B,eAAe;IACf,mEAAmE;IACnE,gBAAgB;IAChB,gBAAgB;CACR,CAAC;AAQX,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,KAAK,sBAAsB,CAAC;AAC5B,KAAK,sBAAsB,CAAC;AAE5B,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
@@ -12,7 +12,7 @@ import type { AuthStatus, Project, ProjectAddParams, ProjectRemoveParams, Projec
12
12
  import type { BridgeSettings, SettingsSetParams, SyncChanges, SyncChangesParams } from '../models/sync.js';
13
13
  import type { ApprovalResponse } from '../models/approval.js';
14
14
  import type { QuestionResponse } from '../models/question.js';
15
- import type { BridgeStatus, ConnectedPhone, DeviceDescribeParams, DeviceDescription, DeviceRenameParams, TrustedDevice } from '../models/session.js';
15
+ import type { BridgeStatus, BridgeUpdate, ConnectedPhone, DeviceDescribeParams, DeviceDescription, DeviceRenameParams, TrustedDevice } from '../models/session.js';
16
16
  import type { PairingPayload } from '../e2ee/pairing-payload.js';
17
17
  import type { AgentCommand, AgentCommandInvocation, AgentDescriptor, AgentDiagnosis, AgentId, AgentModel } from '../agents/agent-capabilities.js';
18
18
  import type { UsageStatsParams, UsageStatsResult } from '../models/usage.js';
@@ -719,6 +719,10 @@ export interface JsonRpcMethodRegistry {
719
719
  };
720
720
  result: void;
721
721
  };
722
+ 'bridge/update': {
723
+ params: void;
724
+ result: BridgeUpdate;
725
+ };
722
726
  'desktop/attach': {
723
727
  params: DesktopAttachParams;
724
728
  result: DesktopAttachResult;
@@ -13,7 +13,7 @@ import type { ApprovalDecision } from '../models/approval.js';
13
13
  import type { QueuePausedReason, Thread, Turn } from '../models/thread.js';
14
14
  import type { Project } from '../models/project.js';
15
15
  import type { BridgeSettings, ClientPresence } from '../models/sync.js';
16
- import type { TrustedDevice } from '../models/session.js';
16
+ import type { BridgeUpdate, TrustedDevice } from '../models/session.js';
17
17
  import type { AgentDescriptor } from '../agents/agent-capabilities.js';
18
18
  export declare const StreamNotification: {
19
19
  readonly TurnStarted: "stream/turn/started";
@@ -61,6 +61,12 @@ export declare const StreamNotification: {
61
61
  readonly DevicesUpdated: "stream/devices/updated";
62
62
  /** An agent became available or unavailable (installed, removed). */
63
63
  readonly AgentsUpdated: "stream/agents/updated";
64
+ /**
65
+ * The bridge's own update changed: a newer version was published, or an
66
+ * update started or failed. The whole state, as `bridge/status` → `update`
67
+ * would answer now (idempotent).
68
+ */
69
+ readonly BridgeUpdated: "stream/bridge/updated";
64
70
  };
65
71
  export type StreamNotification = (typeof StreamNotification)[keyof typeof StreamNotification];
66
72
  export interface TurnStartedParams {
@@ -219,6 +225,10 @@ export interface DevicesUpdatedParams {
219
225
  export interface AgentsUpdatedParams {
220
226
  agents: AgentDescriptor[];
221
227
  }
228
+ /** The bridge's update, as `bridge/status` → `update` would answer now. */
229
+ export interface BridgeUpdatedParams {
230
+ update: BridgeUpdate;
231
+ }
222
232
  /**
223
233
  * A user turn was stored: it started right away (`status` `pending`) or it was
224
234
  * queued behind the running one (`queued`). `turn.messages` holds the user's
@@ -44,5 +44,11 @@ export const StreamNotification = {
44
44
  DevicesUpdated: 'stream/devices/updated',
45
45
  /** An agent became available or unavailable (installed, removed). */
46
46
  AgentsUpdated: 'stream/agents/updated',
47
+ /**
48
+ * The bridge's own update changed: a newer version was published, or an
49
+ * update started or failed. The whole state, as `bridge/status` → `update`
50
+ * would answer now (idempotent).
51
+ */
52
+ BridgeUpdated: 'stream/bridge/updated',
47
53
  };
48
54
  //# sourceMappingURL=notifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/jsonrpc/notifications.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,WAAW,EAAE,qBAAqB;IAClC,YAAY,EAAE,sBAAsB;IACpC,kFAAkF;IAClF,aAAa,EAAE,uBAAuB;IACtC,mFAAmF;IACnF,YAAY,EAAE,sBAAsB;IACpC,aAAa,EAAE,uBAAuB;IACtC,SAAS,EAAE,mBAAmB;IAC9B,WAAW,EAAE,qBAAqB;IAClC,2EAA2E;IAC3E,aAAa,EAAE,uBAAuB;IACtC,+EAA+E;IAC/E,YAAY,EAAE,sBAAsB;IACpC,sFAAsF;IACtF,aAAa,EAAE,uBAAuB;IACtC;;;;OAIG;IACH,aAAa,EAAE,uBAAuB;IACtC,4BAA4B;IAC5B,aAAa,EAAE,uBAAuB;IACtC;;;;OAIG;IACH,WAAW,EAAE,qBAAqB;IAClC,oEAAoE;IACpE,gBAAgB,EAAE,0BAA0B;IAC5C,6EAA6E;IAC7E,gBAAgB,EAAE,0BAA0B;IAC5C,qDAAqD;IACrD,cAAc,EAAE,wBAAwB;IACxC,wEAAwE;IACxE,cAAc,EAAE,wBAAwB;IACxC,yEAAyE;IACzE,eAAe,EAAE,yBAAyB;IAC1C,0CAA0C;IAC1C,eAAe,EAAE,yBAAyB;IAC1C,uDAAuD;IACvD,cAAc,EAAE,wBAAwB;IACxC,qEAAqE;IACrE,aAAa,EAAE,uBAAuB;CAC9B,CAAC"}
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/jsonrpc/notifications.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,WAAW,EAAE,qBAAqB;IAClC,YAAY,EAAE,sBAAsB;IACpC,kFAAkF;IAClF,aAAa,EAAE,uBAAuB;IACtC,mFAAmF;IACnF,YAAY,EAAE,sBAAsB;IACpC,aAAa,EAAE,uBAAuB;IACtC,SAAS,EAAE,mBAAmB;IAC9B,WAAW,EAAE,qBAAqB;IAClC,2EAA2E;IAC3E,aAAa,EAAE,uBAAuB;IACtC,+EAA+E;IAC/E,YAAY,EAAE,sBAAsB;IACpC,sFAAsF;IACtF,aAAa,EAAE,uBAAuB;IACtC;;;;OAIG;IACH,aAAa,EAAE,uBAAuB;IACtC,4BAA4B;IAC5B,aAAa,EAAE,uBAAuB;IACtC;;;;OAIG;IACH,WAAW,EAAE,qBAAqB;IAClC,oEAAoE;IACpE,gBAAgB,EAAE,0BAA0B;IAC5C,6EAA6E;IAC7E,gBAAgB,EAAE,0BAA0B;IAC5C,qDAAqD;IACrD,cAAc,EAAE,wBAAwB;IACxC,wEAAwE;IACxE,cAAc,EAAE,wBAAwB;IACxC,yEAAyE;IACzE,eAAe,EAAE,yBAAyB;IAC1C,0CAA0C;IAC1C,eAAe,EAAE,yBAAyB;IAC1C,uDAAuD;IACvD,cAAc,EAAE,wBAAwB;IACxC,qEAAqE;IACrE,aAAa,EAAE,uBAAuB;IACtC;;;;OAIG;IACH,aAAa,EAAE,uBAAuB;CAC9B,CAAC"}
@@ -75,17 +75,17 @@ export interface BridgeStatus {
75
75
  platform: NodeJS.Platform | string;
76
76
  uptimeMs: number;
77
77
  /**
78
- * Latest bridge version published to npm (dist-tag `alpha`), as discovered by
79
- * the bridge's own background update check. Absent when the check hasn't run
80
- * yet or couldn't reach the registry (offline) — never blocks status.
81
- */
82
- latestVersion?: string;
83
- /**
84
- * True when {@link latestVersion} is strictly newer than {@link version}
85
- * (SemVer precedence). Lets the phone show a "bridge update available" hint
86
- * without querying npm itself. Absent/false when unknown or up to date.
78
+ * The bridge's own update: the newest published version it knows of, whether
79
+ * it can install that version and restart itself, and how an update under way
80
+ * is going ({@link BridgeUpdate}). Every client reads it here and follows
81
+ * `stream/bridge/updated`; none of them asks npm itself.
82
+ *
83
+ * **Absent means the bridge predates updating itself**, and is therefore older
84
+ * than the client reading this: a client offers the update it can do on its
85
+ * own (Uxnan Desktop installs the published bridge) or says to update the
86
+ * bridge on the PC (the phone).
87
87
  */
88
- updateAvailable?: boolean;
88
+ update?: BridgeUpdate;
89
89
  /**
90
90
  * Threads with a turn in flight right now, whichever client started it.
91
91
  * Absent on an older bridge. A client uses it to wait for a quiet moment
@@ -110,6 +110,58 @@ export interface BridgeStatus {
110
110
  /** Who is connected right now. Absent on an older bridge. */
111
111
  clients?: ClientPresence[];
112
112
  }
113
+ /**
114
+ * Where an update of the bridge stands. `updating` spans the whole handover —
115
+ * the bridge stops, installs the published version and its service starts the
116
+ * new one — so a client sees the connection drop and come back on the new
117
+ * {@link BridgeUpdate.version}; the outcome of a failed install is reported by
118
+ * the bridge that comes back.
119
+ */
120
+ export type BridgeUpdatePhase = 'idle' | 'updating' | 'failed';
121
+ /**
122
+ * Why an update did not happen:
123
+ * - `busy`: a turn is running on some client — the bridge never restarts
124
+ * under one; try again when it ends.
125
+ * - `unsupported`: this bridge cannot replace itself (see
126
+ * {@link BridgeUpdate.canApply}).
127
+ * - `permission`: npm could not write its global folder; {@link
128
+ * BridgeUpdateFailure.command} is the command to run by hand.
129
+ * - `install`: npm failed for another reason (offline, registry, disk).
130
+ */
131
+ export type BridgeUpdateFailureReason = 'busy' | 'unsupported' | 'permission' | 'install';
132
+ export interface BridgeUpdateFailure {
133
+ reason: BridgeUpdateFailureReason;
134
+ /** What went wrong, for a person (npm's last lines for `install`). */
135
+ message: string;
136
+ /** The command that does the update by hand, when that is the way out. */
137
+ command?: string;
138
+ }
139
+ /**
140
+ * The bridge's own update, owned by the bridge: it checks the npm registry
141
+ * itself (hourly while it runs), installs the published version and restarts on
142
+ * it (`bridge/update`), and tells every client with `stream/bridge/updated`.
143
+ */
144
+ export interface BridgeUpdate {
145
+ /** The version running now. */
146
+ version: string;
147
+ /** The newest version published under npm's `latest` dist-tag, once known. */
148
+ latestVersion?: string;
149
+ /** {@link latestVersion} is strictly newer than {@link version}. */
150
+ available: boolean;
151
+ /**
152
+ * This bridge can install the published version and restart on it by
153
+ * itself: it runs as the user's service from a global npm install, with npm
154
+ * beside it. False for a bridge started in a terminal or run from a source
155
+ * checkout — {@link unsupportedReason} says which.
156
+ */
157
+ canApply: boolean;
158
+ unsupportedReason?: string;
159
+ phase: BridgeUpdatePhase;
160
+ /** The version being installed while {@link phase} is `updating`. */
161
+ targetVersion?: string;
162
+ /** Why the last attempt failed, while {@link phase} is `failed`. */
163
+ failure?: BridgeUpdateFailure;
164
+ }
113
165
  /**
114
166
  * What started the bridge: its own user service (`service` — the normal case,
115
167
  * it outlives Uxnan Desktop), Uxnan Desktop directly (`desktop`), or a person
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxnan/shared",
3
- "version": "0.0.18-alpha.20260926",
3
+ "version": "0.0.19-alpha.20260926",
4
4
  "description": "Shared JSON-RPC and E2EE contracts for the Uxnan ecosystem (bridge, relay, mobile).",
5
5
  "license": "MPL-2.0",
6
6
  "author": "Luis Donaldo Gamas Vazquez",