@phnx-labs/agents-cli 1.20.91 → 1.20.93

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +276 -0
  2. package/README.md +1 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +72 -6
  5. package/dist/commands/activity.js +198 -49
  6. package/dist/commands/beta.js +1 -0
  7. package/dist/commands/doctor.js +4 -2
  8. package/dist/commands/events.js +91 -1
  9. package/dist/commands/exec.d.ts +14 -0
  10. package/dist/commands/exec.js +144 -14
  11. package/dist/commands/projects.d.ts +22 -0
  12. package/dist/commands/projects.js +539 -0
  13. package/dist/commands/secrets.d.ts +17 -0
  14. package/dist/commands/secrets.js +198 -7
  15. package/dist/commands/send.d.ts +14 -12
  16. package/dist/commands/send.js +105 -35
  17. package/dist/commands/sessions-picker.d.ts +15 -0
  18. package/dist/commands/sessions-picker.js +37 -12
  19. package/dist/commands/sessions-resume.d.ts +2 -0
  20. package/dist/commands/sessions-resume.js +9 -1
  21. package/dist/commands/sessions.d.ts +10 -5
  22. package/dist/commands/sessions.js +65 -27
  23. package/dist/commands/sync.js +9 -3
  24. package/dist/commands/view.js +4 -0
  25. package/dist/index.js +18 -1
  26. package/dist/lib/activity.d.ts +77 -12
  27. package/dist/lib/activity.js +424 -74
  28. package/dist/lib/beta.d.ts +1 -1
  29. package/dist/lib/beta.js +1 -1
  30. package/dist/lib/channels/send.d.ts +83 -0
  31. package/dist/lib/channels/send.js +112 -0
  32. package/dist/lib/devices/registry.d.ts +14 -0
  33. package/dist/lib/devices/registry.js +37 -0
  34. package/dist/lib/events-ingest.d.ts +46 -0
  35. package/dist/lib/events-ingest.js +182 -0
  36. package/dist/lib/events.d.ts +15 -3
  37. package/dist/lib/events.js +55 -3
  38. package/dist/lib/feed-post.js +8 -2
  39. package/dist/lib/hosts/remote-cmd.js +4 -0
  40. package/dist/lib/linear-project-counts.d.ts +62 -0
  41. package/dist/lib/linear-project-counts.js +122 -0
  42. package/dist/lib/linear-projects.d.ts +50 -0
  43. package/dist/lib/linear-projects.js +114 -0
  44. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  45. package/dist/lib/menubar/install-menubar.d.ts +14 -4
  46. package/dist/lib/menubar/install-menubar.js +20 -6
  47. package/dist/lib/menubar/notify-desktop.d.ts +17 -2
  48. package/dist/lib/menubar/notify-desktop.js +8 -2
  49. package/dist/lib/project-key.d.ts +44 -0
  50. package/dist/lib/project-key.js +79 -0
  51. package/dist/lib/project-probe.d.ts +75 -0
  52. package/dist/lib/project-probe.js +160 -0
  53. package/dist/lib/project-resources.d.ts +8 -0
  54. package/dist/lib/project-resources.js +31 -3
  55. package/dist/lib/project-root.js +16 -0
  56. package/dist/lib/project-status.d.ts +100 -0
  57. package/dist/lib/project-status.js +182 -0
  58. package/dist/lib/projects.d.ts +144 -0
  59. package/dist/lib/projects.js +313 -0
  60. package/dist/lib/remote-agents-json.d.ts +9 -0
  61. package/dist/lib/remote-agents-json.js +11 -5
  62. package/dist/lib/routine-notify.d.ts +11 -0
  63. package/dist/lib/routine-notify.js +22 -0
  64. package/dist/lib/run-notify.js +3 -0
  65. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  66. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  67. package/dist/lib/secrets/audit.d.ts +1 -1
  68. package/dist/lib/secrets/audit.js +53 -10
  69. package/dist/lib/secrets/list-filter.d.ts +20 -5
  70. package/dist/lib/secrets/list-filter.js +22 -6
  71. package/dist/lib/secrets/usage-db.d.ts +106 -0
  72. package/dist/lib/secrets/usage-db.js +236 -0
  73. package/dist/lib/session/bash-command.d.ts +53 -0
  74. package/dist/lib/session/bash-command.js +364 -0
  75. package/dist/lib/session/digest.d.ts +6 -0
  76. package/dist/lib/session/digest.js +19 -0
  77. package/dist/lib/session/relative-time.d.ts +23 -0
  78. package/dist/lib/session/relative-time.js +60 -8
  79. package/dist/lib/session/remote-active.d.ts +5 -1
  80. package/dist/lib/session/remote-active.js +4 -1
  81. package/dist/lib/session/remote-list.js +5 -2
  82. package/dist/lib/session/render.d.ts +2 -9
  83. package/dist/lib/session/render.js +25 -56
  84. package/dist/lib/sqlite.js +28 -1
  85. package/dist/lib/ssh-exec.d.ts +6 -0
  86. package/dist/lib/ssh-exec.js +10 -1
  87. package/dist/lib/startup/command-registry.d.ts +1 -0
  88. package/dist/lib/startup/command-registry.js +2 -0
  89. package/dist/lib/state.d.ts +14 -0
  90. package/dist/lib/state.js +19 -0
  91. package/dist/lib/terminal/backends/index.d.ts +10 -2
  92. package/dist/lib/terminal/backends/index.js +14 -2
  93. package/dist/lib/terminal/backends/terminal-app.d.ts +13 -0
  94. package/dist/lib/terminal/backends/terminal-app.js +73 -0
  95. package/dist/lib/terminal/index.d.ts +2 -1
  96. package/dist/lib/terminal/index.js +2 -1
  97. package/dist/lib/terminal/preferred.d.ts +89 -0
  98. package/dist/lib/terminal/preferred.js +87 -0
  99. package/dist/lib/terminal/run-surface.d.ts +82 -0
  100. package/dist/lib/terminal/run-surface.js +146 -0
  101. package/dist/lib/terminal/types.d.ts +1 -1
  102. package/dist/lib/types.d.ts +6 -5
  103. package/dist/lib/versions.d.ts +6 -0
  104. package/dist/lib/versions.js +6 -4
  105. package/package.json +2 -1
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `agents run … --terminal` — re-open this exact run as a tab in a real terminal.
3
+ *
4
+ * A GUI caller (the menu bar's "New Session") cannot host a TUI, so it has to
5
+ * hand the run to a terminal. It used to do that by hardcoding AppleScript at
6
+ * Terminal.app; now it appends `--terminal` and the CLI decides WHICH terminal
7
+ * from the user's own live sessions (preferred.ts) and opens the tab through the
8
+ * launch engine — the same engine `sessions resume` and `sessions focus` use.
9
+ *
10
+ * The re-invocation is the caller's own argv with the `--terminal` flag removed,
11
+ * so every other flag (`--mode`, `--cwd`, a `--` passthrough) rides along
12
+ * untouched and there is no second place that knows how to spell a run.
13
+ */
14
+ import type { Backend, EngineContext } from './types.js';
15
+ import type { ActiveSession } from '../session/active.js';
16
+ import { type LaunchBackendChoice, type SessionHostSample } from './preferred.js';
17
+ /** Backends a user may name in `--terminal <backend>`. */
18
+ export declare const TERMINAL_FLAG_BACKENDS: Backend[];
19
+ /**
20
+ * Validate a `--terminal <value>`. Returns the backend, or an error message
21
+ * naming the valid ids — never a silent fallback to auto-detection, which would
22
+ * open a terminal the user did not ask for.
23
+ */
24
+ export declare function parseTerminalFlag(value: unknown): {
25
+ backend?: Backend;
26
+ error?: string;
27
+ };
28
+ /**
29
+ * The argv to re-invoke, with `--terminal` (and the value commander consumed for
30
+ * it) removed. `consumedValue` is the parsed option value when it is a string —
31
+ * that is the only token after the flag that belongs to it, so a prompt or a
32
+ * following flag is never eaten.
33
+ */
34
+ export declare function stripTerminalFlag(argv: string[], consumedValue?: string): string[];
35
+ /** `agents run …` as a shell-safe command line for the surface to exec. */
36
+ export declare function buildRunCommand(argv: string[]): string[];
37
+ /**
38
+ * Turn live sessions into the samples the resolver reads, filling in the app
39
+ * each tmux-hosted session is currently VIEWED in.
40
+ *
41
+ * This step is what makes detection work for the common case: `agents run`
42
+ * wraps interactive runs in tmux, so a session the user started in Ghostty is
43
+ * attributed `host: 'tmux'` on the discovery path and would otherwise name no
44
+ * terminal at all. `resolveViewingIn` walks the attached tmux client's pid up to
45
+ * its host app — the same resolver `agents sessions` uses to print
46
+ * "viewing in Ghostty tab 2". Sessions that are detached (no client attached)
47
+ * legitimately have no viewer and keep their `tmux` host.
48
+ *
49
+ * One inherited nuance: `resolveViewingIn` labels a client whose app it cannot
50
+ * identify `'terminal'` (viewing-in.ts:87), so a tmux session viewed from an
51
+ * unrecognized emulator resolves to Terminal.app rather than falling through.
52
+ * That lands on the same every-Mac floor the fallback chain ends at anyway, so
53
+ * it costs nothing here — but it is a default, not a detection.
54
+ *
55
+ * Best-effort: any probe failure degrades to the plain host, never throws.
56
+ */
57
+ export declare function toHostSamples(sessions: ActiveSession[]): Promise<SessionHostSample[]>;
58
+ export interface OpenRunSurfaceParams {
59
+ /** This process's argv after the program name (i.e. `['run','claude',…]`). */
60
+ argv: string[];
61
+ /** The parsed `--terminal` value, when the user named a backend. */
62
+ forced?: Backend;
63
+ /** The value commander consumed for `--terminal`, so it can be stripped. */
64
+ consumedValue?: string;
65
+ cwd: string;
66
+ /** Live sessions, used to detect the terminal the user actually works in. */
67
+ sessions: SessionHostSample[];
68
+ ctx: EngineContext;
69
+ }
70
+ export interface OpenRunSurfaceResult {
71
+ ok: boolean;
72
+ choice?: LaunchBackendChoice;
73
+ /** Human line describing the terminal that was chosen. */
74
+ description?: string;
75
+ error?: string;
76
+ }
77
+ /**
78
+ * Open the run as a tab in the resolved terminal. Never throws — a failure comes
79
+ * back as `ok: false` with the reason, so the caller can tell the user rather
80
+ * than exiting silently.
81
+ */
82
+ export declare function openRunInTerminal(params: OpenRunSurfaceParams): Promise<OpenRunSurfaceResult>;
@@ -0,0 +1,146 @@
1
+ import { BACKENDS } from './backends/index.js';
2
+ import { openSurface } from './engine.js';
3
+ import { getCliLaunch } from '../cli-entry.js';
4
+ import { shellQuote } from './quote.js';
5
+ import { resolveLaunchBackend, describeBackendChoice, } from './preferred.js';
6
+ /** Backends a user may name in `--terminal <backend>`. */
7
+ export const TERMINAL_FLAG_BACKENDS = Object.keys(BACKENDS);
8
+ /**
9
+ * Validate a `--terminal <value>`. Returns the backend, or an error message
10
+ * naming the valid ids — never a silent fallback to auto-detection, which would
11
+ * open a terminal the user did not ask for.
12
+ */
13
+ export function parseTerminalFlag(value) {
14
+ if (value === undefined || value === true || value === '')
15
+ return {};
16
+ const raw = String(value);
17
+ if (TERMINAL_FLAG_BACKENDS.includes(raw))
18
+ return { backend: raw };
19
+ // `--terminal [backend]` takes an OPTIONAL value, and commander assigns the
20
+ // next non-option token to it — so `agents run claude --terminal "fix the bug"`
21
+ // lands the prompt here. Say that, or the user just sees their prompt called a
22
+ // bad backend name and has no idea why.
23
+ const looksLikeAPrompt = /\s/.test(raw) || raw.length > 24;
24
+ const hint = looksLikeAPrompt
25
+ ? ` That looks like a prompt: put it BEFORE the flag — agents run <agent> "${raw.length > 40 ? `${raw.slice(0, 40)}…` : raw}" --terminal.`
26
+ : '';
27
+ return {
28
+ error: `Unknown --terminal backend '${raw}'. Use one of: ${TERMINAL_FLAG_BACKENDS.join(', ')} (or pass --terminal alone to auto-detect).${hint}`,
29
+ };
30
+ }
31
+ /**
32
+ * The argv to re-invoke, with `--terminal` (and the value commander consumed for
33
+ * it) removed. `consumedValue` is the parsed option value when it is a string —
34
+ * that is the only token after the flag that belongs to it, so a prompt or a
35
+ * following flag is never eaten.
36
+ */
37
+ export function stripTerminalFlag(argv, consumedValue) {
38
+ const out = [];
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const tok = argv[i];
41
+ // Everything past a bare `--` is forwarded verbatim to the agent's own CLI
42
+ // (`agents run kimi -- --terminal`), so it is not ours to rewrite.
43
+ if (tok === '--') {
44
+ out.push(...argv.slice(i));
45
+ break;
46
+ }
47
+ if (tok === '--terminal') {
48
+ if (consumedValue !== undefined && argv[i + 1] === consumedValue)
49
+ i++;
50
+ continue;
51
+ }
52
+ if (tok.startsWith('--terminal='))
53
+ continue;
54
+ out.push(tok);
55
+ }
56
+ return out;
57
+ }
58
+ /** `agents run …` as a shell-safe command line for the surface to exec. */
59
+ export function buildRunCommand(argv) {
60
+ const { command, args } = getCliLaunch(argv);
61
+ return [command, ...args].map(shellQuote);
62
+ }
63
+ /**
64
+ * Turn live sessions into the samples the resolver reads, filling in the app
65
+ * each tmux-hosted session is currently VIEWED in.
66
+ *
67
+ * This step is what makes detection work for the common case: `agents run`
68
+ * wraps interactive runs in tmux, so a session the user started in Ghostty is
69
+ * attributed `host: 'tmux'` on the discovery path and would otherwise name no
70
+ * terminal at all. `resolveViewingIn` walks the attached tmux client's pid up to
71
+ * its host app — the same resolver `agents sessions` uses to print
72
+ * "viewing in Ghostty tab 2". Sessions that are detached (no client attached)
73
+ * legitimately have no viewer and keep their `tmux` host.
74
+ *
75
+ * One inherited nuance: `resolveViewingIn` labels a client whose app it cannot
76
+ * identify `'terminal'` (viewing-in.ts:87), so a tmux session viewed from an
77
+ * unrecognized emulator resolves to Terminal.app rather than falling through.
78
+ * That lands on the same every-Mac floor the fallback chain ends at anyway, so
79
+ * it costs nothing here — but it is a default, not a detection.
80
+ *
81
+ * Best-effort: any probe failure degrades to the plain host, never throws.
82
+ */
83
+ export async function toHostSamples(sessions) {
84
+ const samples = sessions.map((s) => ({
85
+ host: s.host,
86
+ lastActivityMs: s.lastActivityMs,
87
+ startedAtMs: s.startedAtMs,
88
+ }));
89
+ const tmuxIdx = sessions
90
+ .map((s, i) => ({ s, i }))
91
+ .filter(({ s }) => s.provenance?.mux?.kind === 'tmux' && s.provenance.mux.pane);
92
+ if (tmuxIdx.length === 0)
93
+ return samples;
94
+ try {
95
+ const { enumerateGhosttyTabs } = await import('../session/ghostty-tabs.js');
96
+ const { mapPanesToTargets, listClients } = await import('../tmux/session.js');
97
+ const { resolveViewingIn } = await import('../session/viewing-in.js');
98
+ // One Ghostty enumeration shared across sockets, as the sessions renderer does.
99
+ const ghosttySurfaces = await enumerateGhosttyTabs();
100
+ const sockets = new Set(tmuxIdx.map(({ s }) => s.provenance.mux.socket));
101
+ for (const socket of sockets) {
102
+ const paneToTarget = await mapPanesToTargets(socket);
103
+ if (paneToTarget.size === 0)
104
+ continue;
105
+ const clients = await listClients(socket);
106
+ for (const { s, i } of tmuxIdx) {
107
+ if (s.provenance.mux.socket !== socket)
108
+ continue;
109
+ const viewing = await resolveViewingIn(s, clients, { paneToTarget, ghosttySurfaces });
110
+ if (viewing)
111
+ samples[i].viewingApp = viewing.app;
112
+ }
113
+ }
114
+ }
115
+ catch {
116
+ // tmux/Ghostty probes are best-effort; fall back to the plain host values.
117
+ }
118
+ return samples;
119
+ }
120
+ /**
121
+ * Open the run as a tab in the resolved terminal. Never throws — a failure comes
122
+ * back as `ok: false` with the reason, so the caller can tell the user rather
123
+ * than exiting silently.
124
+ */
125
+ export async function openRunInTerminal(params) {
126
+ const choice = params.forced
127
+ ? { backend: params.forced, source: 'forced' }
128
+ : resolveLaunchBackend(params.ctx, params.sessions);
129
+ if (!choice) {
130
+ return {
131
+ ok: false,
132
+ error: 'No terminal this machine can drive (need iTerm, Ghostty, Terminal.app, VSCodium, or a tmux session). Run without --terminal.',
133
+ };
134
+ }
135
+ if (params.forced && !BACKENDS[choice.backend].isAvailable(params.ctx)) {
136
+ return { ok: false, error: `--terminal ${choice.backend} is not available here.` };
137
+ }
138
+ const command = buildRunCommand(stripTerminalFlag(params.argv, params.consumedValue));
139
+ const result = await openSurface({
140
+ backend: choice.backend,
141
+ layout: 'tab',
142
+ cwd: params.cwd,
143
+ command,
144
+ });
145
+ return { ok: result.ok, choice, description: describeBackendChoice(choice), error: result.error };
146
+ }
@@ -8,7 +8,7 @@
8
8
  * is attended and live. See docs/terminal-engine.md.
