@lanonasis/cli 3.9.6 → 3.9.8

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.
@@ -4,6 +4,7 @@ import ora from 'ora';
4
4
  import { table } from 'table';
5
5
  import wrap from 'word-wrap';
6
6
  import { format } from 'date-fns';
7
+ import { MemoryIntelligenceClient } from '@lanonasis/mem-intel-sdk';
7
8
  import { apiClient } from '../utils/api.js';
8
9
  import { formatBytes, truncateText } from '../utils/formatting.js';
9
10
  import { CLIConfig } from '../utils/config.js';
@@ -12,6 +13,7 @@ import * as fs from 'fs/promises';
12
13
  import { exec as execCb } from 'node:child_process';
13
14
  import { promisify } from 'node:util';
14
15
  const exec = promisify(execCb);
16
+ const MAX_JSON_OPTION_BYTES = 1024 * 1024; // 1 MiB guardrail for CLI JSON flags
15
17
  const MEMORY_TYPE_CHOICES = [
16
18
  'context',
17
19
  'project',
@@ -57,6 +59,205 @@ const collectMemoryContent = async (prompt, inputMode, defaultContent) => {
57
59
  defaultContent,
58
60
  });
59
61
  };
62
+ const parseJsonOption = (value, fieldName) => {
63
+ if (!value)
64
+ return undefined;
65
+ const payloadSize = Buffer.byteLength(value, 'utf8');
66
+ if (payloadSize > MAX_JSON_OPTION_BYTES) {
67
+ throw new Error(`${fieldName} JSON payload is too large (${payloadSize} bytes). Max allowed is ${MAX_JSON_OPTION_BYTES} bytes.`);
68
+ }
69
+ try {
70
+ return JSON.parse(value);
71
+ }
72
+ catch (error) {
73
+ const message = error instanceof Error ? error.message : 'Invalid JSON';
74
+ throw new Error(`Invalid ${fieldName} JSON: ${message}`);
75
+ }
76
+ };
77
+ const isPlainObject = (value) => {
78
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
79
+ };
80
+ const ensureJsonObject = (value, fieldName) => {
81
+ if (value === undefined)
82
+ return undefined;
83
+ if (!isPlainObject(value)) {
84
+ throw new Error(`${fieldName} must be a JSON object`);
85
+ }
86
+ return value;
87
+ };
88
+ const ensureBehaviorActions = (value, fieldName, options = {}) => {
89
+ if (value === undefined) {
90
+ return [];
91
+ }
92
+ if (!Array.isArray(value)) {
93
+ throw new Error(`${fieldName} must be a JSON array`);
94
+ }
95
+ if (!options.allowEmpty && value.length === 0) {
96
+ throw new Error(`${fieldName} must be a non-empty JSON array`);
97
+ }
98
+ return value.map((entry, index) => {
99
+ if (!isPlainObject(entry)) {
100
+ throw new Error(`${fieldName}[${index}] must be a JSON object`);
101
+ }
102
+ const tool = typeof entry.tool === 'string' ? entry.tool.trim() : '';
103
+ if (!tool) {
104
+ throw new Error(`${fieldName}[${index}].tool is required`);
105
+ }
106
+ const parsed = { tool };
107
+ if (entry.params !== undefined) {
108
+ if (!isPlainObject(entry.params)) {
109
+ throw new Error(`${fieldName}[${index}].params must be a JSON object`);
110
+ }
111
+ parsed.params = entry.params;
112
+ }
113
+ if (entry.timestamp !== undefined) {
114
+ if (typeof entry.timestamp !== 'string' || !entry.timestamp.trim()) {
115
+ throw new Error(`${fieldName}[${index}].timestamp must be a non-empty string`);
116
+ }
117
+ parsed.timestamp = entry.timestamp;
118
+ }
119
+ if (entry.duration_ms !== undefined) {
120
+ if (typeof entry.duration_ms !== 'number' ||
121
+ !Number.isFinite(entry.duration_ms) ||
122
+ entry.duration_ms < 0) {
123
+ throw new Error(`${fieldName}[${index}].duration_ms must be a non-negative number`);
124
+ }
125
+ parsed.duration_ms = entry.duration_ms;
126
+ }
127
+ return parsed;
128
+ });
129
+ };
130
+ const clampThreshold = (value) => {
131
+ if (!Number.isFinite(value))
132
+ return 0.55;
133
+ return Math.max(0, Math.min(1, value));
134
+ };
135
+ const buildSearchThresholdPlan = (requestedThreshold, hasTagFilter) => {
136
+ const plan = [];
137
+ const pushUnique = (threshold) => {
138
+ const normalized = Number(threshold.toFixed(2));
139
+ if (!plan.some((item) => Math.abs(item - normalized) < 0.0001)) {
140
+ plan.push(normalized);
141
+ }
142
+ };
143
+ pushUnique(requestedThreshold);
144
+ if (requestedThreshold > 0.45) {
145
+ pushUnique(Math.max(0.45, requestedThreshold - 0.15));
146
+ }
147
+ if (hasTagFilter) {
148
+ // Tag-assisted recall fallback for sparse semantic scores.
149
+ pushUnique(0);
150
+ }
151
+ return plan;
152
+ };
153
+ const tokenizeSearchQuery = (input) => {
154
+ return input
155
+ .toLowerCase()
156
+ .split(/[^a-z0-9]+/g)
157
+ .map((token) => token.trim())
158
+ .filter((token) => token.length >= 2);
159
+ };
160
+ const lexicalSimilarityScore = (query, memory) => {
161
+ const tokens = tokenizeSearchQuery(query);
162
+ if (tokens.length === 0)
163
+ return 0;
164
+ const haystack = `${memory.title || ''} ${memory.content || ''} ${(memory.tags || []).join(' ')}`.toLowerCase();
165
+ const hits = tokens.filter((token) => haystack.includes(token)).length;
166
+ if (hits === 0)
167
+ return 0;
168
+ const ratio = hits / tokens.length;
169
+ return Math.max(0.35, Math.min(0.69, Number((ratio * 0.65).toFixed(3))));
170
+ };
171
+ const lexicalFallbackSearch = async (query, searchOptions) => {
172
+ const candidateLimit = Math.min(Math.max(searchOptions.limit * 8, 50), 200);
173
+ const primaryType = searchOptions.memory_types?.length === 1 ? searchOptions.memory_types[0] : undefined;
174
+ const memoriesResult = await apiClient.getMemories({
175
+ page: 1,
176
+ limit: candidateLimit,
177
+ memory_type: primaryType,
178
+ tags: searchOptions.tags?.join(','),
179
+ });
180
+ let candidates = (memoriesResult.memories || memoriesResult.data || []);
181
+ if (searchOptions.memory_types && searchOptions.memory_types.length > 1) {
182
+ const typeSet = new Set(searchOptions.memory_types);
183
+ candidates = candidates.filter((memory) => typeSet.has(memory.memory_type));
184
+ }
185
+ if (searchOptions.tags && searchOptions.tags.length > 0) {
186
+ const normalizedTags = new Set(searchOptions.tags.map((tag) => tag.toLowerCase()));
187
+ candidates = candidates.filter((memory) => (memory.tags || []).some((tag) => normalizedTags.has(tag.toLowerCase())));
188
+ }
189
+ return candidates
190
+ .map((memory) => ({
191
+ ...memory,
192
+ similarity_score: lexicalSimilarityScore(query, memory),
193
+ }))
194
+ .filter((memory) => memory.similarity_score > 0)
195
+ .sort((a, b) => b.similarity_score - a.similarity_score)
196
+ .slice(0, searchOptions.limit);
197
+ };
198
+ const resolveCurrentUserId = async () => {
199
+ const profile = await apiClient.getUserProfile();
200
+ if (!profile?.id) {
201
+ throw new Error('Unable to resolve user profile id for intelligence request');
202
+ }
203
+ return profile.id;
204
+ };
205
+ const createIntelligenceTransport = async () => {
206
+ const config = new CLIConfig();
207
+ await config.init();
208
+ await config.refreshTokenIfNeeded();
209
+ const authToken = config.getToken();
210
+ const apiKey = await config.getVendorKeyAsync();
211
+ const apiUrl = `${config.getApiUrl().replace(/\/$/, '')}/api/v1`;
212
+ if (authToken) {
213
+ return {
214
+ mode: 'sdk',
215
+ client: new MemoryIntelligenceClient({
216
+ apiUrl,
217
+ authToken,
218
+ authType: 'bearer',
219
+ allowMissingAuth: false,
220
+ }),
221
+ };
222
+ }
223
+ if (apiKey) {
224
+ if (apiKey.startsWith('lano_')) {
225
+ return {
226
+ mode: 'sdk',
227
+ client: new MemoryIntelligenceClient({
228
+ apiUrl,
229
+ apiKey,
230
+ authType: 'apiKey',
231
+ allowMissingAuth: false,
232
+ }),
233
+ };
234
+ }
235
+ // Legacy non-lano key path: use CLI API client auth middleware directly.
236
+ return { mode: 'api' };
237
+ }
238
+ throw new Error('Authentication required. Run "lanonasis auth login" first.');
239
+ };
240
+ const printIntelligenceResult = (title, payload, options) => {
241
+ if (options.json) {
242
+ console.log(JSON.stringify(payload, null, 2));
243
+ return;
244
+ }
245
+ console.log(chalk.cyan.bold(`\n${title}`));
246
+ console.log(JSON.stringify(payload, null, 2));
247
+ };
248
+ const postIntelligenceEndpoint = async (transport, endpoint, payload) => {
249
+ if (transport.mode === 'sdk') {
250
+ if (!transport.client) {
251
+ throw new Error('SDK transport is not initialized');
252
+ }
253
+ const response = await transport.client.getHttpClient().postEnhanced(endpoint, payload);
254
+ if (response.error) {
255
+ throw new Error(response.error.message || `Request failed for ${endpoint}`);
256
+ }
257
+ return response.data;
258
+ }
259
+ return await apiClient.post(`/api/v1${endpoint}`, payload);
260
+ };
60
261
  export function memoryCommands(program) {
61
262
  // Create memory
62
263
  program
@@ -471,15 +672,16 @@ export function memoryCommands(program) {
471
672
  .description('Search memories using semantic search')
472
673
  .argument('<query>', 'search query')
473
674
  .option('-l, --limit <limit>', 'number of results', '20')
474
- .option('--threshold <threshold>', 'similarity threshold (0-1)', '0.7')
675
+ .option('--threshold <threshold>', 'similarity threshold (0-1)', '0.55')
475
676
  .option('--type <types>', 'filter by memory types (comma-separated)')
476
677
  .option('--tags <tags>', 'filter by tags (comma-separated)')
477
678
  .action(async (query, options) => {
478
679
  try {
479
680
  const spinner = ora(`Searching for "${query}"...`).start();
681
+ const requestedThreshold = clampThreshold(parseFloat(options.threshold || '0.55'));
480
682
  const searchOptions = {
481
683
  limit: parseInt(options.limit || '20'),
482
- threshold: parseFloat(options.threshold || '0.7')
684
+ threshold: requestedThreshold
483
685
  };
484
686
  if (options.type) {
485
687
  searchOptions.memory_types = options.type.split(',').map((t) => t.trim());
@@ -487,18 +689,52 @@ export function memoryCommands(program) {
487
689
  if (options.tags) {
488
690
  searchOptions.tags = options.tags.split(',').map((t) => t.trim());
489
691
  }
490
- const result = await apiClient.searchMemories(query, searchOptions);
692
+ const thresholdPlan = buildSearchThresholdPlan(requestedThreshold, Boolean(searchOptions.tags?.length));
693
+ let result = null;
694
+ let results = [];
695
+ let thresholdUsed = requestedThreshold;
696
+ let searchStrategy = 'semantic';
697
+ for (const threshold of thresholdPlan) {
698
+ const attempt = await apiClient.searchMemories(query, {
699
+ ...searchOptions,
700
+ threshold,
701
+ });
702
+ const attemptResults = (attempt.results || attempt.data || []);
703
+ result = attempt;
704
+ if (attemptResults.length > 0) {
705
+ results = attemptResults;
706
+ thresholdUsed = threshold;
707
+ const attemptStrategy = attempt.search_strategy;
708
+ searchStrategy = typeof attemptStrategy === 'string'
709
+ ? attemptStrategy
710
+ : 'semantic';
711
+ break;
712
+ }
713
+ }
714
+ if (results.length === 0) {
715
+ const lexicalResults = await lexicalFallbackSearch(query, searchOptions);
716
+ if (lexicalResults.length > 0) {
717
+ results = lexicalResults;
718
+ searchStrategy = 'cli_lexical_fallback';
719
+ }
720
+ }
491
721
  spinner.stop();
492
- const results = result.results || result.data || [];
493
722
  if (results.length === 0) {
494
723
  console.log(chalk.yellow('No memories found matching your search'));
724
+ console.log(chalk.gray(`Tried thresholds: ${thresholdPlan.map((t) => t.toFixed(2)).join(', ')}`));
495
725
  return;
496
726
  }
497
727
  console.log(chalk.blue.bold(`\n🔍 Search Results (${result.total_results || results.length} found)`));
498
728
  console.log(chalk.gray(`Query: "${query}" | Search time: ${result.search_time_ms || 0}ms`));
729
+ if (Math.abs(thresholdUsed - requestedThreshold) > 0.0001) {
730
+ console.log(chalk.gray(`No matches at ${requestedThreshold.toFixed(2)}; used adaptive threshold ${thresholdUsed.toFixed(2)}`));
731
+ }
732
+ if (searchStrategy) {
733
+ console.log(chalk.gray(`Search strategy: ${searchStrategy}`));
734
+ }
499
735
  console.log();
500
736
  results.forEach((memory, index) => {
501
- const score = (memory.relevance_score * 100).toFixed(1);
737
+ const score = (memory.similarity_score * 100).toFixed(1);
502
738
  console.log(chalk.green(`${index + 1}. ${memory.title}`) + chalk.gray(` (${score}% match)`));
503
739
  console.log(chalk.white(` ${truncateText(memory.content, 100)}`));
504
740
  console.log(chalk.cyan(` ID: ${memory.id}`) + chalk.gray(` | Type: ${memory.memory_type}`));
@@ -706,4 +942,278 @@ export function memoryCommands(program) {
706
942
  process.exit(1);
707
943
  }
708
944
  });
945
+ // Intelligence commands powered by @lanonasis/mem-intel-sdk
946
+ const intelligence = program
947
+ .command('intelligence')
948
+ .description('Memory intelligence operations');
949
+ intelligence
950
+ .command('health-check')
951
+ .description('Run memory intelligence health check')
952
+ .option('--json', 'Output raw JSON payload')
953
+ .action(async (options) => {
954
+ try {
955
+ const spinner = ora('Running intelligence health check...').start();
956
+ const transport = await createIntelligenceTransport();
957
+ const userId = await resolveCurrentUserId();
958
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', { user_id: userId, response_format: 'json' });
959
+ spinner.stop();
960
+ printIntelligenceResult('🩺 Intelligence Health Check', result, options);
961
+ }
962
+ catch (error) {
963
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
964
+ console.error(chalk.red('✖ Intelligence health check failed:'), errorMessage);
965
+ process.exit(1);
966
+ }
967
+ });
968
+ intelligence
969
+ .command('suggest-tags')
970
+ .description('Suggest tags for a memory')
971
+ .argument('<memory-id>', 'Memory ID')
972
+ .option('--max <number>', 'Maximum suggestions', '8')
973
+ .option('--json', 'Output raw JSON payload')
974
+ .action(async (memoryId, options) => {
975
+ try {
976
+ const spinner = ora('Generating tag suggestions...').start();
977
+ const transport = await createIntelligenceTransport();
978
+ const userId = await resolveCurrentUserId();
979
+ const maxSuggestions = Math.max(1, Math.min(20, parseInt(options.max || '8', 10)));
980
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/suggest-tags', {
981
+ memory_id: memoryId,
982
+ user_id: userId,
983
+ max_suggestions: maxSuggestions,
984
+ include_existing_tags: true,
985
+ response_format: 'json',
986
+ });
987
+ spinner.stop();
988
+ printIntelligenceResult('🏷️ Tag Suggestions', result, options);
989
+ }
990
+ catch (error) {
991
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
992
+ console.error(chalk.red('✖ Failed to suggest tags:'), errorMessage);
993
+ process.exit(1);
994
+ }
995
+ });
996
+ intelligence
997
+ .command('find-related')
998
+ .description('Find memories related to a source memory')
999
+ .argument('<memory-id>', 'Source memory ID')
1000
+ .option('--limit <number>', 'Maximum related memories', '5')
1001
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
1002
+ .option('--json', 'Output raw JSON payload')
1003
+ .action(async (memoryId, options) => {
1004
+ try {
1005
+ const spinner = ora('Finding related memories...').start();
1006
+ const transport = await createIntelligenceTransport();
1007
+ const userId = await resolveCurrentUserId();
1008
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/find-related', {
1009
+ memory_id: memoryId,
1010
+ user_id: userId,
1011
+ limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
1012
+ similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
1013
+ response_format: 'json',
1014
+ });
1015
+ spinner.stop();
1016
+ printIntelligenceResult('🔗 Related Memories', result, options);
1017
+ }
1018
+ catch (error) {
1019
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1020
+ console.error(chalk.red('✖ Failed to find related memories:'), errorMessage);
1021
+ process.exit(1);
1022
+ }
1023
+ });
1024
+ intelligence
1025
+ .command('detect-duplicates')
1026
+ .description('Detect duplicate memory entries')
1027
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.88')
1028
+ .option('--max-pairs <number>', 'Maximum duplicate pairs to inspect', '100')
1029
+ .option('--json', 'Output raw JSON payload')
1030
+ .action(async (options) => {
1031
+ try {
1032
+ const spinner = ora('Detecting duplicates...').start();
1033
+ const transport = await createIntelligenceTransport();
1034
+ const userId = await resolveCurrentUserId();
1035
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/detect-duplicates', {
1036
+ user_id: userId,
1037
+ similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.88'))),
1038
+ max_pairs: Math.max(10, Math.min(500, parseInt(options.maxPairs || '100', 10))),
1039
+ response_format: 'json',
1040
+ });
1041
+ spinner.stop();
1042
+ printIntelligenceResult('🧬 Duplicate Detection', result, options);
1043
+ }
1044
+ catch (error) {
1045
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1046
+ console.error(chalk.red('✖ Failed to detect duplicates:'), errorMessage);
1047
+ process.exit(1);
1048
+ }
1049
+ });
1050
+ intelligence
1051
+ .command('extract-insights')
1052
+ .description('Extract insights from memory collection')
1053
+ .option('--topic <topic>', 'Optional topic filter')
1054
+ .option('--type <type>', `Optional memory type filter (${MEMORY_TYPE_CHOICES.join(', ')})`)
1055
+ .option('--max-memories <number>', 'Maximum memories to analyze', '50')
1056
+ .option('--json', 'Output raw JSON payload')
1057
+ .action(async (options) => {
1058
+ try {
1059
+ const spinner = ora('Extracting insights...').start();
1060
+ const transport = await createIntelligenceTransport();
1061
+ const userId = await resolveCurrentUserId();
1062
+ const memoryType = options.type ? coerceMemoryType(options.type) : undefined;
1063
+ if (options.type && !memoryType) {
1064
+ throw new Error(`Invalid type "${options.type}". Expected one of: ${MEMORY_TYPE_CHOICES.join(', ')}`);
1065
+ }
1066
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/extract-insights', {
1067
+ user_id: userId,
1068
+ topic: options.topic,
1069
+ memory_type: memoryType,
1070
+ max_memories: Math.max(5, Math.min(200, parseInt(options.maxMemories || '50', 10))),
1071
+ response_format: 'json',
1072
+ });
1073
+ spinner.stop();
1074
+ printIntelligenceResult('💡 Memory Insights', result, options);
1075
+ }
1076
+ catch (error) {
1077
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1078
+ console.error(chalk.red('✖ Failed to extract insights:'), errorMessage);
1079
+ process.exit(1);
1080
+ }
1081
+ });
1082
+ intelligence
1083
+ .command('analyze-patterns')
1084
+ .description('Analyze memory usage patterns')
1085
+ .option('--days <number>', 'Days to include in analysis', '30')
1086
+ .option('--json', 'Output raw JSON payload')
1087
+ .action(async (options) => {
1088
+ try {
1089
+ const spinner = ora('Analyzing memory patterns...').start();
1090
+ const transport = await createIntelligenceTransport();
1091
+ const userId = await resolveCurrentUserId();
1092
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/analyze-patterns', {
1093
+ user_id: userId,
1094
+ time_range_days: Math.max(1, Math.min(365, parseInt(options.days || '30', 10))),
1095
+ response_format: 'json',
1096
+ });
1097
+ spinner.stop();
1098
+ printIntelligenceResult('📈 Pattern Analysis', result, options);
1099
+ }
1100
+ catch (error) {
1101
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1102
+ console.error(chalk.red('✖ Failed to analyze patterns:'), errorMessage);
1103
+ process.exit(1);
1104
+ }
1105
+ });
1106
+ // Behavior commands powered by @lanonasis/mem-intel-sdk
1107
+ const behavior = program
1108
+ .command('behavior')
1109
+ .description('Behavior pattern intelligence operations');
1110
+ behavior
1111
+ .command('record')
1112
+ .description('Record a behavior pattern from successful workflow steps')
1113
+ .requiredOption('--trigger <text>', 'Behavior trigger description')
1114
+ .requiredOption('--final-outcome <result>', 'Final outcome: success | partial | failed')
1115
+ .requiredOption('--actions <json>', 'Actions JSON array. Example: [{"tool":"memory.search","params":{"query":"auth fix"}}]')
1116
+ .option('--context <json>', 'Context JSON object')
1117
+ .option('--confidence <number>', 'Confidence score (0-1)', '0.7')
1118
+ .option('--json', 'Output raw JSON payload')
1119
+ .action(async (options) => {
1120
+ try {
1121
+ const spinner = ora('Recording behavior pattern...').start();
1122
+ const transport = await createIntelligenceTransport();
1123
+ const userId = await resolveCurrentUserId();
1124
+ const parsedActions = parseJsonOption(options.actions, '--actions');
1125
+ const actions = ensureBehaviorActions(parsedActions, '--actions');
1126
+ const parsedContext = parseJsonOption(options.context, '--context');
1127
+ const context = ensureJsonObject(parsedContext, '--context');
1128
+ const finalOutcome = options.finalOutcome;
1129
+ if (!['success', 'partial', 'failed'].includes(finalOutcome)) {
1130
+ throw new Error('--final-outcome must be one of: success, partial, failed');
1131
+ }
1132
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-record', {
1133
+ user_id: userId,
1134
+ trigger: options.trigger,
1135
+ context: context || {},
1136
+ actions,
1137
+ final_outcome: finalOutcome,
1138
+ confidence: Math.max(0, Math.min(1, parseFloat(options.confidence || '0.7'))),
1139
+ });
1140
+ spinner.stop();
1141
+ printIntelligenceResult('🧠 Behavior Recorded', result, options);
1142
+ }
1143
+ catch (error) {
1144
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1145
+ console.error(chalk.red('✖ Failed to record behavior pattern:'), errorMessage);
1146
+ process.exit(1);
1147
+ }
1148
+ });
1149
+ behavior
1150
+ .command('recall')
1151
+ .description('Recall behavior patterns relevant to the current task')
1152
+ .requiredOption('--task <text>', 'Current task description')
1153
+ .option('--context <json>', 'Additional context JSON object')
1154
+ .option('--limit <number>', 'Maximum patterns to return', '5')
1155
+ .option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
1156
+ .option('--json', 'Output raw JSON payload')
1157
+ .action(async (options) => {
1158
+ try {
1159
+ const spinner = ora('Recalling behavior patterns...').start();
1160
+ const transport = await createIntelligenceTransport();
1161
+ const userId = await resolveCurrentUserId();
1162
+ const parsedContext = parseJsonOption(options.context, '--context');
1163
+ const context = ensureJsonObject(parsedContext, '--context') || {};
1164
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-recall', {
1165
+ user_id: userId,
1166
+ context: {
1167
+ ...context,
1168
+ current_task: options.task,
1169
+ },
1170
+ limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
1171
+ similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
1172
+ });
1173
+ spinner.stop();
1174
+ printIntelligenceResult('🔁 Behavior Recall', result, options);
1175
+ }
1176
+ catch (error) {
1177
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1178
+ console.error(chalk.red('✖ Failed to recall behavior patterns:'), errorMessage);
1179
+ process.exit(1);
1180
+ }
1181
+ });
1182
+ behavior
1183
+ .command('suggest')
1184
+ .description('Suggest next actions from learned behavior patterns')
1185
+ .requiredOption('--task <text>', 'Current task description')
1186
+ .option('--state <json>', 'Additional current state JSON object')
1187
+ .option('--completed-steps <json>', 'Completed steps JSON array')
1188
+ .option('--max-suggestions <number>', 'Maximum suggestions', '3')
1189
+ .option('--json', 'Output raw JSON payload')
1190
+ .action(async (options) => {
1191
+ try {
1192
+ const spinner = ora('Generating behavior suggestions...').start();
1193
+ const transport = await createIntelligenceTransport();
1194
+ const userId = await resolveCurrentUserId();
1195
+ const parsedState = parseJsonOption(options.state, '--state');
1196
+ const state = ensureJsonObject(parsedState, '--state') || {};
1197
+ const parsedCompletedSteps = parseJsonOption(options.completedSteps, '--completed-steps');
1198
+ const completedSteps = parsedCompletedSteps === undefined
1199
+ ? undefined
1200
+ : ensureBehaviorActions(parsedCompletedSteps, '--completed-steps', { allowEmpty: true });
1201
+ const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-suggest', {
1202
+ user_id: userId,
1203
+ current_state: {
1204
+ ...state,
1205
+ task_description: options.task,
1206
+ completed_steps: completedSteps,
1207
+ },
1208
+ max_suggestions: Math.max(1, Math.min(10, parseInt(options.maxSuggestions || '3', 10))),
1209
+ });
1210
+ spinner.stop();
1211
+ printIntelligenceResult('🎯 Behavior Suggestions', result, options);
1212
+ }
1213
+ catch (error) {
1214
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1215
+ console.error(chalk.red('✖ Failed to suggest actions:'), errorMessage);
1216
+ process.exit(1);
1217
+ }
1218
+ });
709
1219
  }
