lua-cli 2.5.2 → 2.5.3

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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Docs Command
3
+ * Opens the Lua documentation website in the user's default browser
4
+ */
5
+ import open from 'open';
6
+ import { withErrorHandling, writeSuccess, writeProgress } from '../utils/cli.js';
7
+ /**
8
+ * Docs command - opens the Lua documentation in the browser.
9
+ *
10
+ * This command opens https://docs.heylua.ai in the user's default browser,
11
+ * providing quick access to:
12
+ * - Platform overview and concepts
13
+ * - CLI command reference
14
+ * - API documentation
15
+ * - Tool examples and guides
16
+ * - Integration tutorials
17
+ * - Best practices
18
+ *
19
+ * @returns Promise that resolves when browser is launched
20
+ */
21
+ export async function docsCommand() {
22
+ return withErrorHandling(async () => {
23
+ writeProgress('Opening Lua Documentation...');
24
+ // Open the documentation website
25
+ const docsUrl = 'https://docs.heylua.ai';
26
+ await open(docsUrl);
27
+ writeSuccess('Lua Documentation opened in your browser');
28
+ console.log(`\n Documentation: ${docsUrl}\n`);
29
+ }, 'docs');
30
+ }
@@ -13,3 +13,7 @@ export { envCommand } from "./env.js";
13
13
  export { personaCommand } from "./persona.js";
14
14
  export { productionCommand } from "./production.js";
15
15
  export { resourcesCommand } from "./resources.js";
16
+ export { adminCommand } from "./admin.js";
17
+ export { docsCommand } from "./docs.js";
18
+ export { channelsCommand } from "./channels.js";
19
+ export { logsCommand } from "./logs.js";
@@ -13,3 +13,7 @@ export { envCommand } from "./env.js";
13
13
  export { personaCommand } from "./persona.js";
14
14
  export { productionCommand } from "./production.js";
15
15
  export { resourcesCommand } from "./resources.js";
