@lanonasis/cli 1.5.0 → 1.5.1

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,519 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { CLIConfig } from './utils/config.js';
6
+ // MCP Protocol Compliance: Redirect all console output to stderr
7
+ // This prevents stdout pollution which breaks JSON-RPC communication
8
+ const originalConsoleError = console.error;
9
+ // Silent mode for MCP protocol compliance
10
+ const isSilentMode = process.env.LANONASIS_SILENT === 'true' || process.argv.includes('--silent');
11
+ if (isSilentMode) {
12
+ // Completely silence all output except JSON-RPC
13
+ console.log = () => { };
14
+ console.error = () => { };
15
+ console.warn = () => { };
16
+ console.info = () => { };
17
+ console.debug = () => { };
18
+ }
19
+ else {
20
+ // Redirect to stderr for debugging
21
+ console.log = (...args) => originalConsoleError('[MCP-LOG]', ...args);
22
+ console.error = (...args) => originalConsoleError('[MCP-ERROR]', ...args);
23
+ console.warn = (...args) => originalConsoleError('[MCP-WARN]', ...args);
24
+ console.info = (...args) => originalConsoleError('[MCP-INFO]', ...args);
25
+ }
26
+ // Disable colors and verbose output for MCP protocol compliance
27
+ process.env.FORCE_COLOR = '0';
28
+ process.env.DEBUG = '';
29
+ process.env.NODE_ENV = process.env.NODE_ENV || 'production';
30
+ class LanonasisMCPServer {
31
+ server;
32
+ config;
33
+ constructor() {
34
+ this.config = new CLIConfig();
35
+ this.server = new Server({
36
+ name: 'lanonasis-mcp-server',
37
+ version: '1.3.0',
38
+ }, {
39
+ capabilities: {
40
+ tools: {},
41
+ resources: {},
42
+ },
43
+ });
44
+ this.setupHandlers();
45
+ }
46
+ setupHandlers() {
47
+ // List available tools - Comprehensive MCP toolset matching legacy CLI
48
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
49
+ return {
50
+ tools: [
51
+ // Memory Management Tools
52
+ {
53
+ name: 'create_memory',
54
+ description: 'Create a new memory entry with vector embedding',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ title: { type: 'string', description: 'Memory title' },
59
+ content: { type: 'string', description: 'Memory content' },
60
+ memory_type: { type: 'string', description: 'Type of memory', enum: ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'] },
61
+ tags: { type: 'array', items: { type: 'string' }, description: 'Memory tags' },
62
+ topic_id: { type: 'string', description: 'Topic ID for organization' }
63
+ },
64
+ required: ['title', 'content']
65
+ }
66
+ },
67
+ {
68
+ name: 'search_memories',
69
+ description: 'Search through memories with semantic vector search',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ query: { type: 'string', description: 'Search query' },
74
+ memory_type: { type: 'string', description: 'Filter by memory type' },
75
+ limit: { type: 'number', description: 'Maximum results to return', default: 10 },
76
+ threshold: { type: 'number', description: 'Similarity threshold (0.0-1.0)', default: 0.7 },
77
+ tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' }
78
+ },
79
+ required: ['query']
80
+ }
81
+ },
82
+ {
83
+ name: 'get_memory',
84
+ description: 'Get a specific memory by ID',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ id: { type: 'string', description: 'Memory ID' }
89
+ },
90
+ required: ['id']
91
+ }
92
+ },
93
+ {
94
+ name: 'update_memory',
95
+ description: 'Update an existing memory',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {
99
+ id: { type: 'string', description: 'Memory ID' },
100
+ title: { type: 'string', description: 'Memory title' },
101
+ content: { type: 'string', description: 'Memory content' },
102
+ memory_type: { type: 'string', description: 'Type of memory' },
103
+ tags: { type: 'array', items: { type: 'string' }, description: 'Memory tags' }
104
+ },
105
+ required: ['id']
106
+ }
107
+ },
108
+ {
109
+ name: 'delete_memory',
110
+ description: 'Delete a memory by ID',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {
114
+ id: { type: 'string', description: 'Memory ID' }
115
+ },
116
+ required: ['id']
117
+ }
118
+ },
119
+ {
120
+ name: 'list_memories',
121
+ description: 'List memories with pagination and filters',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ limit: { type: 'number', description: 'Number of memories to return', default: 20 },
126
+ offset: { type: 'number', description: 'Offset for pagination', default: 0 },
127
+ memory_type: { type: 'string', description: 'Filter by memory type' },
128
+ tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' }
129
+ }
130
+ }
131
+ },
132
+ // API Key Management Tools
133
+ {
134
+ name: 'create_api_key',
135
+ description: 'Create a new API key',
136
+ inputSchema: {
137
+ type: 'object',
138
+ properties: {
139
+ name: { type: 'string', description: 'API key name' },
140
+ description: { type: 'string', description: 'API key description' },
141
+ project_id: { type: 'string', description: 'Project ID' },
142
+ access_level: { type: 'string', description: 'Access level', enum: ['public', 'authenticated', 'team', 'admin', 'enterprise'] },
143
+ expires_in_days: { type: 'number', description: 'Expiration in days', default: 365 }
144
+ },
145
+ required: ['name']
146
+ }
147
+ },
148
+ {
149
+ name: 'list_api_keys',
150
+ description: 'List API keys',
151
+ inputSchema: {
152
+ type: 'object',
153
+ properties: {
154
+ project_id: { type: 'string', description: 'Filter by project ID' },
155
+ active_only: { type: 'boolean', description: 'Show only active keys', default: true }
156
+ }
157
+ }
158
+ },
159
+ {
160
+ name: 'rotate_api_key',
161
+ description: 'Rotate an API key',
162
+ inputSchema: {
163
+ type: 'object',
164
+ properties: {
165
+ key_id: { type: 'string', description: 'API key ID to rotate' }
166
+ },
167
+ required: ['key_id']
168
+ }
169
+ },
170
+ {
171
+ name: 'delete_api_key',
172
+ description: 'Delete an API key',
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ key_id: { type: 'string', description: 'API key ID to delete' }
177
+ },
178
+ required: ['key_id']
179
+ }
180
+ },
181
+ // Project Management Tools
182
+ {
183
+ name: 'create_project',
184
+ description: 'Create a new project',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ name: { type: 'string', description: 'Project name' },
189
+ description: { type: 'string', description: 'Project description' },
190
+ organization_id: { type: 'string', description: 'Organization ID' }
191
+ },
192
+ required: ['name']
193
+ }
194
+ },
195
+ {
196
+ name: 'list_projects',
197
+ description: 'List projects',
198
+ inputSchema: {
199
+ type: 'object',
200
+ properties: {
201
+ organization_id: { type: 'string', description: 'Filter by organization ID' }
202
+ }
203
+ }
204
+ },
205
+ // Organization Management Tools
206
+ {
207
+ name: 'get_organization_info',
208
+ description: 'Get organization information',
209
+ inputSchema: {
210
+ type: 'object',
211
+ properties: {}
212
+ }
213
+ },
214
+ // Authentication Tools
215
+ {
216
+ name: 'get_auth_status',
217
+ description: 'Get authentication status',
218
+ inputSchema: {
219
+ type: 'object',
220
+ properties: {}
221
+ }
222
+ },
223
+ // Configuration Tools
224
+ {
225
+ name: 'get_config',
226
+ description: 'Get configuration settings',
227
+ inputSchema: {
228
+ type: 'object',
229
+ properties: {
230
+ key: { type: 'string', description: 'Specific config key to retrieve' }
231
+ }
232
+ }
233
+ },
234
+ {
235
+ name: 'set_config',
236
+ description: 'Set configuration setting',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ key: { type: 'string', description: 'Configuration key' },
241
+ value: { type: 'string', description: 'Configuration value' }
242
+ },
243
+ required: ['key', 'value']
244
+ }
245
+ },
246
+ // Health and Status Tools
247
+ {
248
+ name: 'get_health_status',
249
+ description: 'Get system health status',
250
+ inputSchema: {
251
+ type: 'object',
252
+ properties: {}
253
+ }
254
+ }
255
+ ]
256
+ };
257
+ });
258
+ // Handle tool calls - Comprehensive implementation
259
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
260
+ const { name, arguments: args } = request.params;
261
+ try {
262
+ const apiKey = process.env.LANONASIS_API_KEY;
263
+ const apiUrl = process.env.LANONASIS_API_URL || 'https://dashboard.lanonasis.com';
264
+ if (!apiKey) {
265
+ return {
266
+ content: [
267
+ {
268
+ type: 'text',
269
+ text: 'Error: LANONASIS_API_KEY environment variable is required'
270
+ }
271
+ ]
272
+ };
273
+ }
274
+ const headers = {
275
+ 'Authorization': `Bearer ${apiKey}`,
276
+ 'Content-Type': 'application/json',
277
+ 'User-Agent': 'lanonasis-mcp-server/1.3.0'
278
+ };
279
+ switch (name) {
280
+ // Memory Management Tools
281
+ case 'create_memory': {
282
+ const response = await fetch(`${apiUrl}/api/v1/memory`, {
283
+ method: 'POST',
284
+ headers,
285
+ body: JSON.stringify(args)
286
+ });
287
+ if (!response.ok) {
288
+ const errorText = await response.text();
289
+ throw new Error(`Memory creation failed: ${response.status} ${response.statusText} - ${errorText}`);
290
+ }
291
+ const result = await response.json();
292
+ return {
293
+ content: [
294
+ {
295
+ type: 'text',
296
+ text: `✅ Memory created successfully:\n${JSON.stringify(result, null, 2)}`
297
+ }
298
+ ]
299
+ };
300
+ }
301
+ case 'search_memories': {
302
+ const queryParams = new URLSearchParams();
303
+ if (args.query)
304
+ queryParams.append('query', String(args.query));
305
+ if (args.memory_type)
306
+ queryParams.append('memory_type', String(args.memory_type));
307
+ if (args.limit)
308
+ queryParams.append('limit', args.limit.toString());
309
+ if (args.threshold)
310
+ queryParams.append('threshold', args.threshold.toString());
311
+ if (args.tags && Array.isArray(args.tags)) {
312
+ args.tags.forEach((tag) => queryParams.append('tags', String(tag)));
313
+ }
314
+ const response = await fetch(`${apiUrl}/api/v1/memory/search?${queryParams}`, {
315
+ method: 'GET',
316
+ headers
317
+ });
318
+ if (!response.ok) {
319
+ const errorText = await response.text();
320
+ throw new Error(`Memory search failed: ${response.status} ${response.statusText} - ${errorText}`);
321
+ }
322
+ const result = await response.json();
323
+ return {
324
+ content: [
325
+ {
326
+ type: 'text',
327
+ text: `🔍 Search results (${result.length || 0} found):\n${JSON.stringify(result, null, 2)}`
328
+ }
329
+ ]
330
+ };
331
+ }
332
+ case 'get_memory': {
333
+ const response = await fetch(`${apiUrl}/api/v1/memory/${args.id}`, {
334
+ method: 'GET',
335
+ headers
336
+ });
337
+ if (!response.ok) {
338
+ const errorText = await response.text();
339
+ throw new Error(`Memory retrieval failed: ${response.status} ${response.statusText} - ${errorText}`);
340
+ }
341
+ const result = await response.json();
342
+ return {
343
+ content: [
344
+ {
345
+ type: 'text',
346
+ text: `📄 Memory details:\n${JSON.stringify(result, null, 2)}`
347
+ }
348
+ ]
349
+ };
350
+ }
351
+ case 'update_memory': {
352
+ const { id, ...updateData } = args;
353
+ const response = await fetch(`${apiUrl}/api/v1/memory/${id}`, {
354
+ method: 'PUT',
355
+ headers,
356
+ body: JSON.stringify(updateData)
357
+ });
358
+ if (!response.ok) {
359
+ const errorText = await response.text();
360
+ throw new Error(`Memory update failed: ${response.status} ${response.statusText} - ${errorText}`);
361
+ }
362
+ const result = await response.json();
363
+ return {
364
+ content: [
365
+ {
366
+ type: 'text',
367
+ text: `✏️ Memory updated successfully:\n${JSON.stringify(result, null, 2)}`
368
+ }
369
+ ]
370
+ };
371
+ }
372
+ case 'delete_memory': {
373
+ const response = await fetch(`${apiUrl}/api/v1/memory/${args.id}`, {
374
+ method: 'DELETE',
375
+ headers
376
+ });
377
+ if (!response.ok) {
378
+ const errorText = await response.text();
379
+ throw new Error(`Memory deletion failed: ${response.status} ${response.statusText} - ${errorText}`);
380
+ }
381
+ return {
382
+ content: [
383
+ {
384
+ type: 'text',
385
+ text: `🗑️ Memory deleted successfully (ID: ${args.id})`
386
+ }
387
+ ]
388
+ };
389
+ }
390
+ case 'list_memories': {
391
+ const queryParams = new URLSearchParams();
392
+ if (args.limit)
393
+ queryParams.append('limit', args.limit.toString());
394
+ if (args.offset)
395
+ queryParams.append('offset', args.offset.toString());
396
+ if (args.memory_type)
397
+ queryParams.append('memory_type', String(args.memory_type));
398
+ if (args.tags && Array.isArray(args.tags)) {
399
+ args.tags.forEach((tag) => queryParams.append('tags', String(tag)));
400
+ }
401
+ const response = await fetch(`${apiUrl}/api/v1/memory?${queryParams}`, {
402
+ method: 'GET',
403
+ headers
404
+ });
405
+ if (!response.ok) {
406
+ const errorText = await response.text();
407
+ throw new Error(`Memory listing failed: ${response.status} ${response.statusText} - ${errorText}`);
408
+ }
409
+ const result = await response.json();
410
+ return {
411
+ content: [
412
+ {
413
+ type: 'text',
414
+ text: `📋 Memory list (${result.length || 0} items):\n${JSON.stringify(result, null, 2)}`
415
+ }
416
+ ]
417
+ };
418
+ }
419
+ // API Key Management Tools
420
+ case 'create_api_key': {
421
+ const response = await fetch(`${apiUrl}/api/v1/api-keys`, {
422
+ method: 'POST',
423
+ headers,
424
+ body: JSON.stringify(args)
425
+ });
426
+ if (!response.ok) {
427
+ const errorText = await response.text();
428
+ throw new Error(`API key creation failed: ${response.status} ${response.statusText} - ${errorText}`);
429
+ }
430
+ const result = await response.json();
431
+ return {
432
+ content: [
433
+ {
434
+ type: 'text',
435
+ text: `🔑 API key created successfully:\n${JSON.stringify(result, null, 2)}`
436
+ }
437
+ ]
438
+ };
439
+ }
440
+ case 'list_api_keys': {
441
+ const queryParams = new URLSearchParams();
442
+ if (args.project_id)
443
+ queryParams.append('project_id', String(args.project_id));
444
+ if (args.active_only !== undefined)
445
+ queryParams.append('active_only', args.active_only.toString());
446
+ const response = await fetch(`${apiUrl}/api/v1/api-keys?${queryParams}`, {
447
+ method: 'GET',
448
+ headers
449
+ });
450
+ if (!response.ok) {
451
+ const errorText = await response.text();
452
+ throw new Error(`API key listing failed: ${response.status} ${response.statusText} - ${errorText}`);
453
+ }
454
+ const result = await response.json();
455
+ return {
456
+ content: [
457
+ {
458
+ type: 'text',
459
+ text: `🔑 API keys (${result.length || 0} found):\n${JSON.stringify(result, null, 2)}`
460
+ }
461
+ ]
462
+ };
463
+ }
464
+ case 'get_health_status': {
465
+ const response = await fetch(`${apiUrl}/api/v1/health`, {
466
+ method: 'GET',
467
+ headers
468
+ });
469
+ if (!response.ok) {
470
+ const errorText = await response.text();
471
+ throw new Error(`Health check failed: ${response.status} ${response.statusText} - ${errorText}`);
472
+ }
473
+ const result = await response.json();
474
+ return {
475
+ content: [
476
+ {
477
+ type: 'text',
478
+ text: `💚 System health status:\n${JSON.stringify(result, null, 2)}`
479
+ }
480
+ ]
481
+ };
482
+ }
483
+ default:
484
+ return {
485
+ content: [
486
+ {
487
+ type: 'text',
488
+ text: `❌ Unknown tool: ${name}. Available tools: create_memory, search_memories, get_memory, update_memory, delete_memory, list_memories, create_api_key, list_api_keys, get_health_status`
489
+ }
490
+ ]
491
+ };
492
+ }
493
+ }
494
+ catch (error) {
495
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
496
+ return {
497
+ content: [
498
+ {
499
+ type: 'text',
500
+ text: `❌ Error: ${errorMessage}`
501
+ }
502
+ ]
503
+ };
504
+ }
505
+ });
506
+ }
507
+ async run() {
508
+ const transport = new StdioServerTransport();
509
+ await this.server.connect(transport);
510
+ // Log to stderr that server is ready
511
+ console.error('[MCP-INFO] Lanonasis MCP Server started and ready');
512
+ }
513
+ }
514
+ // Start the server
515
+ const server = new LanonasisMCPServer();
516
+ server.run().catch((error) => {
517
+ console.error('[MCP-ERROR] Failed to start server:', error);
518
+ process.exit(1);
519
+ });
@@ -57,7 +57,7 @@ export interface GetMemoriesParams {
57
57
  limit?: number;
58
58
  offset?: number;
59
59
  memory_type?: MemoryType;
60
- tags?: string[];
60
+ tags?: string[] | string;
61
61
  topic_id?: string;
62
62
  sort_by?: 'created_at' | 'updated_at' | 'last_accessed' | 'access_count';
63
63
  sort_order?: 'asc' | 'desc';
@@ -125,13 +125,19 @@ export interface HealthStatus {
125
125
  }>;
126
126
  }
