@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64

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 (185) hide show
  1. package/README.md +2 -2
  2. package/knip.json +1 -1
  3. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
  4. package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
  5. package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
  6. package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
  7. package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
  8. package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
  9. package/node_modules/@mycadet/assistant-api/index.ts +11 -11
  10. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
  19. package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
  20. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  21. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  22. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  23. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  24. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  25. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  26. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  27. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  28. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  29. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
  30. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
  31. package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  32. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  33. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  34. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  35. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  36. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  37. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  38. package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
  39. package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
  40. package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
  41. package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
  42. package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
  43. package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
  44. package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
  45. package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
  46. package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
  47. package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
  48. package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
  49. package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
  50. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  51. package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
  52. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  53. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  54. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  55. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  56. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  57. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  58. package/package.json +6 -4
  59. package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
  60. package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
  61. package/src/__tests__/assistant-client-refresh.test.ts +2 -2
  62. package/src/__tests__/backup.test.ts +86 -91
  63. package/src/__tests__/client-token.test.ts +1 -1
  64. package/src/__tests__/client-tui-refresh.test.ts +1 -1
  65. package/src/__tests__/client-web-config.test.ts +1 -2
  66. package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
  67. package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
  68. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  69. package/src/__tests__/connect-import.test.ts +55 -39
  70. package/src/__tests__/debug.test.ts +38 -38
  71. package/src/__tests__/ensure-binary.test.ts +182 -0
  72. package/src/__tests__/guardian-token.test.ts +149 -56
  73. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  74. package/src/__tests__/helpers/guardian-token.ts +22 -0
  75. package/src/__tests__/helpers/http.ts +10 -0
  76. package/src/__tests__/nginx-ingress.test.ts +15 -20
  77. package/src/__tests__/ngrok-spawn.test.ts +49 -0
  78. package/src/__tests__/orphan-detection.test.ts +1 -1
  79. package/src/__tests__/pair.test.ts +149 -85
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -26
  81. package/src/__tests__/ps-table.test.ts +79 -0
  82. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  83. package/src/__tests__/retire.test.ts +58 -199
  84. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  85. package/src/__tests__/sleep.test.ts +19 -19
  86. package/src/__tests__/statefulset.test.ts +8 -8
  87. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
  88. package/src/__tests__/teleport-backup.test.ts +3 -3
  89. package/src/__tests__/teleport.test.ts +180 -211
  90. package/src/__tests__/tunnel.test.ts +490 -270
  91. package/src/__tests__/uninstall.test.ts +150 -9
  92. package/src/__tests__/upgrade-local.test.ts +3 -3
  93. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  94. package/src/__tests__/wake.test.ts +260 -43
  95. package/src/__tests__/workos-pkce.test.ts +2 -2
  96. package/src/commands/backup.ts +24 -24
  97. package/src/commands/client.ts +76 -78
  98. package/src/commands/completions.test.ts +104 -0
  99. package/src/commands/completions.ts +87 -0
  100. package/src/commands/debug.ts +20 -20
  101. package/src/commands/exec.ts +12 -12
  102. package/src/commands/flags.ts +2 -2
  103. package/src/commands/hatch.ts +27 -24
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/pair.ts +140 -37
  107. package/src/commands/ps.ts +232 -86
  108. package/src/commands/restart.ts +30 -0
  109. package/src/commands/restore.ts +140 -22
  110. package/src/commands/retire.ts +48 -150
  111. package/src/commands/rollback.ts +20 -26
  112. package/src/commands/setup.ts +2 -2
  113. package/src/commands/sleep.ts +2 -2
  114. package/src/commands/teleport.ts +110 -129
  115. package/src/commands/terminal.ts +158 -18
  116. package/src/commands/tunnel.ts +156 -175
  117. package/src/commands/uninstall.ts +137 -54
  118. package/src/commands/unpair.ts +3 -3
  119. package/src/commands/upgrade.ts +30 -54
  120. package/src/commands/use.ts +1 -1
  121. package/src/commands/wake.ts +166 -50
  122. package/src/index.ts +18 -9
  123. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  124. package/src/lib/__tests__/docker.test.ts +168 -20
  125. package/src/lib/__tests__/job-polling.test.ts +2 -2
  126. package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
  127. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  128. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  129. package/src/lib/assistant-api-key-resolution.ts +5 -5
  130. package/src/lib/assistant-client.ts +8 -8
  131. package/src/lib/assistant-config.ts +58 -12
  132. package/src/lib/{platform-client.ts → cloud-client.ts} +105 -99
  133. package/src/lib/cloud-login.ts +23 -24
  134. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  135. package/src/lib/cloud-values.ts +3 -0
  136. package/src/lib/color.ts +4 -0
  137. package/src/lib/config-utils.ts +1 -1
  138. package/src/lib/detached-process.ts +1 -1
  139. package/src/lib/docker.ts +300 -111
  140. package/src/lib/ensure-binary.ts +171 -0
  141. package/src/lib/environments/__tests__/paths.test.ts +29 -3
  142. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  143. package/src/lib/environments/paths.ts +16 -8
  144. package/src/lib/environments/resolve.ts +10 -12
  145. package/src/lib/flag-args.test.ts +1 -1
  146. package/src/lib/flag-args.ts +1 -1
  147. package/src/lib/guardian-token.ts +37 -5
  148. package/src/lib/hatch-local.ts +32 -29
  149. package/src/lib/hatch-next-steps.ts +5 -9
  150. package/src/lib/health-check.ts +6 -6
  151. package/src/lib/ingress-config.ts +8 -35
  152. package/src/lib/job-polling.ts +2 -2
  153. package/src/lib/live-voice/connection.ts +12 -12
  154. package/src/lib/live-voice/tunnel.test.ts +11 -11
  155. package/src/lib/live-voice/tunnel.ts +15 -15
  156. package/src/lib/local-runtime-client.ts +20 -20
  157. package/src/lib/local.ts +46 -34
  158. package/src/lib/nginx-ingress.ts +40 -15
  159. package/src/lib/pair.ts +1 -1
  160. package/src/lib/process.ts +1 -1
  161. package/src/lib/provider-secrets.ts +3 -3
  162. package/src/lib/retire-apple-container.ts +1 -1
  163. package/src/lib/runtime-url.ts +8 -8
  164. package/src/lib/statefulset.ts +2 -2
  165. package/src/lib/step-runner.ts +6 -1
  166. package/src/lib/sync-cloud-assistants.ts +19 -19
  167. package/src/lib/teleport-backup.ts +4 -4
  168. package/src/lib/terminal-client.ts +17 -17
  169. package/src/lib/terminal-session.ts +24 -24
  170. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  171. package/src/lib/tunnels/cadet.ts +29 -0
  172. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  173. package/src/lib/tunnels/gateway-notify.ts +61 -0
  174. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  175. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  176. package/src/lib/upgrade-lifecycle.ts +8 -21
  177. package/src/lib/upgrade-preflight.ts +2 -2
  178. package/src/lib/workos-pkce.ts +5 -5
  179. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  180. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  181. package/src/commands/recover.ts +0 -131
  182. package/src/commands/roadmap.ts +0 -459
  183. package/src/commands/ssh.ts +0 -144
  184. package/src/commands/workflows.ts +0 -305
  185. package/src/lib/managed-cloud.ts +0 -3