9
9
  */
10
10
  /** An interactive terminal backend the engine can drive. */
11
- export type Backend = 'iterm' | 'ghostty' | 'tmux' | 'vscodium-agent';
11
+ export type Backend = 'iterm' | 'ghostty' | 'tmux' | 'vscodium-agent' | 'terminal';
12
12
  /** Which way a split pane grows. `right` = side-by-side; `down` = stacked. */
13
13
  export type SplitDirection = 'right' | 'down';
14
14
  /** Where a surface lands: a new tab, or a split of the current pane. */
@@ -62,7 +62,7 @@ export interface BudgetConfig {
62
62
  require_confirm_over?: number;
63
63
  }
64
64
  /** Preview features that users can opt into via `agents beta`. */
65
- export type BetaFeatureName = 'drive' | 'factory' | 'session-sync';
65
+ export type BetaFeatureName = 'drive' | 'factory' | 'session-sync' | 'projects';
66
66
  /** Subset of chalk color names used for agent-specific terminal output. */
67
67
  export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
68
68
  /** Static configuration for a single agent -- paths, capabilities, and format conventions. */
@@ -873,10 +873,11 @@ export interface Meta {
873
873
  };
874
874
  /**
875
875
  * Owner/channel notification config for `agents send` / `agents notify`.
876
- * `owner` is the default recipient for `agents notify` (channel + target).
877
- * `transports` maps a user-facing channel name to the provider that actually
878
- * delivers it — explicit, one provider per channel, no fallback. Omitted keys
879
- * default to name-identity (channel `slack` -> provider `slack`).
876
+ * `owner` is the address expanded by `--to owner` and by `agents notify`
877
+ * (channel + target). `transports` maps a user-facing channel name to the
878
+ * provider that actually delivers it — explicit, one provider per channel,
879
+ * no fallback. Omitted keys default to name-identity (channel `slack` ->
880
+ * provider `slack`).
880
881
  */
