@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.
- package/CHANGELOG.md +52 -0
- package/README.md +20 -4
- package/dist/commands/completion.js +10 -0
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/memory.js +515 -5
- package/dist/core/welcome.js +4 -4
- package/dist/index.js +117 -12
- package/dist/mcp/schemas/tool-schemas.d.ts +186 -538
- package/dist/mcp/schemas/tool-schemas.js +8 -8
- package/dist/mcp/server/lanonasis-server.js +2 -2
- package/dist/utils/api.d.ts +34 -1
- package/dist/utils/api.js +56 -1
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.js +53 -16
- package/package.json +23 -22
package/dist/commands/memory.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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
|
|
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.
|
|
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
|
}
|
package/dist/core/welcome.js
CHANGED
|
@@ -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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
398
|
+
type: 'select',
|
|
399
399
|
name: 'outputFormat',
|
|
400
400
|
message: 'Preferred output format:',
|
|
401
401
|
choices: [
|