16
+ export { adminCommand } from "./admin.js";
17
+ export { docsCommand } from "./docs.js";
18
+ export { channelsCommand } from "./channels.js";
19
+ export { logsCommand } from "./logs.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Logs Command
3
+ * View and navigate agent/skill logs with pagination
4
+ */
5
+ /**
6
+ * Main logs command - displays agent/skill logs with navigation
7
+ *
8
+ * Features:
9
+ * - View all agent logs or filter by specific skill
10
+ * - Interactive pagination
11
+ * - Color-coded log types
12
+ * - Real-time navigation
13
+ *
14
+ * @returns Promise that resolves when the command completes
15
+ * @throws Error if API key or configuration is not found
16
+ */
17
+ export declare function logsCommand(): Promise<void>;
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Logs Command
3
+ * View and navigate agent/skill logs with pagination
4
+ */
5
+ import inquirer from "inquirer";
6
+ import chalk from "chalk";
7
+ import { loadApiKey } from "../services/auth.js";
8
+ import { readSkillConfig } from "../utils/files.js";
9
+ import { withErrorHandling, writeSuccess, writeInfo, writeError, clearPromptLines } from "../utils/cli.js";
10
+ import LogsApi from '../api/logs.api.service.js';
11
+ import { BASE_URLS } from '../config/constants.js';
12
+ /**
13
+ * Main logs command - displays agent/skill logs with navigation
14
+ *
15
+ * Features:
16
+ * - View all agent logs or filter by specific skill
17
+ * - Interactive pagination
18
+ * - Color-coded log types
19
+ * - Real-time navigation
20
+ *
21
+ * @returns Promise that resolves when the command completes
22
+ * @throws Error if API key or configuration is not found
23
+ */
24
+ export async function logsCommand() {
25
+ return withErrorHandling(async () => {
26
+ // Load API key
27
+ const apiKey = await loadApiKey();
28
+ if (!apiKey) {
29
+ console.error("❌ No API key found. Run `lua auth configure` first.");
30
+ process.exit(1);
31
+ }
32
+ // Load agent ID from configuration
33
+ const config = readSkillConfig();
34
+ const agentId = config?.agent?.agentId;
35
+ if (!agentId) {
36
+ throw new Error("No agent ID found in lua.skill.yaml. Please ensure you are in a Lua skill directory, or run 'lua init' to create a new skill.");
37
+ }
38
+ writeInfo(`📊 Viewing logs for agent: ${agentId}\n`);
39
+ const logsApi = new LogsApi(BASE_URLS.API, apiKey);
40
+ // Ask if user wants all logs or specific skill
41
+ const { viewType } = await inquirer.prompt([
42
+ {
43
+ type: "list",
44
+ name: "viewType",
45
+ message: "What logs do you want to view?",
46
+ choices: [
47
+ { name: "📋 All agent logs", value: "all" },
48
+ { name: "🎯 Specific skill logs", value: "skill" }
49
+ ]
50
+ }
51
+ ]);
52
+ clearPromptLines(2);
53
+ if (viewType === "all") {
54
+ await viewAgentLogs(logsApi, agentId);
55
+ }
56
+ else {
57
+ await viewSkillLogs(logsApi, agentId, config);
58
+ }
59
+ }, "logs viewing");
60
+ }
61
+ /**
62
+ * View all logs for an agent with pagination
63
+ */
64
+ async function viewAgentLogs(logsApi, agentId) {
65
+ let currentPage = 1;
66
+ const limit = 20;
67
+ let keepViewing = true;
68
+ while (keepViewing) {
69
+ const response = await logsApi.getAgentLogs(agentId, limit, currentPage);
70
+ if (!response.success) {
71
+ writeError(`❌ Error: ${response.error?.message || 'Unknown error'}`);
72
+ process.exit(1);
73
+ }
74
+ const data = response.data;
75
+ displayLogs(data.logs, data.pagination, "All Agent Logs");
76
+ // Navigation menu
77
+ const { action } = await inquirer.prompt([
78
+ {
79
+ type: "list",
80
+ name: "action",
81
+ message: "Navigation:",
82
+ choices: getNavigationChoices(data.pagination)
83
+ }
84
+ ]);
85
+ clearPromptLines(2);
86
+ keepViewing = await handleNavigation(action, data.pagination, (page) => {
87
+ currentPage = page;
88
+ });
89
+ }
90
+ }
91
+ /**
92
+ * View logs for a specific skill with pagination
93
+ */
94
+ async function viewSkillLogs(logsApi, agentId, config) {
95
+ // Get skills from config
96
+ const skills = config?.skills || [];
97
+ if (skills.length === 0) {
98
+ writeError("❌ No skills found in lua.skill.yaml");
99
+ writeInfo("💡 Run 'lua push' to deploy skills first.\n");
100
+ return;
101
+ }
102
+ // Let user select a skill
103
+ const skillChoices = skills.map((skill) => ({
104
+ name: `${skill.name} (${skill.skillId || 'No ID'})`,
105
+ value: skill.skillId
106
+ }));
107
+ skillChoices.push({
108
+ name: '← Back to main menu',
109
+ value: null
110
+ });
111
+ const { skillId } = await inquirer.prompt([
112
+ {
113
+ type: "list",
114
+ name: "skillId",
115
+ message: "Select a skill:",
116
+ choices: skillChoices
117
+ }
118
+ ]);
119
+ if (!skillId) {
120
+ return;
121
+ }
122
+ clearPromptLines(2);
123
+ let currentPage = 1;
124
+ const limit = 20;
125
+ let keepViewing = true;
126
+ const selectedSkill = skills.find((s) => s.skillId === skillId);
127
+ const skillName = selectedSkill?.name || skillId;
128
+ while (keepViewing) {
129
+ const response = await logsApi.getSkillLogs(agentId, skillId, limit, currentPage);
130
+ if (!response.success) {
131
+ writeError(`❌ Error: ${response.error?.message || 'Unknown error'}`);
132
+ process.exit(1);
133
+ }
134
+ const data = response.data;
135
+ displayLogs(data.logs, data.pagination, `Logs for ${skillName}`);
136
+ // Navigation menu
137
+ const { action } = await inquirer.prompt([
138
+ {
139
+ type: "list",
140
+ name: "action",
141
+ message: "Navigation:",
142
+ choices: getNavigationChoices(data.pagination)
143
+ }
144
+ ]);
145
+ clearPromptLines(2);
146
+ keepViewing = await handleNavigation(action, data.pagination, (page) => {
147
+ currentPage = page;
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Display logs with color coding
153
+ */
154
+ function displayLogs(logs, pagination, title) {
155
+ console.clear();
156
+ writeSuccess(`\n${title}\n`);
157
+ writeInfo(`${'─'.repeat(80)}`);
158
+ writeInfo(`Page ${pagination.currentPage} of ${pagination.totalPages} (${pagination.totalCount} total logs)\n`);
159
+ if (logs.length === 0) {
160
+ writeInfo("No logs found.");
161
+ return;
162
+ }
163
+ logs.forEach((log, index) => {
164
+ const timestamp = new Date(log.timestamp).toLocaleString();
165
+ const icon = getLogIcon(log.subType);
166
+ const color = getLogColor(log.subType);
167
+ console.log(color(`${icon} [${timestamp}] ${log.subType.toUpperCase()}`));
168
+ if (log.metadata.skillName) {
169
+ console.log(chalk.gray(` Skill: ${log.metadata.skillName}`));
170
+ }
171
+ if (log.metadata.toolId) {
172
+ console.log(chalk.gray(` Tool: ${log.metadata.toolId}`));
173
+ }
174
+ if (log.duration !== undefined) {
175
+ console.log(chalk.gray(` Duration: ${log.duration}ms`));
176
+ }
177
+ // Display message (truncate if too long)
178
+ const message = log.message.length > 200
179
+ ? log.message.substring(0, 200) + '...'
180
+ : log.message;
181
+ console.log(` ${message}`);
182
+ if (index < logs.length - 1) {
183
+ console.log(chalk.gray(` ${'-'.repeat(78)}`));
184
+ }
185
+ });
186
+ writeInfo(`\n${'─'.repeat(80)}\n`);
187
+ }
188
+ /**
189
+ * Get navigation choices based on pagination state
190
+ */
191
+ function getNavigationChoices(pagination) {
192
+ const choices = [];
193
+ if (pagination.hasPrevPage) {
194
+ choices.push({ name: '← Previous Page', value: 'prev' });
195
+ }
196
+ if (pagination.hasNextPage) {
197
+ choices.push({ name: 'Next Page →', value: 'next' });
198
+ }
199
+ if (pagination.totalPages > 1) {
200
+ choices.push({ name: '🔢 Go to specific page', value: 'goto' });
201
+ }
202
+ choices.push({ name: '🔄 Refresh', value: 'refresh' });
203
+ choices.push({ name: '❌ Exit', value: 'exit' });
204
+ return choices;
205
+ }
206
+ /**
207
+ * Handle navigation actions
208
+ */
209
+ async function handleNavigation(action, pagination, setPage) {
210
+ switch (action) {
211
+ case 'prev':
212
+ if (pagination.hasPrevPage) {
213
+ setPage(pagination.prevPage);
214
+ }
215
+ return true;
216
+ case 'next':
217
+ if (pagination.hasNextPage) {
218
+ setPage(pagination.nextPage);
219
+ }
220
+ return true;
221
+ case 'goto':
222
+ const { page } = await inquirer.prompt({
223
+ type: "number",
224
+ name: "page",
225
+ message: `Enter page number (1-${pagination.totalPages}):`,
226
+ validate: (input) => {
227
+ if (input === undefined || input < 1 || input > pagination.totalPages) {
228
+ return `Please enter a number between 1 and ${pagination.totalPages}`;
229
+ }
230
+ return true;
231
+ }
232
+ });
233
+ setPage(page);
234
+ return true;
235
+ case 'refresh':
236
+ return true;
237
+ case 'exit':
238
+ writeInfo("👋 Exiting logs viewer\n");
239
+ return false;
240
+ default:
241
+ return false;
242
+ }
243
+ }
244
+ /**
245
+ * Get icon for log type
246
+ */
247
+ function getLogIcon(subType) {
248
+ const icons = {
249
+ 'error': '❌',
250
+ 'warn': '⚠️',
251
+ 'debug': '🔍',
252
+ 'info': 'ℹ️',
253
+ 'start': '▶️',
254
+ 'complete': '✅'
255
+ };
256
+ return icons[subType] || '📝';
257
+ }
258
+ /**
259
+ * Get color function for log type
260
+ */
261
+ function getLogColor(subType) {
262
+ const colors = {
263
+ 'error': chalk.red,
264
+ 'warn': chalk.yellow,
265
+ 'debug': chalk.blue,
266
+ 'info': chalk.cyan,
267
+ 'start': chalk.green,
268
+ 'complete': chalk.green
269
+ };
270
+ return colors[subType] || chalk.white;
271
+ }
@@ -32,11 +32,21 @@ export class HttpClient {
32
32
  if (response.status === 401) {
33
33
  throw new AuthenticationError("Invalid API key");
34
34
  }
35
+ // Try to parse error body as JSON
36
+ let errorData;
37
+ try {
38
+ errorData = await response.json();
39
+ }
40
+ catch (jsonError) {
41
+ errorData = {};
42
+ }
35
43
  return {
36
44
  success: false,
37
45
  error: {
38
- message: `HTTP ${response.status}: ${response.statusText}`,
46
+ message: errorData.message || `HTTP ${response.status}: ${response.statusText}`,
39
47
  statusCode: response.status,
48
+ error: errorData.error,
49
+ ...errorData
40
50
  },
41
51
  };
42
52
  }
@@ -1,8 +1,7 @@
1
- import { UserData } from "../interfaces/admin.js";
2
1
  import { UserDataAPI } from "../types/index.js";
3
2
  /**
4
3
  * User data instance class providing a fluent API for managing user data
5
- * Automatically sanitizes sensitive fields and provides methods for updating and clearing data
4
+ * Provides methods for updating and clearing data
6
5
  * Supports direct property access (e.g., user.name) instead of user.data.name
7
6
  */
8
7
  export default class UserDataInstance {
@@ -12,17 +11,10 @@ export default class UserDataInstance {
12
11
  /**
13
12
  * Creates a new UserDataInstance with proxy support for direct property access
14
13
  * @param api - The UserDataAPI instance for making API calls
15
- * @param data - The user data from the API (will be sanitized automatically)
14
+ * @param data - The user data from the API
16
15
  * @returns Proxied instance that allows direct access to data properties
17
16
  */
18
- constructor(api: UserDataAPI, data: UserData);
19
- /**
20
- * Removes sensitive fields from the data before storing
21
- * @param data - The raw user data to sanitize
22
- * @returns Sanitized data with sensitive fields removed
23
- * @private
24
- */
25
- private sanitizeData;
17
+ constructor(api: UserDataAPI, data: any);
26
18
  /**
27
19
  * Custom toJSON method to control what gets serialized when logging
28
20
  * @returns Serialized user data
@@ -31,7 +23,7 @@ export default class UserDataInstance {
31
23
  /**
32
24
  * Updates the user's data
33
25
  * @param data - The data fields to update or add to user data
34
- * @returns Promise resolving to the updated sanitized user data
26
+ * @returns Promise resolving to the updated user data
35
27
  * @throws Error if the update fails
36
28
  */
37
29
  update(data: Record<string, any>): Promise<any>;
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * User data instance class providing a fluent API for managing user data
3
- * Automatically sanitizes sensitive fields and provides methods for updating and clearing data
3
+ * Provides methods for updating and clearing data
4
4
  * Supports direct property access (e.g., user.name) instead of user.data.name
5
5
  */
6
6
  export default class UserDataInstance {
7
7
  /**
8
8
  * Creates a new UserDataInstance with proxy support for direct property access
9
9
  * @param api - The UserDataAPI instance for making API calls
10
- * @param data - The user data from the API (will be sanitized automatically)
10
+ * @param data - The user data from the API
11
11
  * @returns Proxied instance that allows direct access to data properties
12
12
  */
13
13
  constructor(api, data) {
14
- this.data = this.sanitizeData(data);
14
+ this.data = data;
15
15
  // Make userAPI non-enumerable so it doesn't show up in console.log
16
16
  Object.defineProperty(this, 'userAPI', {
17
17
  value: api,
@@ -74,25 +74,6 @@ export default class UserDataInstance {
74
74
  }
75
75
  });
76
76
  }
77
- /**
78
- * Removes sensitive fields from the data before storing
79
- * @param data - The raw user data to sanitize
80
- * @returns Sanitized data with sensitive fields removed
81
- * @private
82
- */
83
- sanitizeData(data) {
84
- if (!data || typeof data !== 'object') {
85
- return data;
86
- }
87
- // Create a copy of the data to avoid mutating the original
88
- const sanitized = { ...data };
89
- // Remove sensitive fields
90
- const sensitiveFields = ['agentId', 'userId'];
91
- sensitiveFields.forEach(field => {
92
- delete sanitized[field];
93
- });
94
- return sanitized;
95
- }
96
77
  /**
97
78
  * Custom toJSON method to control what gets serialized when logging
98
79
  * @returns Serialized user data
@@ -110,13 +91,13 @@ export default class UserDataInstance {
110
91
  /**
111
92
  * Updates the user's data
112
93
  * @param data - The data fields to update or add to user data
113
- * @returns Promise resolving to the updated sanitized user data
94
+ * @returns Promise resolving to the updated user data
114
95
  * @throws Error if the update fails
115
96
  */
116
97
  async update(data) {
117
98
  try {
118
99
  const response = await this.userAPI.update(data);
119
- this.data = this.sanitizeData(response.data);
100
+ this.data = response.data;
120
101
  return this.data;
121
102
  }
122
103
  catch (error) {
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ Categories:
30
30
  ☁️ Deployment Push and deploy to production
31
31
  💬 Testing Chat and test your agent
32
32
  ⚙️ Configuration Manage environment and persona
33
+ 📖 Utilities Quick access to admin and docs
33
34
 
34
35
  Examples:
35
36
  $ lua auth configure 🔑 Set up your API key
@@ -41,6 +42,8 @@ Examples:
41
42
  $ lua chat 💬 Start interactive chat
42
43
  $ lua env ⚙️ Manage environment variables
43
44
  $ lua persona 🤖 Manage agent persona
45
+ $ lua admin 🔧 Open admin dashboard
46
+ $ lua docs 📖 Open documentation
44
47
 
45
48
  🌙 Documentation: https://docs.heylua.ai
46
49
  🌙 Support: https://heylua.ai/support
@@ -89,3 +89,221 @@ export interface AgentDetailsResponse {
89
89
  };
90
90
  skills: any[];
91
91
  }
92
+ /**
93
+ * Agent channel configuration.
94
+ * Represents a communication channel connected to an agent.
95
+ */
96
+ export interface AgentChannel {
97
+ type: string;
98
+ identifier: string;
99
+ createdAt: number;
100
+ whatsapp?: string;
101
+ facebook?: string;
102
+ instagram?: string;
103
+ data: Record<string, any>;
104
+ }
105
+ /**
106
+ * Channel link configuration for connecting new channels.
107
+ */
108
+ export interface ChannelLink {
109
+ type: string;
110
+ method: string;
111
+ link: string;
112
+ data: string[];
113
+ }
114
+ /**
115
+ * Response from get agent channels API.
116
+ */
117
+ export interface AgentChannelsResponse {
118
+ channels: AgentChannel[];
119
+ links: ChannelLink[];
120
+ status: string;
121
+ message: string;
122
+ }
123
+ /**
124
+ * Request payload for creating a WhatsApp channel.
125
+ */
126
+ export interface CreateWhatsAppChannelRequest {
127
+ type: "whatsapp";
128
+ whatsapp: {
129
+ phoneNumberId: string;
130
+ wabaId: string;
131
+ accessToken: string;
132
+ };
133
+ isManual: boolean;
134
+ }
135
+ /**
136
+ * Response from creating a WhatsApp channel.
137
+ */
138
+ export interface CreateWhatsAppChannelResponse {
139
+ type: string;
140
+ identifier: string;
141
+ agentId: string;
142
+ userId: string;
143
+ whatsapp: {
144
+ number: string;
145
+ global: boolean;
146
+ phoneId: string;
147
+ accessToken: string;
148
+ metadata: {
149
+ id: string;
150
+ display_phone_number: string;
151
+ verified_name: string;
152
+ quality_rating: string;
153
+ status: string;
154
+ code_verification_status: string;
155
+ search_visibility: string;
156
+ platform_type: string;
157
+ };
158
+ };
159
+ webhookUrl: string;
160
+ }
161
+ /**
162
+ * Request payload for creating a Facebook Messenger channel.
163
+ */
164
+ export interface CreateFacebookChannelRequest {
165
+ type: "facebook";
166
+ facebook: {
167
+ accessToken: string;
168
+ pageId: string;
169
+ };
170
+ }
171
+ /**
172
+ * Response from creating a Facebook Messenger channel.
173
+ */
174
+ export interface CreateFacebookChannelResponse {
175
+ type: string;
176
+ identifier: string;
177
+ agentId: string;
178
+ userId: string;
179
+ facebook: {
180
+ pageId: string;
181
+ id: string;
182
+ name: string;
183
+ category: string;
184
+ };
185
+ webhookUrl: string;
186
+ }
187
+ /**
188
+ * Request payload for creating a private Slack channel.
189
+ */
190
+ export interface CreateSlackPrivateChannelRequest {
191
+ type: "slack";
192
+ slack: {
193
+ type: "private";
194
+ accessToken: string;
195
+ };
196
+ }
197
+ /**
198
+ * Request payload for creating a public Slack channel.
199
+ */
200
+ export interface CreateSlackPublicChannelRequest {
201
+ type: "slack";
202
+ slack: {
203
+ type: "public";
204
+ appId: string;
205
+ clientId: string;
206
+ clientSecret: string;
207
+ };
208
+ }
209
+ /**
210
+ * Union type for creating any Slack channel (private or public).
211
+ */
212
+ export type CreateSlackChannelRequest = CreateSlackPrivateChannelRequest | CreateSlackPublicChannelRequest;
213
+ /**
214
+ * Slack app manifest configuration (shared between private and public channels).
215
+ */
216
+ export interface SlackManifest {
217
+ display_information: {
218
+ name: string;
219
+ description: string;
220
+ background_color: string;
221
+ long_description: string;
222
+ };
223
+ features: {
224
+ app_home: {
225
+ home_tab_enabled: boolean;
226
+ messages_tab_enabled: boolean;
227
+ messages_tab_read_only_enabled: boolean;
228
+ };
229
+ bot_user: {
230
+ display_name: string;
231
+ always_online: boolean;
232
+ };
233
+ };
234
+ oauth_config: {
235
+ redirect_urls: string[];
236
+ scopes: {
237
+ bot: string[];
238
+ };
239
+ };
240
+ settings: {
241
+ event_subscriptions: {
242
+ request_url: string;
243
+ bot_events: string[];
244
+ };
245
+ interactivity: {
246
+ is_enabled: boolean;
247
+ request_url: string;
248
+ };
249
+ org_deploy_enabled: boolean;
250
+ socket_mode_enabled: boolean;
251
+ token_rotation_enabled: boolean;
252
+ };
253
+ }
254
+ /**
255
+ * Response from creating a private Slack channel.
256
+ */
257
+ export interface CreateSlackPrivateChannelResponse {
258
+ type: string;
259
+ agentId: string;
260
+ userId: string;
261
+ slack: {
262
+ type: string;
263
+ appId: string;
264
+ teamId: string;
265
+ accessToken: string;
266
+ replyTo: string[];
267
+ botId: string;
268
+ url: string;
269
+ botUserId: string;
270
+ botName: string;
271
+ botIcon: string;
272
+ manifest: SlackManifest;
273
+ };
274
+ webhookUrl: string;
275
+ }
276
+ /**
277
+ * Response from creating a public Slack channel.
278
+ */
279
+ export interface CreateSlackPublicChannelResponse {
280
+ appId: string;
281
+ clientId: string;
282
+ clientSecret: string;
283
+ agentId: string;
284
+ userId: string;
285
+ redirectUri: string;
286
+ manifest: SlackManifest;
287
+ webhookUrl: string;
288
+ }
289
+ /**
290
+ * Union type for Slack channel creation responses (private or public).
291
+ */
292
+ export type CreateSlackChannelResponse = CreateSlackPrivateChannelResponse | CreateSlackPublicChannelResponse;
293
+ /**
294
+ * Request payload for creating an Email channel.
295
+ */
296
+ export interface CreateEmailChannelRequest {
297
+ type: "email";
298
+ email: {
299
+ name: string;
300
+ email: string;
301
+ };
302
+ }
303
+ /**
304
+ * Response from creating an Email channel.
305
+ */
306
+ export interface CreateEmailChannelResponse {
307
+ forwardTo: string;
308
+ status: string;
309
+ }