881
882
  notify?: {
882
883
  owner?: {
@@ -464,6 +464,12 @@ export interface SyncResult {
464
464
  subagents: string[];
465
465
  plugins: string[];
466
466
  workflows: string[];
467
+ /**
468
+ * Project files the sync left alone because the workspace already has them
469
+ * (repo-relative). Reported once, grouped, by the command that rendered the
470
+ * sync — never one line per file from down here.
471
+ */
472
+ projectSkipped: string[];
467
473
  }
468
474
  /**
469
475
  * Enumerate the DotAgent repo names that resources can be scoped to:
@@ -2420,7 +2420,7 @@ export function mergeRepoScopedSelections(repos, cwd = process.cwd()) {
2420
2420
  */
2421
2421
  export function syncResourcesToVersion(agent, version, selection, options = {}) {
2422
2422
  if (isAgentHardDeprecated(agent)) {
2423
- return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
2423
+ return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [], projectSkipped: [] };
2424
2424
  }
2425
2425
  const agentConfig = AGENTS[agent];
2426
2426
  const versionHome = getVersionHomePath(agent, version);
@@ -2431,7 +2431,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2431
2431
  // care about the ORIGINAL intent: a caller passing no selection means
2432
2432
  // "full sync; persist the staleness manifest after."
2433
2433
  const userPassedSelection = selection !== undefined;
2434
- const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
2434
+ const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [], projectSkipped: [] };
2435
2435
  const cwd = options.cwd || process.cwd();
2436
2436
  const projectAgentsDir = options.projectDir || getProjectAgentsDir(cwd);
2437
2437
  const userAgentsDir = getUserAgentsDir();
@@ -2499,7 +2499,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2499
2499
  }
