@lanonasis/cli 3.9.7 → 3.9.9

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog - @lanonasis/cli
2
2
 
3
+ ## [3.9.8] - 2026-02-25
4
+
5
+ ### ✨ New Features
6
+
7
+ - **Issue #98 (CLI Memory UX Enhancements)**:
8
+ - Added `onasis memory create --json <json>` for direct JSON payload creation.
9
+ - Added `onasis memory create --content-file <path>` for file-based content ingestion.
10
+ - Added `onasis memory save-session` to persist branch/status/changed-files session context as memory.
11
+ - **Behavior methods via CLI commands**:
12
+ - Added `onasis memory intelligence` subcommands for health check, tag suggestions, related lookup, duplicate detection, insight extraction, and pattern analysis.
13
+ - Added `onasis memory behavior` subcommands for `record`, `recall`, and `suggest` workflow behavior operations.
14
+
15
+ ### 🐛 Bug Fixes
16
+
17
+ - Normalized memory response handling for create/get/update wrappers (`{ data: ... }`) so CLI output fields like ID/Title/Type are consistently resolved.
18
+ - Ensured token refresh is executed before memory command paths to reduce intermittent re-auth prompts during active OAuth sessions.
19
+ - Aligned default semantic search thresholds to `0.55` across memory and MCP search command paths for consistent result behavior.
20
+
3
21
  ## [3.9.7] - 2026-02-21
4
22
 
5
23
  ### ✨ New Features
@@ -162,7 +162,7 @@ apiKeysCommand
162
162
  validate: (input) => input.length > 0 || 'Value is required'
163
163
  },
164
164
  {
165
- type: 'list',
165
+ type: 'select',
166
166
  name: 'keyType',
167
167
  message: 'Key type:',
168
168
  when: !keyData.keyType,
@@ -177,21 +177,21 @@ apiKeysCommand
177
177
  ]
178
178
  },
179
179
  {
180
- type: 'list',
180
+ type: 'select',
181
181
  name: 'environment',
182
182
  message: 'Environment:',
183
183
  choices: ['development', 'staging', 'production'],
184
184
  default: 'development'
185
185
  },
186
186
  {
187
- type: 'list',
187
+ type: 'select',
188
188
  name: 'projectId',
189
189
  message: 'Select project:',
190
190
  when: !keyData.projectId && projects.length > 0,
191
191
  choices: projects.map((p) => ({ name: `${p.name} (${p.id})`, value: p.id }))
192
192
  },
193
193
  {
194
- type: 'list',
194
+ type: 'select',
195
195
  name: 'accessLevel',
196
196
  message: 'Access level:',
197
197
  choices: ['public', 'authenticated', 'team', 'admin', 'enterprise'],
@@ -543,7 +543,7 @@ mcpCommand
543
543
  default: false
544
544
  },