@@ -1,9 +1,10 @@
1
1
  /**
2
- * `cadet terminal` — Interactive shell into a managed assistant container.
2
+ * `cadet terminal` — Interactive shell into an assistant.
3
3
  *
4
- * Bridges the local tty to a platform terminal session (K8s exec) so the
5
- * user can interact with their assistant's sandbox from iTerm2 or any
6
- * local terminal emulator.
4
+ * Dispatches by cloud type: a cloud terminal session (K8s exec) for
5
+ * cloud assistants, `docker exec` for docker assistants, `gcloud compute
6
+ * ssh` for GCP, raw ssh for custom hosts, and the container management
7
+ * socket for apple-container assistants.
7
8
  *
8
9
  * Subcommands:
9
10
  * cadet terminal — Interactive shell
@@ -11,6 +12,13 @@
11
12
  * cadet terminal list — List tmux sessions
12
13
  */
13
14
 
15
+ import { isCloudHome } from "../lib/cloud-values.js";
16
+ import { spawn } from "child_process";
17
+
18
+ import { extractHostFromUrl, resolveAssistant } from "../lib/assistant-config";
19
+ import { dockerResourceNames } from "../lib/docker";
20
+ import { getCloudUrl, readCloudToken } from "../lib/cloud-client";
21
+ import { sshAppleContainer } from "../lib/ssh-apple-container";
14
22
  import {
15
23
  closeTerminalSession,
16
24
  createTerminalSession,
@@ -22,6 +30,18 @@ import {
22
30
  resolveManagedAssistant,
23
31
  } from "../lib/terminal-session.js";
24
32
  import type { ResolvedManagedAssistant } from "../lib/terminal-session.js";
33
+ import type { ChildProcess } from "child_process";
34
+
35
+ const SSH_OPTS = [
36
+ "-o",
37
+ "StrictHostKeyChecking=no",
38
+ "-o",
39
+ "UserKnownHostsFile=/dev/null",
40
+ "-o",
41
+ "ConnectTimeout=10",
42
+ "-o",
43
+ "LogLevel=ERROR",
44
+ ];
25
45
 
26
46
  // ---------------------------------------------------------------------------
27
47
  // Helpers
@@ -30,9 +50,15 @@ import type { ResolvedManagedAssistant } from "../lib/terminal-session.js";
30
50
  function printHelp(): void {
31
51
  console.log("Usage: cadet terminal [subcommand] [options]");
32
52
  console.log("");
53
+ console.log("Open an interactive terminal session into an assistant.");
54
+ console.log("");
33
55
  console.log(
34
- "Open an interactive terminal session into a managed assistant container.",
56
+ "Managed (cloud-hosted) assistants get a cloud terminal session; docker",
35
57
  );
58
+ console.log(
59
+ "assistants shell in via docker exec, GCP assistants via gcloud, custom",
60
+ );
61
+ console.log("hosts via ssh, and apple-container assistants via their management socket.");
36
62
  console.log("");
37
63
  console.log("Subcommands:");
38
64
  console.log(" (none) Interactive shell");
@@ -45,7 +71,7 @@ function printHelp(): void {
45
71
  console.log("");
46
72
  console.log("Options:");
47
73
  console.log(
48
- " <name> Name of the assistant (defaults to active)",
74
+ " <name> Name of the assistant, defaults to active",
49
75
  );
50
76
  console.log(
51
77
  " --assistant <name> Explicit assistant name (alternative to positional)",
@@ -58,6 +84,19 @@ function printHelp(): void {
58
84
  console.log(" cadet terminal --assistant my-assistant");
59
85
  }
60
86
 
87
+ function waitForExit(child: ChildProcess): Promise<void> {
88
+ return new Promise<void>((resolve, reject) => {
89
+ child.on("close", (code) => {
90
+ if (code === 0) {
91
+ resolve();
92
+ } else {
93
+ reject(new Error(`terminal exited with code ${code}`));
94
+ }
95
+ });
96
+ child.on("error", reject);
97
+ });
98
+ }
99
+
61
100
  // ---------------------------------------------------------------------------
62
101
  // List tmux sessions
63
102
  // ---------------------------------------------------------------------------
@@ -73,7 +112,7 @@ async function listTmuxSessions(
73
112
  assistant.assistantId,
74
113
  cols,
75
114
  rows,
76
- assistant.platformUrl,
115
+ assistant.cloudUrl,
77
116
  );
78
117
 
79
118
  const abortController = new AbortController();
@@ -88,7 +127,7 @@ async function listTmuxSessions(
88
127
  assistant.token,
89
128
  assistant.assistantId,
90
129
  sessionId,
91
- assistant.platformUrl,
130
+ assistant.cloudUrl,
92
131
  abortController.signal,
93
132
  )) {
94
133
  const text = Buffer.from(event.data, "base64").toString("utf-8");
@@ -108,7 +147,7 @@ async function listTmuxSessions(
108
147
  assistant.assistantId,
109
148
  sessionId,
110
149
  'tmux list-sessions 2>/dev/null || echo "No tmux sessions found"; exit\r',
111
- assistant.platformUrl,
150
+ assistant.cloudUrl,
112
151
  );
113
152
  }
114
153
  }
@@ -125,7 +164,7 @@ async function listTmuxSessions(
125
164
  assistant.token,
126
165
  assistant.assistantId,
127
166
  sessionId,
128
- assistant.platformUrl,
167
+ assistant.cloudUrl,
129
168
  ).catch(() => {});
130
169
  }
131
170
 
@@ -200,10 +239,9 @@ export const terminal = Object.assign(
200
239
  }
201
240
  }
