@uxnan/shared 0.0.5-alpha.20260711 → 0.0.6-alpha.20260716

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
@@ -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 — **62 JSON-RPC methods** + **8 streaming
16
+ > **Status:** implemented and stable — **66 JSON-RPC methods** + **8 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,7 +3,7 @@
3
3
  *
4
4
  * Source: architecture/02a-system-architecture.md §5.8.2 (adapters/base-adapter).
5
5
  */
6
- import type { AgentCapabilities, AgentId, AgentModel } from './agent-capabilities.js';
6
+ import type { AgentCapabilities, AgentCommand, AgentCommandInvocation, AgentId, AgentModel } from './agent-capabilities.js';
7
7
  import type { AgentConfig } from './agent-config.js';
8
8
  import type { TurnAttachment } from '../models/workspace.js';
9
9
  import type { ApprovalDecision } from '../models/approval.js';
@@ -52,6 +52,15 @@ export interface SendTurnOptions {
52
52
  * configured default posture (no behaviour change).
53
53
  */
54
54
  accessMode?: AccessMode;
55
+ /**
56
+ * Invoke an advertised agent command (from `agent/commands`) instead of
57
+ * free-form {@link text}. The {@link AgentManager} resolves it before the
58
+ * adapter runs — expanding a custom prompt-template via {@link
59
+ * IAgentAdapter.expandCommand}, or composing the CLI's native `/name args`
60
+ * form — and sets {@link text} to the result, so most adapters need no
61
+ * per-command handling. Absent for an ordinary text turn.
62
+ */
63
+ command?: AgentCommandInvocation;
55
64
  }
56
65
  export interface IAgentAdapter {
57
66
  readonly agentId: AgentId;
@@ -75,4 +84,23 @@ export interface IAgentAdapter {
75
84
  onEvent(listener: (event: AgentStreamEvent) => void): () => void;
76
85
  /** List the models this agent's CLI reports as available (optional). */
77
86
  listModels?(): Promise<AgentModel[]>;
87
+ /**
88
+ * List the special ("slash") commands this agent exposes — control commands
89
+ * reachable headless plus user-defined prompt-template commands scanned from
90
+ * disk (optional; adapters with none simply don't implement it). `cwd` is the
91
+ * thread/project directory so project-scoped custom commands (`<cwd>/.claude/
92
+ * commands`, `<cwd>/.gemini/commands`, …) are discovered alongside user-level
93
+ * ones. Discovery only; invocation flows through {@link sendTurn} with {@link
94
+ * SendTurnOptions.command}.
95
+ */
96
+ listCommands?(cwd?: string): Promise<AgentCommand[]>;
97
+ /**
98
+ * Resolve a custom prompt-template command to the final prompt text (reads the
99
+ * template file from `cwd`/user config, substitutes arguments). Implemented
100
+ * only by adapters whose commands are prompt templates the bridge expands
101
+ * itself (Codex/Gemini/OpenCode); adapters whose commands run natively (Claude
102
+ * Code, ACP agents) leave it unset and receive the composed `/name args` form
103
+ * as {@link SendTurnOptions.text}. Throw if `name` is not a known custom command.
104
+ */
105
+ expandCommand?(name: string, args?: string, cwd?: string): Promise<string>;
78
106
  }
@@ -36,6 +36,13 @@ export interface AgentCapabilities {
36
36
  * false means the agent either gates tools or is pending approval wiring.
37
37
  */
38
38
  autonomous?: boolean;
39
+ /**
40
+ * Agent exposes special ("slash") commands the phone can discover via
41
+ * `agent/commands` and invoke through `turn/send` `command`. Optional for
42
+ * back-compat; absent/false means the agent advertises none (the phone shows
43
+ * only its client-side `/` palette). See {@link AgentCommand}.
44
+ */
45
+ commands?: boolean;
39
46
  }
40
47
  /**
41
48
  * A registered agent the phone can pick for a thread, returned by `agent/list`.
@@ -124,3 +131,52 @@ export interface AgentModel {
124
131
  */
125
132
  isLatestAlias?: boolean;
126
133
  }