127
127
  export interface PaginatedResponse<T> {
128
- data: T[];
128
+ data?: T[];
129
+ memories?: T[];
130
+ results?: T[];
129
131
  pagination: {
130
132
  total: number;
131
133
  limit: number;
132
134
  offset: number;
133
135
  has_more: boolean;
136
+ page?: number;
137
+ pages?: number;
134
138
  };
139
+ total_results?: number;
140
+ search_time_ms?: number;
135
141
  }
136
142
  export interface ApiErrorResponse {
137
143
  error: string;
@@ -159,6 +165,10 @@ export declare class APIClient {
159
165
  updateTopic(id: string, data: UpdateTopicRequest): Promise<MemoryTopic>;
160
166
  deleteTopic(id: string): Promise<void>;
161
167
  getHealth(): Promise<HealthStatus>;
168
+ get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
169
+ post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
170
+ put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
171
+ delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
162
172
  request<T = Record<string, unknown>>(config: AxiosRequestConfig): Promise<T>;
163
173
  }
164
174
  export declare const apiClient: APIClient;
package/dist/utils/api.js CHANGED
@@ -132,6 +132,23 @@ export class APIClient {
132
132
  const response = await this.client.get('/api/v1/health');
133
133
  return response.data;
134
134
  }
135
+ // Generic HTTP methods
136
+ async get(url, config) {
137
+ const response = await this.client.get(url, config);
138
+ return response.data;
139
+ }
140
+ async post(url, data, config) {
141
+ const response = await this.client.post(url, data, config);
142
+ return response.data;
143
+ }
144
+ async put(url, data, config) {
145
+ const response = await this.client.put(url, data, config);
146
+ return response.data;
147
+ }
148
+ async delete(url, config) {
149
+ const response = await this.client.delete(url, config);
150
+ return response.data;
151
+ }
135
152
  // Generic request method
136
153
  async request(config) {
137
154
  const response = await this.client.request(config);
@@ -16,8 +16,6 @@ export declare class CLIConfig {
16
16
  setApiUrl(url: string): Promise<void>;
17
17
  setToken(token: string): Promise<void>;
18
18
  getToken(): string | undefined;
19
- setApiKey(apiKey: string): Promise<void>;
20
- getApiKey(): string | undefined;
21
19
  getCurrentUser(): Promise<UserProfile | undefined>;
22
20
  isAuthenticated(): Promise<boolean>;
23
21
  logout(): Promise<void>;
@@ -39,7 +39,7 @@ export class CLIConfig {
39
39
  getApiUrl() {
40
40
  return process.env.MEMORY_API_URL ||
41
41
  this.config.apiUrl ||
42
- 'http://localhost:3000/api/v1';
42
+ 'https://api.lanonasis.com';
43
43
  }
44
44
  async setApiUrl(url) {
45
45
  this.config.apiUrl = url;
@@ -67,22 +67,10 @@ export class CLIConfig {
67
67
  getToken() {
68
68
  return this.config.token;
69
69
  }
70
- async setApiKey(apiKey) {
71
- this.config.apiKey = apiKey;
72
- await this.save();
73
- }
74
- getApiKey() {
75
- return this.config.apiKey || process.env.LANONASIS_API_KEY;
76
- }
77
70
  async getCurrentUser() {
78
71
  return this.config.user;
79
72
  }
80
73
  async isAuthenticated() {
81
- // Check for API key first
82
- const apiKey = this.getApiKey();
83
- if (apiKey)
84
- return true;
85
- // Then check for valid JWT token
86
74
  const token = this.getToken();
87
75
  if (!token)
88
76
  return false;
@@ -97,7 +85,6 @@ export class CLIConfig {
97
85
  }
98
86
  async logout() {
99
87
  this.config.token = undefined;
100
- this.config.apiKey = undefined;
101
88
  this.config.user = undefined;
102
89
  await this.save();
103
90
  }
@@ -133,7 +120,7 @@ export class CLIConfig {
133
120
  return this.config.mcpServerPath || path.join(__dirname, '../../../../onasis-gateway/mcp-server/server.js');
134
121
  }
135
122
  getMCPServerUrl() {
136
- return this.config.mcpServerUrl || 'https://api.lanonasis.com';
123
+ return this.config.mcpServerUrl || 'https://dashboard.lanonasis.com';
137
124
  }
138
125
  shouldUseRemoteMCP() {
139
126
  const preference = this.config.mcpPreference || 'auto';
@@ -2,3 +2,5 @@ export declare function formatOutput(data: unknown, format?: string): void;
2
2
  export declare function formatBytes(bytes: number): string;
3
3
  export declare function truncateText(text: string, maxLength: number): string;
4
4
  export declare function formatDuration(ms: number): string;
5
+ export declare function formatDate(date: string | Date): string;
6
+ export declare function formatTableData(data: unknown[]): string[][];
@@ -32,3 +32,16 @@ export function formatDuration(ms) {
32
32
  return `${(ms / 1000).toFixed(1)}s`;
33
33
  return `${(ms / 60000).toFixed(1)}m`;
34
34
  }
35
+ export function formatDate(date) {
36
+ const d = new Date(date);
37
+ return d.toLocaleString();
38
+ }
39
+ export function formatTableData(data) {
40
+ return data.map(item => {
41
+ if (Array.isArray(item))
42
+ return item.map(String);
43
+ if (typeof item === 'object' && item !== null)
44
+ return Object.values(item).map(String);
45
+ return [String(item)];
46
+ });
47
+ }