202
241
 
203
- const assistant = resolveManagedAssistant(assistantName);
204
-
242
+ // tmux subcommands are managed-cloud features
205
243
  if (subcommand === "list") {
206
- await listTmuxSessions(assistant);
244
+ await listTmuxSessions(resolveManagedAssistant(assistantName));
207
245
  return;
208
246
  }
209
247
 
@@ -217,13 +255,115 @@ export const terminal = Object.assign(
217
255
  }
218
256
  // Shell-escape the session name to handle spaces/metacharacters
219
257
  const escaped = tmuxSessionName.replace(/'/g, "'\\''");
220
- await interactiveSession(assistant, `tmux attach -t '${escaped}'`);
258
+ await interactiveSession(
259
+ resolveManagedAssistant(assistantName),
260
+ `tmux attach -t '${escaped}'`,
261
+ );
262
+ return;
263
+ }
264
+
265
+ // Default: interactive shell, dispatched by the assistant's cloud type.
266
+ const entry = resolveAssistant(assistantName);
267
+
268
+ if (!entry) {
269
+ if (assistantName) {
270
+ console.error(`No assistant instance found with name '${assistantName}'.`);
271
+ } else {
272
+ console.error("No assistant instance found. Run `cadet hatch` first.");
273
+ }
274
+ process.exit(1);
275
+ }
276
+
277
+ const cloud = entry.cloud;
278
+
279
+ if (cloud === "local") {
280
+ console.error(
281
+ "Cannot open a terminal into a local assistant. Local assistants run directly on this machine.\n" +
282
+ `Use 'cadet ps ${entry.assistantId}' to check its processes instead.`,
283
+ );
284
+ process.exit(1);
285
+ }
286
+
287
+ if (cloud === "aws") {
288
+ console.error("Terminals on AWS instances are not yet supported.");
289
+ process.exit(1);
290
+ }
291
+
292
+ // Apple container: connect to the management socket for an interactive shell.
293
+ if (cloud === "apple-container") {
294
+ await sshAppleContainer(entry);
221
295
  return;
222
296
  }