134
+ /**
135
+ * A special ("slash") command an agent exposes, returned by `agent/commands`.
136
+ *
137
+ * Two kinds are unified under this one shape:
138
+ * - **control** commands the CLI understands in its headless/programmatic mode
139
+ * (Claude Code's `/compact` sent as the prompt with `--resume`; the commands
140
+ * ACP agents advertise via `available_commands_update`), and
141
+ * - **custom** user-defined prompt-template commands (`.claude/commands`,
142
+ * `~/.codex/prompts`, `.gemini/commands`, `.opencode/command`) that the bridge
143
+ * expands itself before running a normal turn.
144
+ *
145
+ * The phone is a generic renderer: it lists the advertised commands in its `/`
146
+ * palette and, when one is picked, echoes it back on `turn/send` under
147
+ * {@link AgentCommandInvocation} — the bridge resolves it to the final prompt
148
+ * text (expanded template) or the CLI's native `/name args` form. Consumers MUST
149
+ * tolerate absent optional fields so a newer bridge advertising a richer command
150
+ * never breaks an older app.
151
+ */
152
+ export interface AgentCommand {
153
+ /** Command name WITHOUT the leading slash (e.g. `compact`, `refactor`). */
154
+ name: string;
155
+ /** One-line description for the palette, when the source provides one. */
156
+ description?: string;
157
+ /** Hint for the arguments the command accepts (e.g. `<file> <priority>`). */
158
+ argumentHint?: string;
159
+ /**
160
+ * Where the command comes from: `acp` (advertised by an ACP agent),
161
+ * `builtin` (a CLI control command reachable headless), or `custom` (a
162
+ * user-defined prompt-template file the bridge expands).
163
+ */
164
+ source: 'acp' | 'builtin' | 'custom';
165
+ /**
166
+ * Whether the command actually runs in the agent's headless/programmatic mode.
167
+ * Absent/true means yes; `false` marks a command that only works in the CLI's
168
+ * interactive TUI, which the phone should hide. The bridge only advertises
169
+ * commands it can run, so this is a belt-and-suspenders gate.
170
+ */
171
+ headlessSupported?: boolean;
172
+ }
173
+ /**
174
+ * A picked {@link AgentCommand} carried on `turn/send` under `command`, instead
175
+ * of free-form `text`. The bridge resolves `{ name, args }` to the final prompt.
176
+ */
177
+ export interface AgentCommandInvocation {
178
+ /** The command's {@link AgentCommand.name} (no leading slash). */
179
+ name: string;
180
+ /** Raw argument string the user appended after the command, if any. */
181
+ args?: string;
182
+ }
@@ -24,5 +24,6 @@ export * from './models/session.js';
24
24
  export * from './models/approval.js';
25
25
  export * from './models/question.js';
26
26
  export * from './models/usage.js';
27
+ export * from './models/metrics.js';
27
28
  export * from './validators/validate.js';
28
29
  export * from './validators/json-schema/schemas.js';
package/dist/src/index.js CHANGED
@@ -30,6 +30,7 @@ export * from './models/session.js';
30
30
  export * from './models/approval.js';
31
31
  export * from './models/question.js';
32
32
  export * from './models/usage.js';
33
+ export * from './models/metrics.js';
33
34
  // Validators
34
35
  export * from './validators/validate.js';
35
36
  export * from './validators/json-schema/schemas.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,gBAAgB,CAAC;AAE/B,WAAW;AACX,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,OAAO;AACP,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAE1C,SAAS;AACT,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAE1C,UAAU;AACV,cAAc,sBAAsB,CAAC;AAErC,gBAAgB;AAChB,cAAc,iCAAiC,CAAC;AAEhD,SAAS;AACT,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,gBAAgB,CAAC;AAE/B,WAAW;AACX,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,OAAO;AACP,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAE1C,SAAS;AACT,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAE1C,UAAU;AACV,cAAc,sBAAsB,CAAC;AAErC,gBAAgB;AAChB,cAAc,iCAAiC,CAAC;AAEhD,SAAS;AACT,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
@@ -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", "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/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "agent/list", "agent/models", "agent/usageStats", "auth/status", "auth/login", "auth/logout", "notifications/register", "notifications/update", "notifications/unregister", "bridge/status", "bridge/generatePairingQr", "bridge/connectedPhones", "bridge/disconnectPhone", "bridge/trustedDevices", "bridge/removeTrustedDevice"];
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", "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/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "agent/list", "agent/models", "agent/commands", "agent/usageStats", "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"];
7
7
  export declare function isKnownMethod(method: string): method is JsonRpcMethodName;
