@phnx-labs/agents-cli 1.20.74 → 1.20.77
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/CHANGELOG.md +1055 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/exec.js +83 -13
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +38 -8
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +286 -15
- package/dist/commands/secrets.js +102 -37
- package/dist/commands/sessions-browser.js +12 -2
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.d.ts +14 -1
- package/dist/commands/sessions-picker.js +184 -16
- package/dist/commands/sessions.d.ts +56 -14
- package/dist/commands/sessions.js +322 -62
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/ssh.d.ts +13 -0
- package/dist/commands/ssh.js +110 -37
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +15 -6
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +24 -4
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +17 -6
- package/dist/lib/activity.js +466 -40
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +21 -0
- package/dist/lib/agents.js +70 -6
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.js +4 -0
- package/dist/lib/daemon.d.ts +10 -1
- package/dist/lib/daemon.js +114 -11
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +37 -1
- package/dist/lib/devices/fleet.js +36 -2
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +82 -0
- package/dist/lib/exec.js +218 -6
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/feed.d.ts +7 -1
- package/dist/lib/feed.js +96 -6
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/heal.d.ts +7 -4
- package/dist/lib/heal.js +10 -22
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +16 -0
- package/dist/lib/hosts/dispatch.js +26 -4
- package/dist/lib/hosts/passthrough.js +9 -2
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.d.ts +11 -0
- package/dist/lib/hosts/remote-cmd.js +32 -8
- package/dist/lib/hosts/remote-session-id.d.ts +45 -0
- package/dist/lib/hosts/remote-session-id.js +84 -0
- package/dist/lib/hosts/run-target.d.ts +17 -5
- package/dist/lib/hosts/run-target.js +30 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +40 -5
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +2 -0
- package/dist/lib/import.js +35 -2
- package/dist/lib/menubar/install-menubar.d.ts +29 -0
- package/dist/lib/menubar/install-menubar.js +59 -1
- package/dist/lib/menubar/notify-desktop.d.ts +44 -0
- package/dist/lib/menubar/notify-desktop.js +78 -0
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +6 -2
- package/dist/lib/overdue.js +10 -36
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routine-notify.d.ts +76 -0
- package/dist/lib/routine-notify.js +190 -0
- package/dist/lib/routines-placement.d.ts +38 -0
- package/dist/lib/routines-placement.js +79 -0
- package/dist/lib/routines-project.d.ts +97 -0
- package/dist/lib/routines-project.js +349 -0
- package/dist/lib/routines.d.ts +94 -0
- package/dist/lib/routines.js +93 -9
- package/dist/lib/runner.d.ts +12 -2
- package/dist/lib/runner.js +242 -20
- package/dist/lib/sandbox.js +10 -0
- package/dist/lib/secrets/account-token.d.ts +20 -0
- package/dist/lib/secrets/account-token.js +64 -0
- package/dist/lib/secrets/agent.d.ts +74 -4
- package/dist/lib/secrets/agent.js +181 -107
- package/dist/lib/secrets/bundles.d.ts +54 -6
- package/dist/lib/secrets/bundles.js +230 -34
- package/dist/lib/secrets/index.d.ts +26 -3
- package/dist/lib/secrets/index.js +42 -12
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/session-store.d.ts +6 -2
- package/dist/lib/secrets/session-store.js +65 -15
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/secrets/unlock-hints.d.ts +27 -0
- package/dist/lib/secrets/unlock-hints.js +36 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +124 -7
- package/dist/lib/session/active.js +390 -56
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +54 -0
- package/dist/lib/session/db.js +320 -19
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +478 -3
- package/dist/lib/session/discover.js +1740 -472
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +141 -32
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/prompt.d.ts +7 -0
- package/dist/lib/session/prompt.js +37 -0
- package/dist/lib/session/provenance.d.ts +7 -0
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +40 -26
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.d.ts +4 -0
- package/dist/lib/session/state.js +99 -13
- package/dist/lib/session/types.d.ts +25 -1
- package/dist/lib/session/types.js +8 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +20 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +3 -0
- package/dist/lib/state.d.ts +8 -4
- package/dist/lib/state.js +21 -37
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +47 -2
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +9 -2
- package/dist/lib/versions.js +37 -8
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -182,7 +182,10 @@ export function probeDeviceStats(device, opts = {}) {
|
|
|
182
182
|
// remote login shell, which evaluates the snippet's `;`/`||` directly — no
|
|
183
183
|
// `sh -c` wrapper needed (and a wrapper would only re-quote the first token).
|
|
184
184
|
// For powershell devices it base64-encodes the snippet instead.
|
|
185
|
-
|
|
185
|
+
// agentOnly: this is a read-only stats probe. A password-auth device must
|
|
186
|
+
// resolve its bundle broker-only — never force a foreground Touch ID sheet
|
|
187
|
+
// just to render the load/mem columns of `agents devices` (RUSH-1970).
|
|
188
|
+
({ args, env } = buildSshInvocation(device, [isWin ? WIN_PROBE_SNIPPET : PROBE_SNIPPET], shim, {}, { agentOnly: true }));
|
|
186
189
|
}
|
|
187
190
|
catch {
|
|
188
191
|
return Promise.resolve({ host, reachable: false, fetchedAt });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One reachability resolver for the fleet render (RUSH-1965).
|
|
3
|
+
*
|
|
4
|
+
* "Is this device online?" had three disagreeing answers: the live SSH probe
|
|
5
|
+
* (`DeviceStats.reachable`, display-only), the persisted registry verdict, and
|
|
6
|
+
* the cached `tailscale.online` snapshot that the online/offline word actually
|
|
7
|
+
* read. A box reachable every day but whose tailscale snapshot was stale (or a
|
|
8
|
+
* `via:"manual"` device that never got a tailscale field at all) rendered
|
|
9
|
+
* "offline forever". This module is the single ordering the render + the
|
|
10
|
+
* write-back both go through, so the word and the persisted truth agree.
|
|
11
|
+
*/
|
|
12
|
+
import type { DeviceProfile, DeviceReachability, DeviceRegistry } from './registry.js';
|
|
13
|
+
import type { DeviceStats } from './health.js';
|
|
14
|
+
export type OnlineState = 'online' | 'offline' | 'unknown';
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a device's online/offline state, preferring the freshest signal.
|
|
17
|
+
*
|
|
18
|
+
* Precedence: a live stat from THIS run (the probe that just ran) > the
|
|
19
|
+
* persisted reachability verdict written back by a prior probe > the cached
|
|
20
|
+
* `tailscale.online` snapshot. 'unknown' only when nothing at all is known.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deviceOnlineState(d: DeviceProfile, stats?: DeviceStats): OnlineState;
|
|
23
|
+
/** Build the reachability verdict to persist from a fresh probe stat. Pure. */
|
|
24
|
+
export declare function reachabilityFromStats(d: DeviceProfile, stats: DeviceStats): DeviceReachability;
|
|
25
|
+
/**
|
|
26
|
+
* Collect the reachability verdicts to write back for a fleet's freshly-read
|
|
27
|
+
* stats map. Only devices present in the registry are included (a stat for a
|
|
28
|
+
* name we don't track is dropped rather than resurrected). Pure — the caller
|
|
29
|
+
* hands the result to {@link writeReachability}.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectReachabilityWriteBacks(reg: DeviceRegistry, statsMap: Map<string, DeviceStats>): Record<string, DeviceReachability>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a device's online/offline state, preferring the freshest signal.
|
|
3
|
+
*
|
|
4
|
+
* Precedence: a live stat from THIS run (the probe that just ran) > the
|
|
5
|
+
* persisted reachability verdict written back by a prior probe > the cached
|
|
6
|
+
* `tailscale.online` snapshot. 'unknown' only when nothing at all is known.
|
|
7
|
+
*/
|
|
8
|
+
export function deviceOnlineState(d, stats) {
|
|
9
|
+
if (stats)
|
|
10
|
+
return stats.reachable ? 'online' : 'offline';
|
|
11
|
+
if (d.reachability)
|
|
12
|
+
return d.reachability.reachable ? 'online' : 'offline';
|
|
13
|
+
if (d.tailscale)
|
|
14
|
+
return d.tailscale.online ? 'online' : 'offline';
|
|
15
|
+
return 'unknown';
|
|
16
|
+
}
|
|
17
|
+
/** Build the reachability verdict to persist from a fresh probe stat. Pure. */
|
|
18
|
+
export function reachabilityFromStats(d, stats) {
|
|
19
|
+
return {
|
|
20
|
+
reachable: stats.reachable,
|
|
21
|
+
via: d.address?.via,
|
|
22
|
+
checkedAt: new Date(stats.fetchedAt).toISOString(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Collect the reachability verdicts to write back for a fleet's freshly-read
|
|
27
|
+
* stats map. Only devices present in the registry are included (a stat for a
|
|
28
|
+
* name we don't track is dropped rather than resurrected). Pure — the caller
|
|
29
|
+
* hands the result to {@link writeReachability}.
|
|
30
|
+
*/
|
|
31
|
+
export function collectReachabilityWriteBacks(reg, statsMap) {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [name, stats] of statsMap) {
|
|
34
|
+
const d = reg[name];
|
|
35
|
+
if (!d)
|
|
36
|
+
continue;
|
|
37
|
+
out[name] = reachabilityFromStats(d, stats);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
@@ -40,6 +40,24 @@ export interface DeviceTailscale {
|
|
|
40
40
|
relay?: string;
|
|
41
41
|
lastSeen?: string;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Verdict of the last live SSH reachability probe (RUSH-1965).
|
|
45
|
+
*
|
|
46
|
+
* The live `agents devices` probe already learns whether a box answered right
|
|
47
|
+
* now; this persists that answer so the online/offline word is read from a
|
|
48
|
+
* fresh probe instead of the potentially-stale {@link DeviceTailscale.online}
|
|
49
|
+
* snapshot (which only `agents devices sync` / the daemon ever writes). A
|
|
50
|
+
* `via:"manual"` device — which never gets a tailscale peer entry — gets a
|
|
51
|
+
* reachability verdict this way too, so it stops rendering "offline forever".
|
|
52
|
+
*/
|
|
53
|
+
export interface DeviceReachability {
|
|
54
|
+
/** Whether the last live probe reached the device. */
|
|
55
|
+
reachable: boolean;
|
|
56
|
+
/** Transport the verdict came through — the address kind used to dial it. */
|
|
57
|
+
via?: DeviceAddress['via'];
|
|
58
|
+
/** ISO-8601 timestamp of the probe that produced this verdict. */
|
|
59
|
+
checkedAt: string;
|
|
60
|
+
}
|
|
43
61
|
/** A single registered device. */
|
|
44
62
|
export interface DeviceProfile {
|
|
45
63
|
name: string;
|
|
@@ -49,6 +67,10 @@ export interface DeviceProfile {
|
|
|
49
67
|
address: DeviceAddress;
|
|
50
68
|
auth: DeviceAuth;
|
|
51
69
|
tailscale?: DeviceTailscale;
|
|
70
|
+
/** Last live SSH-probe reachability verdict (RUSH-1965). Preferred over the
|
|
71
|
+
* cached {@link DeviceTailscale.online} snapshot when rendering online/offline,
|
|
72
|
+
* because the live probe reflects whether the box answered right now. */
|
|
73
|
+
reachability?: DeviceReachability;
|
|
52
74
|
/** What the device is for. Absent means `worker` (see {@link DeviceRole}). */
|
|
53
75
|
role?: DeviceRole;
|
|
54
76
|
createdAt: string;
|
|
@@ -88,6 +110,7 @@ export interface DeviceInput {
|
|
|
88
110
|
address?: DeviceAddress;
|
|
89
111
|
auth?: DeviceAuth;
|
|
90
112
|
tailscale?: DeviceTailscale;
|
|
113
|
+
reachability?: DeviceReachability;
|
|
91
114
|
role?: DeviceRole;
|
|
92
115
|
}
|
|
93
116
|
/**
|
|
@@ -96,6 +119,16 @@ export interface DeviceInput {
|
|
|
96
119
|
* platform so the two can never drift. Returns the resulting profile.
|
|
97
120
|
*/
|
|
98
121
|
export declare function upsertDevice(name: string, input: DeviceInput): Promise<DeviceProfile>;
|
|
122
|
+
/**
|
|
123
|
+
* Persist live reachability verdicts for many devices in one locked pass
|
|
124
|
+
* (RUSH-1965). A no-op verdict (same `reachable` and not newer than what's
|
|
125
|
+
* stored) is skipped, so a cache-served `agents devices` render doesn't churn
|
|
126
|
+
* the registry every call; only a flip or a fresher probe writes. Unlike
|
|
127
|
+
* {@link upsertDevice} this does NOT bump `updatedAt` — reachability is
|
|
128
|
+
* transient liveness (like the tailscale snapshot), not a profile edit. Unknown
|
|
129
|
+
* device names are ignored. Returns the names actually updated.
|
|
130
|
+
*/
|
|
131
|
+
export declare function writeReachability(updates: Record<string, DeviceReachability>): Promise<string[]>;
|
|
99
132
|
/** Remove a device. Returns false if it was not registered. */
|
|
100
133
|
export declare function removeDevice(name: string): Promise<boolean>;
|
|
101
134
|
/** Load the set of ignored node names. Missing file => empty set. A malformed
|
|
@@ -178,6 +178,7 @@ export async function upsertDevice(name, input) {
|
|
|
178
178
|
address: input.address ?? prev?.address ?? { via: 'manual' },
|
|
179
179
|
auth: input.auth ?? prev?.auth ?? { method: 'key' },
|
|
180
180
|
tailscale: input.tailscale ?? prev?.tailscale,
|
|
181
|
+
reachability: input.reachability ?? prev?.reachability,
|
|
181
182
|
role: input.role ?? prev?.role,
|
|
182
183
|
createdAt: prev?.createdAt ?? now,
|
|
183
184
|
updatedAt: now,
|
|
@@ -187,6 +188,42 @@ export async function upsertDevice(name, input) {
|
|
|
187
188
|
return merged;
|
|
188
189
|
});
|
|
189
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Persist live reachability verdicts for many devices in one locked pass
|
|
193
|
+
* (RUSH-1965). A no-op verdict (same `reachable` and not newer than what's
|
|
194
|
+
* stored) is skipped, so a cache-served `agents devices` render doesn't churn
|
|
195
|
+
* the registry every call; only a flip or a fresher probe writes. Unlike
|
|
196
|
+
* {@link upsertDevice} this does NOT bump `updatedAt` — reachability is
|
|
197
|
+
* transient liveness (like the tailscale snapshot), not a profile edit. Unknown
|
|
198
|
+
* device names are ignored. Returns the names actually updated.
|
|
199
|
+
*/
|
|
200
|
+
export async function writeReachability(updates) {
|
|
201
|
+
const names = Object.keys(updates);
|
|
202
|
+
if (names.length === 0)
|
|
203
|
+
return [];
|
|
204
|
+
const p = registryPath();
|
|
205
|
+
return withRegistryLock(p, async () => {
|
|
206
|
+
const reg = await loadDevices();
|
|
207
|
+
const changed = [];
|
|
208
|
+
for (const name of names) {
|
|
209
|
+
const prev = reg[name];
|
|
210
|
+
if (!prev)
|
|
211
|
+
continue; // never resurrect a device the user removed
|
|
212
|
+
const next = updates[name];
|
|
213
|
+
const cur = prev.reachability;
|
|
214
|
+
if (cur &&
|
|
215
|
+
cur.reachable === next.reachable &&
|
|
216
|
+
Date.parse(cur.checkedAt) >= Date.parse(next.checkedAt)) {
|
|
217
|
+
continue; // unchanged verdict, no fresher timestamp — skip the write
|
|
218
|
+
}
|
|
219
|
+
reg[name] = { ...prev, reachability: next };
|
|
220
|
+
changed.push(name);
|
|
221
|
+
}
|
|
222
|
+
if (changed.length > 0)
|
|
223
|
+
await saveDevices(reg);
|
|
224
|
+
return changed;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
190
227
|
/** Remove a device. Returns false if it was not registered. */
|
|
191
228
|
export async function removeDevice(name) {
|
|
192
229
|
const p = registryPath();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { splitUserHost } from '../hosts/registry.js';
|
|
2
|
+
import { type DeviceProfile } from './registry.js';
|
|
3
|
+
export { splitUserHost };
|
|
2
4
|
/** A dialable peer: the ssh target, the machine id used to tag its rows, a
|
|
3
5
|
* display name, and the OS family that picks the remote shell dialect. */
|
|
4
6
|
export interface ResolvedSshTarget {
|
|
@@ -7,35 +9,21 @@ export interface ResolvedSshTarget {
|
|
|
7
9
|
name: string;
|
|
8
10
|
os?: string;
|
|
9
11
|
}
|
|
10
|
-
/** Split a `user@host` / `host` token into its login user (if any) and host part. */
|
|
11
|
-
export declare function splitUserHost(token: string): {
|
|
12
|
-
user?: string;
|
|
13
|
-
host: string;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Resolve one `--host`/`--device` token to a concrete ssh target through the
|
|
17
|
-
* registry. Registry hit → the device's real address + platform (so the machine
|
|
18
|
-
* id, route, and OS all match the auto-discovery sweep), with any `user@`
|
|
19
|
-
* overriding the login account. Miss → a literal `user@host` fallback, its OS
|
|
20
|
-
* taken from the host overlay if enrolled. Returns undefined only when the token
|
|
21
|
-
* fails the shared ssh-target injection guard.
|
|
22
|
-
*/
|
|
23
|
-
export declare function resolveSshTarget(token: string, reg: DeviceRegistry): ResolvedSshTarget | undefined;
|
|
24
12
|
/**
|
|
25
13
|
* Resolve a target token to a full {@link DeviceProfile} for `agents ssh`. Same
|
|
26
|
-
* grammar as
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* returns undefined so the caller reports "Unknown
|
|
32
|
-
*
|
|
14
|
+
* grammar as the fan-out, but returns the whole profile (auth, shell, tailscale
|
|
15
|
+
* metadata) `buildSshInvocation` needs. A registered `name` / `user@device`
|
|
16
|
+
* yields that profile (login user overridden by any `user@`); an ad-hoc
|
|
17
|
+
* `user@host` / IP / FQDN literal yields a synthesized key-auth profile. A bare
|
|
18
|
+
* unregistered alias (no `@`/dot) — or an ssh_config-only alias, which `agents
|
|
19
|
+
* ssh` has never dialed — returns undefined so the caller reports "Unknown
|
|
20
|
+
* device" rather than dialing a literal.
|
|
33
21
|
*/
|
|
34
|
-
export declare function resolveDeviceTarget(token: string
|
|
22
|
+
export declare function resolveDeviceTarget(token: string): Promise<DeviceProfile | undefined>;
|
|
35
23
|
/**
|
|
36
|
-
* Resolve an explicit `--host`/`--device` list to dialable targets
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
24
|
+
* Resolve an explicit `--host`/`--device` list to dialable targets. A token that
|
|
25
|
+
* fails the injection guard or names nothing reachable is skipped with a stderr
|
|
26
|
+
* note (never fatal — one bad token must not blank the fan-out). Shared by every
|
|
27
|
+
* cross-machine fan-out so they can never diverge onto two routes.
|
|
40
28
|
*/
|
|
41
29
|
export declare function resolveExplicitTargets(hosts: string[]): Promise<ResolvedSshTarget[]>;
|
|
@@ -1,86 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Fan-out + `agents ssh` adapters over the single host/device resolver.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
4
|
+
* Every `--host` / `--device` token becomes a concrete target through one core,
|
|
5
|
+
* {@link matchHost} in ../hosts/registry.ts — the merged directory that reads the
|
|
6
|
+
* devices registry, the agents.yaml overlay, AND ssh_config (RUSH-1967). This
|
|
7
|
+
* module is only the two thin adapters the fan-out and interactive-ssh paths
|
|
8
|
+
* need on top of that core:
|
|
9
|
+
* - {@link resolveExplicitTargets} — a token list → dialable `{target, machine,
|
|
10
|
+
* name, os}` rows, so `sessions --host` / session bundles / remote agents-json
|
|
11
|
+
* dial the exact same address (and machine id) `run --host` does.
|
|
12
|
+
* - {@link resolveDeviceTarget} — a token → the full {@link DeviceProfile}
|
|
13
|
+
* `agents ssh` needs (auth, shell, tailscale metadata), with the same
|
|
14
|
+
* grammar; a bare unregistered alias returns undefined ("Unknown device").
|
|
15
15
|
*
|
|
16
|
-
* The grammar is uniform across the fleet: `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* short-circuits to a bare `ssh muqsit@mac-mini` (LAN DNS). A `user@host` that
|
|
20
|
-
* matches no registered device falls back to a literal target so ad-hoc boxes
|
|
21
|
-
* still work.
|
|
16
|
+
* The grammar is uniform across the fleet: `name`, `user@name` (same device,
|
|
17
|
+
* login user overridden), a tailnet FQDN, an ssh_config alias, and an ad-hoc
|
|
18
|
+
* `user@host` all resolve identically no matter which subcommand called.
|
|
22
19
|
*/
|
|
23
20
|
import chalk from 'chalk';
|
|
24
|
-
import {
|
|
21
|
+
import { sshTargetFor } from '../hosts/types.js';
|
|
22
|
+
import { matchHost, splitUserHost } from '../hosts/registry.js';
|
|
25
23
|
import { normalizeHost } from '../machine-id.js';
|
|
26
24
|
import { resolveRemoteOsSync } from '../hosts/remote-os.js';
|
|
27
|
-
|
|
28
|
-
import { loadDevices } from './registry.js';
|
|
29
|
-
/** Split a `user@host` / `host` token into its login user (if any) and host part. */
|
|
30
|
-
export function splitUserHost(token) {
|
|
31
|
-
const at = token.indexOf('@');
|
|
32
|
-
return at === -1 ? { host: token } : { user: token.slice(0, at), host: token.slice(at + 1) };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Match a host part (the piece after any `user@`) to a registered device: exact
|
|
36
|
-
* registry key first, then a normalized-host match so `yosemite-s0` and
|
|
37
|
-
* `yosemite-s0.<tailnet>.ts.net` land on the same profile. The single source of
|
|
38
|
-
* truth both `resolveSshTarget` (fan-out) and `resolveDeviceTarget` (`agents
|
|
39
|
-
* ssh`) share, so a `user@device` can never resolve two different routes.
|
|
40
|
-
*/
|
|
41
|
-
function matchDevice(host, reg) {
|
|
42
|
-
return reg[host] ?? Object.values(reg).find((d) => normalizeHost(d.name) === normalizeHost(host));
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Resolve one `--host`/`--device` token to a concrete ssh target through the
|
|
46
|
-
* registry. Registry hit → the device's real address + platform (so the machine
|
|
47
|
-
* id, route, and OS all match the auto-discovery sweep), with any `user@`
|
|
48
|
-
* overriding the login account. Miss → a literal `user@host` fallback, its OS
|
|
49
|
-
* taken from the host overlay if enrolled. Returns undefined only when the token
|
|
50
|
-
* fails the shared ssh-target injection guard.
|
|
51
|
-
*/
|
|
52
|
-
export function resolveSshTarget(token, reg) {
|
|
53
|
-
try {
|
|
54
|
-
assertValidSshTarget(token);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
const { user, host } = splitUserHost(token);
|
|
60
|
-
// A device and a `user@device` are the same box; resolve the host part through
|
|
61
|
-
// the registry so both dial the Tailscale route, and let an explicit `user@`
|
|
62
|
-
// override only the login account.
|
|
63
|
-
const device = matchDevice(host, reg);
|
|
64
|
-
if (device) {
|
|
65
|
-
try {
|
|
66
|
-
const effective = user ? { ...device, user } : device;
|
|
67
|
-
return { target: sshTargetFor(effective), machine: normalizeHost(device.name), name: device.name, os: device.platform };
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
// Registered but has no address to dial — fall through to the literal token.
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { target: token, machine: normalizeHost(host), name: token, os: resolveRemoteOsSync(token) };
|
|
74
|
-
}
|
|
25
|
+
export { splitUserHost };
|
|
75
26
|
/** Timestamps for a synthesized ad-hoc profile — never persisted, so a constant
|
|
76
27
|
* keeps the value deterministic (and side-effect free) without reading the clock. */
|
|
77
28
|
const SYNTH_TS = '1970-01-01T00:00:00.000Z';
|
|
78
|
-
/** True when a token is clearly a network target (a `user@`, or a dotted/IPv6
|
|
79
|
-
* host / IP) rather than a bare alias. A bare unknown word is a typo, so `agents
|
|
80
|
-
* ssh foo` still says "Unknown device" instead of dialing a literal `foo`. */
|
|
81
|
-
function looksLikeHostLiteral(token) {
|
|
82
|
-
return token.includes('@') || token.includes('.') || token.includes(':');
|
|
83
|
-
}
|
|
84
29
|
/** Synthesize a throwaway device profile for an ad-hoc `user@host` / `host`
|
|
85
30
|
* literal so `agents ssh` can dial a box that was never registered. */
|
|
86
31
|
function adHocDevice(token, host, user) {
|
|
@@ -97,49 +42,65 @@ function adHocDevice(token, host, user) {
|
|
|
97
42
|
};
|
|
98
43
|
}
|
|
99
44
|
/**
|
|
100
|
-
* Resolve
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* yields a synthesized key-auth profile. A bare unregistered alias (no `@`/dot)
|
|
106
|
-
* returns undefined so the caller reports "Unknown device" rather than dialing a
|
|
107
|
-
* literal — the strict behaviour the interactive wrapper has always had.
|
|
45
|
+
* Resolve one token to a dialable {@link ResolvedSshTarget}, or undefined when it
|
|
46
|
+
* fails the injection guard or names nothing reachable. A registered device (or
|
|
47
|
+
* `user@device`) dials its live Tailscale route; an ssh_config-only alias dials
|
|
48
|
+
* by its bare name (ssh applies the stanza) — now visible to the fan-out too; an
|
|
49
|
+
* ad-hoc `user@host` dials the literal.
|
|
108
50
|
*/
|
|
109
|
-
|
|
51
|
+
async function toResolvedTarget(token) {
|
|
52
|
+
const host = await matchHost(token);
|
|
53
|
+
if (!host)
|
|
54
|
+
return undefined;
|
|
55
|
+
let target;
|
|
110
56
|
try {
|
|
111
|
-
|
|
57
|
+
target = sshTargetFor(host);
|
|
112
58
|
}
|
|
113
59
|
catch {
|
|
60
|
+
return undefined; // matched a device/host with no address to dial
|
|
61
|
+
}
|
|
62
|
+
const hostPart = host.device ? host.device.name : splitUserHost(host.name).host;
|
|
63
|
+
const name = host.device ? host.device.name : host.name;
|
|
64
|
+
return { target, machine: normalizeHost(hostPart), name, os: host.os ?? resolveRemoteOsSync(name) };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a target token to a full {@link DeviceProfile} for `agents ssh`. Same
|
|
68
|
+
* grammar as the fan-out, but returns the whole profile (auth, shell, tailscale
|
|
69
|
+
* metadata) `buildSshInvocation` needs. A registered `name` / `user@device`
|
|
70
|
+
* yields that profile (login user overridden by any `user@`); an ad-hoc
|
|
71
|
+
* `user@host` / IP / FQDN literal yields a synthesized key-auth profile. A bare
|
|
72
|
+
* unregistered alias (no `@`/dot) — or an ssh_config-only alias, which `agents
|
|
73
|
+
* ssh` has never dialed — returns undefined so the caller reports "Unknown
|
|
74
|
+
* device" rather than dialing a literal.
|
|
75
|
+
*/
|
|
76
|
+
export async function resolveDeviceTarget(token) {
|
|
77
|
+
const host = await matchHost(token, { allowBareLiteral: true });
|
|
78
|
+
if (!host)
|
|
114
79
|
return undefined;
|
|
80
|
+
if (host.device) {
|
|
81
|
+
const { user } = splitUserHost(token);
|
|
82
|
+
return user ? { ...host.device, user } : host.device;
|
|
115
83
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
84
|
+
if (host.adhoc) {
|
|
85
|
+
const { user, host: hostPart } = splitUserHost(token);
|
|
86
|
+
return adHocDevice(token, hostPart, user);
|
|
87
|
+
}
|
|
88
|
+
// An overlay / ssh_config-only match is not a device — `agents ssh` stays
|
|
89
|
+
// devices-and-literals only, so report it as unknown.
|
|
122
90
|
return undefined;
|
|
123
91
|
}
|
|
124
92
|
/**
|
|
125
|
-
* Resolve an explicit `--host`/`--device` list to dialable targets
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
93
|
+
* Resolve an explicit `--host`/`--device` list to dialable targets. A token that
|
|
94
|
+
* fails the injection guard or names nothing reachable is skipped with a stderr
|
|
95
|
+
* note (never fatal — one bad token must not blank the fan-out). Shared by every
|
|
96
|
+
* cross-machine fan-out so they can never diverge onto two routes.
|
|
129
97
|
*/
|
|
130
98
|
export async function resolveExplicitTargets(hosts) {
|
|
131
|
-
let reg;
|
|
132
|
-
try {
|
|
133
|
-
reg = await loadDevices();
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
reg = {};
|
|
137
|
-
}
|
|
138
99
|
const out = [];
|
|
139
100
|
for (const h of hosts) {
|
|
140
|
-
const resolved =
|
|
101
|
+
const resolved = await toResolvedTarget(h);
|
|
141
102
|
if (!resolved) {
|
|
142
|
-
process.stderr.write(chalk.gray(` ${h}: not a
|
|
103
|
+
process.stderr.write(chalk.gray(` ${h}: not a resolvable ssh target — skipped\n`));
|
|
143
104
|
continue;
|
|
144
105
|
}
|
|
145
106
|
out.push(resolved);
|
|
@@ -45,6 +45,24 @@ export interface DeviceSyncResult {
|
|
|
45
45
|
/** Populated when ok is false: why discovery was skipped. */
|
|
46
46
|
reason?: string;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The nodes automatic discovery is allowed to see: sharee nodes (shared INTO
|
|
50
|
+
* the tailnet by another user, e.g. a funnel ingress relay) are not the
|
|
51
|
+
* operator's machines, so they must never be bootstrap-registered nor surfaced
|
|
52
|
+
* as pending. Explicit paths (`devices register <name>`, `devices add`, the
|
|
53
|
+
* `fleet:` manifest bootstrap) are deliberate user actions and stay unfiltered.
|
|
54
|
+
* Pure so the policy is unit-testable without a tailnet.
|
|
55
|
+
*/
|
|
56
|
+
export declare function discoverableNodes(nodes: TailscaleNode[]): TailscaleNode[];
|
|
57
|
+
/**
|
|
58
|
+
* Default checked-state for a node in the interactive sync picker. Pressing
|
|
59
|
+
* Enter registers every checked node, so "checked" must mean "auto-sync would
|
|
60
|
+
* register this": not dismissed, and not a sharee node — unless the user
|
|
61
|
+
* already deliberately registered that sharee node, in which case Enter must
|
|
62
|
+
* keep the fleet as-is. Pure so the default matrix is unit-testable without a
|
|
63
|
+
* prompt.
|
|
64
|
+
*/
|
|
65
|
+
export declare function defaultPickerChecked(node: TailscaleNode, registered: Set<string>, ignored: Set<string>): boolean;
|
|
48
66
|
/**
|
|
49
67
|
* Node names present on the tailnet but neither already in the registry nor on
|
|
50
68
|
* the ignore-list — i.e. genuinely new devices worth surfacing. Pure so the
|
package/dist/lib/devices/sync.js
CHANGED
|
@@ -59,6 +59,28 @@ export function withDefaultUser(input, prevUser, localUser) {
|
|
|
59
59
|
return input;
|
|
60
60
|
return { ...input, user: localUser };
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The nodes automatic discovery is allowed to see: sharee nodes (shared INTO
|
|
64
|
+
* the tailnet by another user, e.g. a funnel ingress relay) are not the
|
|
65
|
+
* operator's machines, so they must never be bootstrap-registered nor surfaced
|
|
66
|
+
* as pending. Explicit paths (`devices register <name>`, `devices add`, the
|
|
67
|
+
* `fleet:` manifest bootstrap) are deliberate user actions and stay unfiltered.
|
|
68
|
+
* Pure so the policy is unit-testable without a tailnet.
|
|
69
|
+
*/
|
|
70
|
+
export function discoverableNodes(nodes) {
|
|
71
|
+
return nodes.filter((n) => !n.sharee);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Default checked-state for a node in the interactive sync picker. Pressing
|
|
75
|
+
* Enter registers every checked node, so "checked" must mean "auto-sync would
|
|
76
|
+
* register this": not dismissed, and not a sharee node — unless the user
|
|
77
|
+
* already deliberately registered that sharee node, in which case Enter must
|
|
78
|
+
* keep the fleet as-is. Pure so the default matrix is unit-testable without a
|
|
79
|
+
* prompt.
|
|
80
|
+
*/
|
|
81
|
+
export function defaultPickerChecked(node, registered, ignored) {
|
|
82
|
+
return !ignored.has(node.name) && (!node.sharee || registered.has(node.name));
|
|
83
|
+
}
|
|
62
84
|
/**
|
|
63
85
|
* Node names present on the tailnet but neither already in the registry nor on
|
|
64
86
|
* the ignore-list — i.e. genuinely new devices worth surfacing. Pure so the
|
|
@@ -101,7 +123,7 @@ export async function runDeviceSync(opts = {}) {
|
|
|
101
123
|
// the same host at once) would otherwise abort the whole `agents sync`. The
|
|
102
124
|
// whole body is inside the guard so the "never a sync failure" promise holds.
|
|
103
125
|
try {
|
|
104
|
-
const nodes = parseTailscaleStatus(tailscaleStatusJson());
|
|
126
|
+
const nodes = discoverableNodes(parseTailscaleStatus(tailscaleStatusJson()));
|
|
105
127
|
const [registeredBefore, ignored] = await Promise.all([loadDevices(), loadIgnored()]);
|
|
106
128
|
const registered = new Set(Object.keys(registeredBefore));
|
|
107
129
|
const pendingNames = computePendingDevices(nodes, registered, ignored);
|
|
@@ -9,6 +9,9 @@ export interface TailscaleNode {
|
|
|
9
9
|
direct: boolean;
|
|
10
10
|
relay?: string;
|
|
11
11
|
lastSeen?: string;
|
|
12
|
+
/** True for a node another user shared INTO this tailnet — not the operator's
|
|
13
|
+
* own machine, so discovery must never auto-register or suggest it. */
|
|
14
|
+
sharee: boolean;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Slugify a raw Tailscale HostName into a valid logical device name (ssh alias
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Performance tracking: withTiming() wrapper for any async function
|
|
13
13
|
*/
|
|
14
14
|
export type EventLevel = 'audit' | 'warn' | 'info' | 'debug';
|
|
15
|
-
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'file.edited' | 'error' | 'warn' | 'info' | 'debug';
|
|
15
|
+
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created' | 'status.posted' | 'file.edited' | 'error' | 'warn' | 'info' | 'debug';
|
|
16
16
|
export declare function levelFor(event: EventType): EventLevel;
|
|
17
17
|
export interface EventMeta {
|
|
18
18
|
ts: string;
|