223
297
 
224
- // Default: interactive shell
225
- await interactiveSession(assistant);
298
+ if (cloud === "docker") {
299
+ const res = dockerResourceNames(entry.assistantId);
300
+ console.log(`🔗 Connecting to ${entry.assistantId} via docker exec...\n`);
301
+
302
+ const child = spawn(
303
+ "docker",
304
+ ["exec", "-it", res.assistantContainer, "/bin/sh"],
305
+ { stdio: "inherit", windowsHide: true },
306
+ );
307
+ await waitForExit(child);
308
+ return;
309
+ }
310
+
311
+ if (cloud === "gcp") {
312
+ const project = entry.project;
313
+ const zone = entry.zone;
314
+ if (!project || !zone) {
315
+ console.error("Error: GCP project and zone not found in assistant config.");
316
+ process.exit(1);
317
+ }
318
+
319
+ const sshTarget = entry.sshUser
320
+ ? `${entry.sshUser}@${entry.assistantId}`
321
+ : entry.assistantId;
322
+
323
+ console.log(`🔗 Connecting to ${entry.assistantId} via gcloud...\n`);
324
+
325
+ const child = spawn(
326
+ "gcloud",
327
+ ["compute", "ssh", sshTarget, `--project=${project}`, `--zone=${zone}`],
328
+ { stdio: "inherit", windowsHide: true },
329
+ );
330
+ await waitForExit(child);
331
+ return;
332
+ }
333
+
334
+ if (isCloudHome(cloud)) {
335
+ const token = readCloudToken();
336
+ if (!token) {
337
+ console.error(
338
+ "Not logged in. Run `cadet login` first to authenticate with Cadet Cloud.",
339
+ );
340
+ process.exit(1);
341
+ }
342
+ await interactiveSession({
343
+ assistantId: entry.assistantId,
344
+ token,
345
+ cloudUrl: getCloudUrl(),
346
+ });
347
+ return;
348
+ }
349
+
350
+ if (cloud === "custom") {
351
+ const host = extractHostFromUrl(entry.runtimeUrl);
352
+ const sshUser = entry.sshUser ?? "root";
353
+ const sshTarget = `${sshUser}@${host}`;
354
+
355
+ console.log(`🔗 Connecting to ${entry.assistantId} via ssh...\n`);
356
+
357
+ const child = spawn("ssh", [...SSH_OPTS, sshTarget], {
358
+ stdio: "inherit",
359
+ windowsHide: true,
360
+ });
361
+ await waitForExit(child);
362
+ return;
363
+ }
364
+
365
+ console.error(`Error: Unknown cloud type '${cloud}'.`);
366
+ process.exit(1);
226
367
  },
227
- { description: "Open a terminal into a managed assistant container" },
368
+ { description: "Open a terminal into an assistant" },
228
369
  );
229
-