@@ -53,7 +53,12 @@ export const METHOD_NAMES = [
53
53
  // Agents
54
54
  'agent/list',
55
55
  'agent/models',
56
+ 'agent/commands',
56
57
  'agent/usageStats',
58
+ // Metrics (bridge-owned, survivable profile stats + tamper-proof backup)
59
+ 'metrics/get',
60
+ 'metrics/export',
61
+ 'metrics/import',
57
62
  // Auth
58
63
  'auth/status',
59
64
  'auth/login',
@@ -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,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,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,YAAY;IACZ,cAAc;IACd,kBAAkB;IAClB,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;CACpB,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,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,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,kBAAkB;IAClB,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;CACpB,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"}
@@ -13,8 +13,9 @@ import type { ApprovalResponse } from '../models/approval.js';
13
13
  import type { QuestionResponse } from '../models/question.js';
14
14
  import type { BridgeStatus, ConnectedPhone, TrustedDevice } from '../models/session.js';
15
15
  import type { PairingPayload } from '../e2ee/pairing-payload.js';
16
- import type { AgentDescriptor, AgentId, AgentModel } from '../agents/agent-capabilities.js';
16
+ import type { AgentCommand, AgentCommandInvocation, AgentDescriptor, AgentId, AgentModel } from '../agents/agent-capabilities.js';
17
17
  import type { UsageStatsParams, UsageStatsResult } from '../models/usage.js';
18
+ import type { MetricsExportParams, MetricsExportResult, MetricsImportParams, MetricsImportResult, MetricsSnapshot } from '../models/metrics.js';
18
19
  import type { PushPlatform } from '../notifications/push-payload.js';
19
20
  export interface ListThreadsParams {
20
21
  projectId?: string;
@@ -82,6 +83,14 @@ export interface TurnSendParams {
82
83
  * is not required.
83
84
  */
84
85
  questionResponse?: QuestionResponse;
86
+ /**
87
+ * Invoke an advertised agent command (from `agent/commands`) instead of
88
+ * free-form `text`. The bridge resolves `{ name, args }` to the final prompt —
89
+ * expanding a custom prompt-template file, or composing the CLI's native
90
+ * `/name args` form — then runs a normal turn. When present, `text` is not
91
+ * required.
92
+ */
93
+ command?: AgentCommandInvocation;
85
94
  }
86
95
  export interface ThreadSetModelParams {
87
96
  threadId: string;
@@ -220,6 +229,19 @@ export interface AgentModelsResult {
220
229
  /** Models the agent can use, with presentation metadata, as reported by its CLI. */
221
230
  models: AgentModel[];
222
231
  }
232
+ export interface AgentCommandsParams {
233
+ agentId: AgentId;
234
+ /**
235
+ * Thread/project directory, so project-scoped custom commands (e.g.
236
+ * `<cwd>/.claude/commands`, `<cwd>/.gemini/commands`) are discovered alongside
237
+ * the user-level ones. Omitted → only user-level commands are returned.
238
+ */
239
+ cwd?: string;
240
+ }
241
+ export interface AgentCommandsResult {
242
+ /** Special ("slash") commands the agent exposes, discovered from its CLI/disk. */
243
+ commands: AgentCommand[];
244
+ }
223
245
  /** What the phone wants to be notified about (background push). */
224
246
  export interface NotificationPreferences {
225
247
  /** Push when an agent turn completes. */
@@ -476,10 +498,26 @@ export interface JsonRpcMethodRegistry {
476
498
  params: AgentModelsParams;
477
499
  result: AgentModelsResult;
478
500
  };
501
+ 'agent/commands': {
502
+ params: AgentCommandsParams;
503
+ result: AgentCommandsResult;
504
+ };
479
505
  'agent/usageStats': {
480
506
  params: UsageStatsParams;
481
507
  result: UsageStatsResult;
482
508
  };
509
+ 'metrics/get': {
510
+ params: void;
511
+ result: MetricsSnapshot;
512
+ };
513
+ 'metrics/export': {
514
+ params: MetricsExportParams;
515
+ result: MetricsExportResult;
516
+ };
517
+ 'metrics/import': {
518
+ params: MetricsImportParams;
519
+ result: MetricsImportResult;
520
+ };
483
521
  'auth/status': {
484
522
  params: {
485
523
  agentId: AgentId;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Profile metrics — a bridge-owned, survivable tally of a PC's activity, plus a
3
+ * tamper-proof export/import backup.
4
+ *
5
+ * The mobile profile screen shows aggregated metrics (conversations, messages,
6
+ * agents/models used, connection time, git actions, an activity heatmap). Those
7
+ * were derived on the phone from local storage and so were lost on an app
8
+ * uninstall (the app has no cloud login). To make them durable, the **bridge**
9
+ * becomes the source of truth: it computes them from its own authoritative data
10
+ * (the conversation store) plus persisted session + git-action event logs it
11
+ * observes itself, and serves them over `metrics/get`. The phone renders one
12
+ * snapshot per PC and sums across PCs.
13
+ *
14
+ * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
+ * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
16
+ * keychain), so users cannot fabricate or edit their stats; `metrics/import`
17
+ * feeds one back and merges its events by id (idempotent).
18
+ *
19
+ * Provider usage/credits are deliberately NOT part of this — those are read live
20
+ * via `agent/usageStats` and never persisted.
21
+ *
22
+ * Source: architecture/02a-system-architecture.md §5.8.11 and
23
+ * 02b-contracts-and-requirements.md.
24
+ */
25
+ /** Transport a phone→PC connection session used. */
26
+ export type MetricsTransport = 'relay' | 'direct';
27
+ /** Per-agent conversation tally (most-used first). */
28
+ export interface MetricsAgentUsage {
29
+ /** Agent wire id (e.g. `claude-code`, `codex`). */
30
+ agentId: string;
31
+ /** Conversations started with this agent on this PC. */
32
+ conversations: number;
33
+ }
34
+ /** One agent's activity on a given day (drives the per-agent bars). */
35
+ export interface MetricsAgentDay {
36
+ /** Agent wire id (e.g. `claude-code`, `codex`). */
37
+ agentId: string;
38
+ /** Conversations (threads) this agent started that day. */
39
+ conversations: number;
40
+ /** Messages exchanged that day in this agent's threads. */
41
+ messages: number;
42
+ /**
43
+ * Tokens processed that day — the sum of each turn's reported usage (input
44
+ * incl. the re-sent context + output). **Throughput, not billed cost**: caching
45
+ * and input/output pricing differ (use `agent/usageStats` for money). 0 for
46
+ * agents that don't report usage (e.g. Zero).
47
+ */
48
+ tokens: number;
49
+ }
50
+ /** One calendar day's activity split per agent. */
51
+ export interface MetricsDayBreakdown {
52
+ /** UTC-midnight epoch ms of the calendar date (same encoding as `activity`). */
53
+ day: number;
54
+ /** Per-agent activity that day (agents with any conversation/message/token). */
55
+ byAgent: MetricsAgentDay[];
56
+ }
57
+ /**
58
+ * One local calendar-day activity bucket. Counts are split by category so a
59
+ * client can render any metric filter (combined / conversations / messages /
60
+ * work) with no extra round-trip. Days with no activity are omitted.
61
+ */
62
+ export interface MetricsActivityDay {
63
+ /**
64
+ * The calendar date this bucket covers, as **UTC-midnight epoch ms** of the
65
+ * bridge host's local date. Encoding the local date at UTC midnight (not the
66
+ * local-midnight instant) makes the key timezone-stable, so a phone in any
67
+ * timezone maps it to the correct heatmap cell.
68
+ */
69
+ day: number;
70
+ /** Conversations started that day. */
71
+ conversations: number;
72
+ /** Messages exchanged that day. */
73
+ messages: number;
74
+ /** Git/work actions performed that day. */
75
+ work: number;
76
+ }
77
+ /**
78
+ * Aggregated metrics for ONE PC (the bridge that returns it), built from the
79
+ * bridge's own authoritative data: the conversation store (threads/turns) plus
80
+ * its persisted session + git-action event logs. The phone renders this per PC
81
+ * and sums across PCs for the all-PCs profile. Every field is a count the phone
82
+ * cannot inflate — the bridge observes it. Source of `metrics/get`.
83
+ */
84
+ export interface MetricsSnapshot {
85
+ /** Schema version, for forward-compatible parsing. */
86
+ version: number;
87
+ /** The bridge PC's `macDeviceId` this snapshot belongs to. */
88
+ deviceId: string;
89
+ /** Total conversations (threads) started on this PC. */
90
+ conversations: number;
91
+ /** Distinct agents used. */
92
+ agentsUsed: number;
93
+ /** Distinct models used. */
94
+ modelsUsed: number;
95
+ /** Total messages exchanged (both roles). */
96
+ messages: number;
97
+ /** Total git actions performed (mutating operations). */
98
+ gitActions: number;
99
+ /** Connection sessions recorded. */
100
+ sessions: number;
101
+ /** Cumulative connected time across all sessions, ms. */
102
+ totalConnectedMs: number;
103
+ /** The single longest connection session, ms. */
104
+ longestSessionMs: number;
105
+ /** Sessions that ran over the relay. */
106
+ relaySessions: number;
107
+ /** Sessions that ran over a direct LAN/Tailscale host. */
108
+ directSessions: number;
109
+ /** Per-agent conversation tallies, most-used first. */
110
+ byAgent: MetricsAgentUsage[];
111
+ /** Earliest conversation creation (epoch ms); absent when there are none. */
112
+ memberSince?: number;
113
+ /** Per-day activity buckets for the contribution heatmap. */
114
+ activity: MetricsActivityDay[];
115
+ /**
116
+ * Per-day activity split per agent (conversations, messages, tokens), for the
117
+ * unified agent-activity view: the per-agent bars show all-time totals, or a
118
+ * single day's totals when a heatmap cell is selected. See
119
+ * {@link MetricsAgentDay}.
120
+ */
121
+ byAgentDay: MetricsDayBreakdown[];
122
+ /** When the bridge produced this snapshot (epoch ms). */
123
+ updatedAt: number;
124
+ }
125
+ /**
126
+ * `metrics/export` request. The bridge seals its metrics event log into an
127
+ * opaque, tamper-proof blob that only THIS same bridge can later verify +
128
+ * decrypt (AES-256-GCM under a secret held in the OS keychain). An optional user
129
+ * [passphrase] adds a second confidentiality layer (scrypt-derived), so a leaked
130
+ * file also needs the phrase; it must be supplied again at import.
131
+ */
132
+ export interface MetricsExportParams {
133
+ /** Optional extra passphrase lock; required again at import when set. */
134
+ passphrase?: string;
135
+ }
136
+ export interface MetricsExportResult {
137
+ /** The sealed blob to write to a file (a JSON string, opaque to the phone). */
138
+ blob: string;
139
+ /** Suggested filename (e.g. `uxnan-metrics-<host>-<date>.uxmetrics`). */
140
+ filename: string;
141
+ /** Whether the blob carries an extra passphrase lock (import will need it). */
142
+ passphraseProtected: boolean;
143
+ }
144
+ /**
145
+ * `metrics/import` request. The phone sends back a previously exported [blob].
146
+ * The bridge verifies it was sealed by THIS PC — a foreign or edited file is
147
+ * rejected — decrypts it (using [passphrase] when the file was passphrase-locked)
148
+ * and merges its events by id (idempotent: re-importing the same file changes
149
+ * nothing). Returns the refreshed snapshot.
150
+ */
151
+ export interface MetricsImportParams {
152
+ /** The sealed blob produced by a prior `metrics/export` on this PC. */
153
+ blob: string;
154
+ /** The passphrase, when the file was exported with one. */
155
+ passphrase?: string;
156
+ }
157
+ export interface MetricsImportResult {
158
+ /** How many new session + git-action events were merged in. */
159
+ imported: number;
160
+ /** The refreshed snapshot after the merge. */
161
+ snapshot: MetricsSnapshot;
162
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Profile metrics — a bridge-owned, survivable tally of a PC's activity, plus a
3
+ * tamper-proof export/import backup.
4
+ *
5
+ * The mobile profile screen shows aggregated metrics (conversations, messages,
6
+ * agents/models used, connection time, git actions, an activity heatmap). Those
7
+ * were derived on the phone from local storage and so were lost on an app
8
+ * uninstall (the app has no cloud login). To make them durable, the **bridge**
9
+ * becomes the source of truth: it computes them from its own authoritative data
10
+ * (the conversation store) plus persisted session + git-action event logs it
11
+ * observes itself, and serves them over `metrics/get`. The phone renders one
12
+ * snapshot per PC and sums across PCs.
13
+ *
14
+ * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
+ * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
16
+ * keychain), so users cannot fabricate or edit their stats; `metrics/import`
17
+ * feeds one back and merges its events by id (idempotent).
18
+ *
19
+ * Provider usage/credits are deliberately NOT part of this — those are read live
20
+ * via `agent/usageStats` and never persisted.
21
+ *
22
+ * Source: architecture/02a-system-architecture.md §5.8.11 and
23
+ * 02b-contracts-and-requirements.md.
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/models/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -15,7 +15,7 @@
15
15
  * user-pasted API keys.
16
16
  */
17
17
  /** A coding CLI whose usage we read from its own stored token. */
18
- export type UsageProvider = 'codex' | 'claude' | 'copilot' | 'gemini';
18
+ export type UsageProvider = 'codex' | 'claude' | 'copilot' | 'gemini' | 'grok';
19
19
  /** Outcome of reading one provider's usage. */
20
20
  export type UsageStatus =
21
21
  /** Fresh quota/credit data was read. */
@@ -29,6 +29,22 @@ export type UsageStatus =
29
29
  /** How the data was obtained, for the UI's provenance label. Every wired
30
30
  * provider reads its quota from the CLI's own signed-in token. */
31
31
  export type UsageSource = 'token';
32
+ /**
33
+ * The kind of billing relationship, so the UI can label an account beyond its
34
+ * plan name (e.g. distinguish a flat subscription from usage/credit billing).
35
+ * Derived per provider from its plan / billing signals.
36
+ */
37
+ export type AccountType =
38
+ /** A flat paid subscription (Pro / Max / Plus / Pro+ …). */
39
+ 'subscription'
40
+ /** Usage-billed: credits, on-demand, or pay-as-you-go on top of / instead of a plan. */
41
+ | 'payAsYouGo'
42
+ /** Free tier. */
43
+ | 'free'
44
+ /** A team / organization seat. */
45
+ | 'team'
46
+ /** An enterprise plan. */
47
+ | 'enterprise';
32
48
  /**
33
49
  * A single quota/rate window, expressed as a used-percentage with an optional
34
50
  * reset time — the atomic unit a provider reports (e.g. a 5-hour session, a
@@ -59,6 +75,32 @@ export interface CreditBalance {
59
75
  period: string;
60
76
  /** When the balance resets (epoch ms), when known. */
61
77
  resetsAt?: number;
78
+ /** Amount still available this period, in `currency`, when the provider
79
+ * reports a remaining balance directly (e.g. Grok on-demand / prepaid). */
80
+ available?: number;
81
+ }
82
+ /**
83
+ * "Reset credits" a provider grants to roll a hit rate-limit back early — Codex's
84
+ * rate-limit reset-credit system. Distinct from `credit` (money): these are
85
+ * redeemable reset tokens, not a balance.
86
+ */
87
+ /** One redeemable reset — for the per-credit detail (which one, when it expires). */
88
+ export interface ResetCreditEntry {
89
+ /** Short label the provider gives the reset (e.g. "Full reset"). */
90
+ title?: string;
91
+ /** When this reset lapses (epoch ms). */
92
+ expiresAt?: number;
93
+ }
94
+ export interface ResetCredits {
95
+ /** How many resets can be redeemed right now. */
96
+ available: number;
97
+ /** Total resets ever granted to this account, when reported. */
98
+ totalEarned?: number;
99
+ /** When the soonest still-available reset lapses (epoch ms), when known. */
100
+ nextExpiresAt?: number;
101
+ /** The individual available resets, soonest-expiring first, when the provider
102
+ * details them. */
103
+ entries?: ResetCreditEntry[];
62
104
  }
63
105
  /** One provider's usage snapshot. */
64
106
  export interface ProviderUsage {
@@ -70,10 +112,13 @@ export interface ProviderUsage {
70
112
  email?: string;
71
113
  organization?: string;
72
114
  plan?: string;
115
+ accountType?: AccountType;
73
116
  };
74
117
  /** Quota/rate windows (percentage-based). Empty when none apply. */
75
118
  windows: UsageWindow[];
76
119
  credit?: CreditBalance;
120
+ /** Redeemable rate-limit resets (Codex), when the provider grants them. */
121
+ resetCredits?: ResetCredits;
77
122
  /** When this snapshot was produced (epoch ms). */
78
123
  updatedAt: number;
79
124
  /** Error/hint message for `error` / `authRequired` / `notInstalled` states. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxnan/shared",
3
- "version": "0.0.5-alpha.20260711",
3
+ "version": "0.0.6-alpha.20260716",
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",