@@ -73,7 +73,7 @@ export class WelcomeExperience {
73
73
  const choices = isAuthenticated ? existingUserOptions : newUserOptions;
74
74
  const { choice } = await inquirer.prompt([
75
75
  {
76
- type: 'list',
76
+ type: 'select',
77
77
  name: 'choice',
78
78
  message: isAuthenticated ?
79
79
  'Welcome back! What would you like to do?' :
@@ -229,7 +229,7 @@ export class InteractiveSetup {
229
229
  console.log("Let's connect to your Onasis service\n");
230
230
  const { connectionType } = await inquirer.prompt([
231
231
  {
232
- type: 'list',
232
+ type: 'select',
233
233
  name: 'connectionType',
234
234
  message: 'Where is your Onasis service hosted?',
235
235
  choices: [
@@ -291,7 +291,7 @@ export class InteractiveSetup {
291
291
  };
292
292
  const { authMethod } = await inquirer.prompt([
293
293
  {
294
- type: 'list',
294
+ type: 'select',
295
295
  name: 'authMethod',
296
296
  message: 'Select authentication method:',
297
297
  choices: [
@@ -395,7 +395,7 @@ export class InteractiveSetup {
395
395
  console.log("Let's personalize your experience\n");
396
396
  const answers = await inquirer.prompt([
397
397
  {
398
- type: 'list',
398
+ type: 'select',
399
399
  name: 'outputFormat',
400
400
  message: 'Preferred output format:',
401
401
  choices: [