545
545
  {
546
- type: 'list',
546
+ type: 'select',
547
547
  name: 'riskLevel',
548
548
  message: 'Risk level:',
549
549
  choices: ['low', 'medium', 'high', 'critical'],
@@ -647,7 +647,7 @@ mcpCommand
647
647
  const tools = await apiClient.get('/api-keys/mcp/tools');
648
648
  const answers = await inquirer.prompt([
649
649
  {
650
- type: 'list',
650
+ type: 'select',
651
651
  name: 'toolId',
652
652
  message: 'Select MCP tool:',
653
653
  when: !requestData.toolId && tools.length > 0,
@@ -672,7 +672,7 @@ mcpCommand
672
672
  validate: (input) => input.length > 0 || 'At least one key name is required'
673
673
  },
674
674
  {
675
- type: 'list',
675
+ type: 'select',
676
676
  name: 'environment',
677
677
  message: 'Environment:',
678
678
  when: !requestData.environment,
@@ -569,7 +569,7 @@ export async function loginCommand(options) {
569
569
  // Show authentication options
570
570
  const authChoice = await inquirer.prompt([
571
571
  {
572
- type: 'list',
572
+ type: 'select',
573
573
  name: 'method',
574
574
  message: 'Choose authentication method:',
575
575
  choices: [
@@ -821,7 +821,7 @@ async function handleCredentialsFlow(options, config) {
821
821
  }
822
822
  // Store JWT token for API authentication
823
823
  await config.setToken(authToken);
824
- await config.set('authMethod', 'jwt');
824
+ await config.setAndSave('authMethod', 'jwt');
825
825
  spinner.succeed('Login successful');
826
826
  console.log();
827
827
  console.log(chalk.green('✓ Authenticated successfully'));
@@ -132,6 +132,16 @@ export async function generateCompletionData() {
132
132
  description: 'Show memory statistics',
133
133
  options: []
134
134
  },
135
+ {
136
+ name: 'intelligence',
137
+ description: 'Memory intelligence operations (use: memory intelligence --help)',
138
+ options: []
139
+ },
140
+ {
141
+ name: 'behavior',
142
+ description: 'Behavior pattern operations (use: memory behavior --help)',
143
+ options: []
144
+ },
135
145
  {
136
146
  name: 'bulk-delete',
137
147
  description: 'Delete multiple memories',
@@ -188,7 +188,7 @@ export class UserGuidanceSystem {
188
188
  default: 'https://api.lanonasis.com/api/v1'
189
189
  },
190
190
  {
191
- type: 'list',
191
+ type: 'select',
192
192
  name: 'outputFormat',
193
193
  message: 'Preferred output format:',
194
194
  choices: ['table', 'json', 'yaml', 'csv'],
@@ -206,7 +206,7 @@ export class UserGuidanceSystem {
206
206
  console.log();
207
207
  const { authMethod } = await inquirer.prompt([
208
208
  {
209
- type: 'list',
209
+ type: 'select',
210
210
  name: 'authMethod',
211
211
  message: 'Choose authentication method:',
212
212
  choices: [
@@ -335,7 +335,7 @@ export class UserGuidanceSystem {
335
335
  console.log();
336
336
  const { shell } = await inquirer.prompt([
337
337
  {
338
- type: 'list',
338
+ type: 'select',
339
339
  name: 'shell',
340
340
  message: 'Which shell do you use?',
341
341
  choices: [
@@ -4,9 +4,42 @@ import { table } from 'table';
4
4
  import { getMCPClient } from '../utils/mcp-client.js';
5
5
  import { EnhancedMCPClient } from '../mcp/client/enhanced-client.js';
6
6
  import { CLIConfig } from '../utils/config.js';
7
+ import { apiClient } from '../utils/api.js';
7
8
  import WebSocket from 'ws';
8
9
  import { dirname, join } from 'path';
9
10
  import { createConnectionManager } from '../ux/index.js';
11
+ const tokenizeQuery = (input) => input
12
+ .toLowerCase()
13
+ .split(/[^a-z0-9]+/g)
14
+ .map((token) => token.trim())
15
+ .filter((token) => token.length >= 2);
16
+ const lexicalScore = (query, memory) => {
17
+ const tokens = tokenizeQuery(query);
18
+ if (tokens.length === 0)
19
+ return 0;
20
+ const haystack = `${memory.title || ''} ${memory.content || ''} ${(memory.tags || []).join(' ')}`.toLowerCase();
21
+ const hits = tokens.filter((token) => haystack.includes(token)).length;
22
+ if (hits === 0)
23
+ return 0;
24
+ const ratio = hits / tokens.length;
25
+ return Math.max(0.35, Math.min(0.69, Number((ratio * 0.65).toFixed(3))));
26
+ };
27
+ const fallbackMemorySearch = async (query, limit) => {
28
+ const candidateLimit = Math.min(Math.max(limit * 8, 50), 200);
29
+ const memoriesResult = await apiClient.getMemories({ page: 1, limit: candidateLimit });
30
+ const candidates = (memoriesResult.memories || memoriesResult.data || []);
31
+ return candidates
32
+ .map((memory) => ({
33
+ id: memory.id,
34
+ title: memory.title,
35
+ memory_type: memory.memory_type,
36
+ similarity_score: lexicalScore(query, memory),
37
+ content: memory.content || ''
38
+ }))
39
+ .filter((memory) => memory.similarity_score > 0)
40
+ .sort((a, b) => b.similarity_score - a.similarity_score)
41
+ .slice(0, limit);
42
+ };
10
43
  /**
11
44
  * Register MCP-related CLI commands (mcp and mcp-server) on a Commander program.
12
45
  *
@@ -460,25 +493,54 @@ export function mcpCommands(program) {
460
493
  });
461
494
  memory.command('search')
462
495
  .description('Search memories via MCP')
463
- .argument('<query>', 'Search query')
496
+ .argument('<query...>', 'Search query')
464
497
  .option('-l, --limit <number>', 'Maximum results', '10')
465
- .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.7')
466
- .action(async (query, options) => {
498
+ .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.55')
499
+ .action(async (queryParts, options) => {
500
+ const query = Array.isArray(queryParts) ? queryParts.join(' ').trim() : String(queryParts || '').trim();
501
+ if (!query) {
502
+ console.error(chalk.red('Search query is required'));
503
+ process.exit(1);
504
+ }
467
505
  const spinner = ora('Searching memories via MCP...').start();
506
+ const client = getMCPClient();
468
507
  try {
469
- const client = getMCPClient();
470
508
  if (!client.isConnectedToServer()) {
471
509
  spinner.info('Not connected. Attempting auto-connect...');
472
510
  const config = new CLIConfig();
473
511
  const useRemote = !!config.get('token');
474
512
  await client.connect({ useRemote });
475
513
  }
476
- const results = await client.callTool('memory_search_memories', {
477
- query,
478
- limit: parseInt(options.limit),
479
- threshold: parseFloat(options.threshold)
480
- });
481
- spinner.succeed(`Found ${results.length} memories`);
514
+ const limit = parseInt(options.limit);
515
+ const threshold = parseFloat(options.threshold);
516
+ let results = [];
517
+ let usedLexicalFallback = false;
518
+ try {
519
+ const rawResult = await client.callTool('memory_search_memories', {
520
+ query,
521
+ limit,
522
+ threshold
523
+ });
524
+ results = Array.isArray(rawResult)
525
+ ? rawResult
526
+ : Array.isArray(rawResult?.results)
527
+ ? rawResult.results
528
+ : Array.isArray(rawResult?.result)
529
+ ? rawResult.result
530
+ : [];
531
+ }
532
+ catch (error) {
533
+ const message = error instanceof Error ? error.message : String(error);
534
+ if (/vector dimensions|hybrid search failed|memory search failed/i.test(message)) {
535
+ spinner.info('Semantic search unavailable in MCP path, using lexical fallback...');
536
+ results = await fallbackMemorySearch(query, limit);
537
+ usedLexicalFallback = true;
538
+ }
539
+ else {
540
+ throw error;
541
+ }
542
+ }
543
+ spinner.succeed(`Found ${results.length} memories${usedLexicalFallback ? ' (lexical fallback)' : ''}`);
482
544
  if (results.length === 0) {
483
545
  console.log(chalk.yellow('\nNo memories found matching your query'));
484
546
  return;
@@ -488,7 +550,7 @@ export function mcpCommands(program) {
488
550
  console.log(`\n${chalk.bold(`${index + 1}. ${memory.title}`)}`);
489
551
  console.log(` ID: ${chalk.gray(memory.id)}`);
490
552
  console.log(` Type: ${chalk.blue(memory.memory_type)}`);
491
- console.log(` Score: ${chalk.green((memory.relevance_score * 100).toFixed(1) + '%')}`);
553
+ console.log(` Score: ${chalk.green((memory.similarity_score * 100).toFixed(1) + '%')}`);
492
554
  console.log(` Content: ${memory.content.substring(0, 100)}...`);
493
555
  });
494
556
  }
@@ -496,6 +558,11 @@ export function mcpCommands(program) {
496
558
  spinner.fail(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
497
559
  process.exit(1);
498
560
  }
561
+ finally {
562
+ if (client.isConnectedToServer()) {
563
+ await client.disconnect().catch(() => { });
564
+ }
565
+ }
499
566
  });
500
567
  // Configure MCP preferences
501
568
  mcp.command('config')