2500
2500
  }
2501
2501
  if (projectAgentsDir) {
2502
- syncProjectResourcesToAgent(agent, version, projectAgentsDir);
2502
+ result.projectSkipped = syncProjectResourcesToAgent(agent, version, projectAgentsDir).skipped;
2503
2503
  }
2504
2504
  // Fast guard: skip the entire sync when the caller requested a full sync and
2505
2505
  // nothing has changed since the last full sync. Pattern-derived selections
@@ -2508,7 +2508,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2508
2508
  if (!userPassedSelection && !options.force) {
2509
2509
  const manifest = loadManifest(agent, version);
2510
2510
  if (manifest && !isStale(manifest, agent, version, cwd)) {
2511
- return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
2511
+ // Nothing synced, but the project sync above already ran carry its
2512
+ // skipped files out so the caller can still report them.
2513
+ return { ...result };
2512
2514
  }
2513
2515
  }
2514
2516
  // Helper: remove a path (symlink or real) if it exists
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.91",
3
+ "version": "1.20.93",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -99,6 +99,7 @@
99
99
  "marked-terminal": "7.3.0",
100
100
  "ora": "9.4.1",
101
101
  "proper-lockfile": "4.1.2",
102
+ "shlex": "3.0.0",
102
103
  "simple-git": "3.36.0",
103
104
  "smol-toml": "1.7.0",
104
105
  "ws": "^8.21.0",