@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 +18 -0
- package/dist/commands/api-keys.js +7 -7
- package/dist/commands/auth.js +2 -2
- package/dist/commands/completion.js +10 -0
- package/dist/commands/guide.js +3 -3
- package/dist/commands/mcp.js +78 -11
- package/dist/commands/memory.js +544 -11
- package/dist/core/dashboard.js +4 -4
- package/dist/index.js +8 -1
- package/dist/mcp/schemas/tool-schemas.js +1 -1
- package/dist/mcp/server/lanonasis-server.js +2 -2
- package/dist/mcp-server-entry.js +0 -0
- package/dist/utils/api.d.ts +22 -1
- package/dist/utils/api.js +198 -12
- package/dist/utils/config.d.ts +10 -5
- package/dist/utils/config.js +66 -32
- package/dist/utils/mcp-client.d.ts +2 -0
- package/dist/utils/mcp-client.js +74 -46
- package/package.json +3 -2
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,208 @@ 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
|
+
const authMethod = config.getAuthMethod();
|
|
213
|
+
// When vendor_key is explicitly set, skip JWT even if a token exists in storage.
|
|
214
|
+
// JWT validates against auth-gateway's DB; vendor keys validate against mcp-core's DB.
|
|
215
|
+
if (authToken && authMethod !== 'vendor_key') {
|
|
216
|
+
return {
|
|
217
|
+
mode: 'sdk',
|
|
218
|
+
client: new MemoryIntelligenceClient({
|
|
219
|
+
apiUrl,
|
|
220
|
+
authToken,
|
|
221
|
+
authType: 'bearer',
|
|
222
|
+
allowMissingAuth: false,
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (apiKey) {
|
|
227
|
+
if (apiKey.startsWith('lano_') || apiKey.startsWith('lms_')) {
|
|
228
|
+
return {
|
|
229
|
+
mode: 'sdk',
|
|
230
|
+
client: new MemoryIntelligenceClient({
|
|
231
|
+
apiUrl,
|
|
232
|
+
apiKey,
|
|
233
|
+
authType: 'apiKey',
|
|
234
|
+
allowMissingAuth: false,
|
|
235
|
+
}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// Legacy key path: use CLI API client auth middleware directly.
|
|
239
|
+
return { mode: 'api' };
|
|
240
|
+
}
|
|
241
|
+
throw new Error('Authentication required. Run "lanonasis auth login" first.');
|
|
242
|
+
};
|
|
243
|
+
const printIntelligenceResult = (title, payload, options) => {
|
|
244
|
+
if (options.json) {
|
|
245
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
console.log(chalk.cyan.bold(`\n${title}`));
|
|
249
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
250
|
+
};
|
|
251
|
+
const postIntelligenceEndpoint = async (transport, endpoint, payload) => {
|
|
252
|
+
if (transport.mode === 'sdk') {
|
|
253
|
+
if (!transport.client) {
|
|
254
|
+
throw new Error('SDK transport is not initialized');
|
|
255
|
+
}
|
|
256
|
+
const response = await transport.client.getHttpClient().postEnhanced(endpoint, payload);
|
|
257
|
+
if (response.error) {
|
|
258
|
+
throw new Error(response.error.message || `Request failed for ${endpoint}`);
|
|
259
|
+
}
|
|
260
|
+
return response.data;
|
|
261
|
+
}
|
|
262
|
+
return await apiClient.post(`/api/v1${endpoint}`, payload);
|
|
263
|
+
};
|
|
60
264
|
export function memoryCommands(program) {
|
|
61
265
|
// Create memory
|
|
62
266
|
program
|
|
@@ -123,7 +327,7 @@ export function memoryCommands(program) {
|
|
|
123
327
|
validate: (input) => input.length > 0 || 'Title is required',
|
|
124
328
|
},
|
|
125
329
|
{
|
|
126
|
-
type: '
|
|
330
|
+
type: 'select',
|
|
127
331
|
name: 'type',
|
|
128
332
|
message: 'Memory type:',
|
|
129
333
|
choices: [...MEMORY_TYPE_CHOICES],
|
|
@@ -469,17 +673,23 @@ export function memoryCommands(program) {
|
|
|
469
673
|
program
|
|
470
674
|
.command('search')
|
|
471
675
|
.description('Search memories using semantic search')
|
|
472
|
-
.argument('<query
|
|
676
|
+
.argument('<query...>', 'search query')
|
|
473
677
|
.option('-l, --limit <limit>', 'number of results', '20')
|
|
474
|
-
.option('--threshold <threshold>', 'similarity threshold (0-1)', '0.
|
|
678
|
+
.option('--threshold <threshold>', 'similarity threshold (0-1)', '0.55')
|
|
475
679
|
.option('--type <types>', 'filter by memory types (comma-separated)')
|
|
476
680
|
.option('--tags <tags>', 'filter by tags (comma-separated)')
|
|
477
|
-
.action(async (
|
|
681
|
+
.action(async (queryParts, options) => {
|
|
478
682
|
try {
|
|
683
|
+
const query = Array.isArray(queryParts) ? queryParts.join(' ').trim() : String(queryParts || '').trim();
|
|
684
|
+
if (!query) {
|
|
685
|
+
console.error(chalk.red('✖ Search query is required'));
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
479
688
|
const spinner = ora(`Searching for "${query}"...`).start();
|
|
689
|
+
const requestedThreshold = clampThreshold(parseFloat(options.threshold || '0.55'));
|
|
480
690
|
const searchOptions = {
|
|
481
691
|
limit: parseInt(options.limit || '20'),
|
|
482
|
-
threshold:
|
|
692
|
+
threshold: requestedThreshold
|
|
483
693
|
};
|
|
484
694
|
if (options.type) {
|
|
485
695
|
searchOptions.memory_types = options.type.split(',').map((t) => t.trim());
|
|
@@ -487,18 +697,67 @@ export function memoryCommands(program) {
|
|
|
487
697
|
if (options.tags) {
|
|
488
698
|
searchOptions.tags = options.tags.split(',').map((t) => t.trim());
|
|
489
699
|
}
|
|
490
|
-
const
|
|
700
|
+
const thresholdPlan = buildSearchThresholdPlan(requestedThreshold, Boolean(searchOptions.tags?.length));
|
|
701
|
+
let result = null;
|
|
702
|
+
let results = [];
|
|
703
|
+
let thresholdUsed = requestedThreshold;
|
|
704
|
+
let searchStrategy = 'semantic';
|
|
705
|
+
let semanticSearchError = null;
|
|
706
|
+
for (const threshold of thresholdPlan) {
|
|
707
|
+
try {
|
|
708
|
+
const attempt = await apiClient.searchMemories(query, {
|
|
709
|
+
...searchOptions,
|
|
710
|
+
threshold,
|
|
711
|
+
});
|
|
712
|
+
const attemptResults = (attempt.results || attempt.data || []);
|
|
713
|
+
result = attempt;
|
|
714
|
+
if (attemptResults.length > 0) {
|
|
715
|
+
results = attemptResults;
|
|
716
|
+
thresholdUsed = threshold;
|
|
717
|
+
const attemptStrategy = attempt.search_strategy;
|
|
718
|
+
searchStrategy = typeof attemptStrategy === 'string'
|
|
719
|
+
? attemptStrategy
|
|
720
|
+
: 'semantic';
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
semanticSearchError = error instanceof Error ? error.message : 'Unknown semantic search error';
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (results.length === 0) {
|
|
730
|
+
const lexicalResults = await lexicalFallbackSearch(query, searchOptions);
|
|
731
|
+
if (lexicalResults.length > 0) {
|
|
732
|
+
results = lexicalResults;
|
|
733
|
+
searchStrategy = 'cli_lexical_fallback';
|
|
734
|
+
}
|
|
735
|
+
}
|
|
491
736
|
spinner.stop();
|
|
492
|
-
const results = result.results || result.data || [];
|
|
493
737
|
if (results.length === 0) {
|
|
494
738
|
console.log(chalk.yellow('No memories found matching your search'));
|
|
739
|
+
if (semanticSearchError) {
|
|
740
|
+
console.log(chalk.gray(`Semantic search error: ${semanticSearchError}`));
|
|
741
|
+
}
|
|
742
|
+
console.log(chalk.gray(`Tried thresholds: ${thresholdPlan.map((t) => t.toFixed(2)).join(', ')}`));
|
|
495
743
|
return;
|
|
496
744
|
}
|
|
497
|
-
|
|
498
|
-
|
|
745
|
+
if (semanticSearchError && searchStrategy === 'cli_lexical_fallback') {
|
|
746
|
+
console.log(chalk.yellow(`⚠ Semantic search unavailable, using lexical fallback (${semanticSearchError})`));
|
|
747
|
+
}
|
|
748
|
+
const totalResults = typeof result?.total_results === 'number' ? result.total_results : results.length;
|
|
749
|
+
const searchTimeMs = typeof result?.search_time_ms === 'number' ? result.search_time_ms : 0;
|
|
750
|
+
console.log(chalk.blue.bold(`\n🔍 Search Results (${totalResults} found)`));
|
|
751
|
+
console.log(chalk.gray(`Query: "${query}" | Search time: ${searchTimeMs}ms`));
|
|
752
|
+
if (Math.abs(thresholdUsed - requestedThreshold) > 0.0001) {
|
|
753
|
+
console.log(chalk.gray(`No matches at ${requestedThreshold.toFixed(2)}; used adaptive threshold ${thresholdUsed.toFixed(2)}`));
|
|
754
|
+
}
|
|
755
|
+
if (searchStrategy) {
|
|
756
|
+
console.log(chalk.gray(`Search strategy: ${searchStrategy}`));
|
|
757
|
+
}
|
|
499
758
|
console.log();
|
|
500
759
|
results.forEach((memory, index) => {
|
|
501
|
-
const score = (memory.
|
|
760
|
+
const score = (memory.similarity_score * 100).toFixed(1);
|
|
502
761
|
console.log(chalk.green(`${index + 1}. ${memory.title}`) + chalk.gray(` (${score}% match)`));
|
|
503
762
|
console.log(chalk.white(` ${truncateText(memory.content, 100)}`));
|
|
504
763
|
console.log(chalk.cyan(` ID: ${memory.id}`) + chalk.gray(` | Type: ${memory.memory_type}`));
|
|
@@ -584,7 +843,7 @@ export function memoryCommands(program) {
|
|
|
584
843
|
default: currentMemory.title,
|
|
585
844
|
},
|
|
586
845
|
{
|
|
587
|
-
type: '
|
|
846
|
+
type: 'select',
|
|
588
847
|
name: 'type',
|
|
589
848
|
message: 'Memory type:',
|
|
590
849
|
choices: [...MEMORY_TYPE_CHOICES],
|
|
@@ -706,4 +965,278 @@ export function memoryCommands(program) {
|
|
|
706
965
|
process.exit(1);
|
|
707
966
|
}
|
|
708
967
|
});
|
|
968
|
+
// Intelligence commands powered by @lanonasis/mem-intel-sdk
|
|
969
|
+
const intelligence = program
|
|
970
|
+
.command('intelligence')
|
|
971
|
+
.description('Memory intelligence operations');
|
|
972
|
+
intelligence
|
|
973
|
+
.command('health-check')
|
|
974
|
+
.description('Run memory intelligence health check')
|
|
975
|
+
.option('--json', 'Output raw JSON payload')
|
|
976
|
+
.action(async (options) => {
|
|
977
|
+
try {
|
|
978
|
+
const spinner = ora('Running intelligence health check...').start();
|
|
979
|
+
const transport = await createIntelligenceTransport();
|
|
980
|
+
const userId = await resolveCurrentUserId();
|
|
981
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', { user_id: userId, response_format: 'json' });
|
|
982
|
+
spinner.stop();
|
|
983
|
+
printIntelligenceResult('🩺 Intelligence Health Check', result, options);
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
987
|
+
console.error(chalk.red('✖ Intelligence health check failed:'), errorMessage);
|
|
988
|
+
process.exit(1);
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
intelligence
|
|
992
|
+
.command('suggest-tags')
|
|
993
|
+
.description('Suggest tags for a memory')
|
|
994
|
+
.argument('<memory-id>', 'Memory ID')
|
|
995
|
+
.option('--max <number>', 'Maximum suggestions', '8')
|
|
996
|
+
.option('--json', 'Output raw JSON payload')
|
|
997
|
+
.action(async (memoryId, options) => {
|
|
998
|
+
try {
|
|
999
|
+
const spinner = ora('Generating tag suggestions...').start();
|
|
1000
|
+
const transport = await createIntelligenceTransport();
|
|
1001
|
+
const userId = await resolveCurrentUserId();
|
|
1002
|
+
const maxSuggestions = Math.max(1, Math.min(20, parseInt(options.max || '8', 10)));
|
|
1003
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/suggest-tags', {
|
|
1004
|
+
memory_id: memoryId,
|
|
1005
|
+
user_id: userId,
|
|
1006
|
+
max_suggestions: maxSuggestions,
|
|
1007
|
+
include_existing_tags: true,
|
|
1008
|
+
response_format: 'json',
|
|
1009
|
+
});
|
|
1010
|
+
spinner.stop();
|
|
1011
|
+
printIntelligenceResult('🏷️ Tag Suggestions', result, options);
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1015
|
+
console.error(chalk.red('✖ Failed to suggest tags:'), errorMessage);
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
intelligence
|
|
1020
|
+
.command('find-related')
|
|
1021
|
+
.description('Find memories related to a source memory')
|
|
1022
|
+
.argument('<memory-id>', 'Source memory ID')
|
|
1023
|
+
.option('--limit <number>', 'Maximum related memories', '5')
|
|
1024
|
+
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
|
|
1025
|
+
.option('--json', 'Output raw JSON payload')
|
|
1026
|
+
.action(async (memoryId, options) => {
|
|
1027
|
+
try {
|
|
1028
|
+
const spinner = ora('Finding related memories...').start();
|
|
1029
|
+
const transport = await createIntelligenceTransport();
|
|
1030
|
+
const userId = await resolveCurrentUserId();
|
|
1031
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/find-related', {
|
|
1032
|
+
memory_id: memoryId,
|
|
1033
|
+
user_id: userId,
|
|
1034
|
+
limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
|
|
1035
|
+
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
|
|
1036
|
+
response_format: 'json',
|
|
1037
|
+
});
|
|
1038
|
+
spinner.stop();
|
|
1039
|
+
printIntelligenceResult('🔗 Related Memories', result, options);
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1043
|
+
console.error(chalk.red('✖ Failed to find related memories:'), errorMessage);
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
intelligence
|
|
1048
|
+
.command('detect-duplicates')
|
|
1049
|
+
.description('Detect duplicate memory entries')
|
|
1050
|
+
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.88')
|
|
1051
|
+
.option('--max-pairs <number>', 'Maximum duplicate pairs to inspect', '100')
|
|
1052
|
+
.option('--json', 'Output raw JSON payload')
|
|
1053
|
+
.action(async (options) => {
|
|
1054
|
+
try {
|
|
1055
|
+
const spinner = ora('Detecting duplicates...').start();
|
|
1056
|
+
const transport = await createIntelligenceTransport();
|
|
1057
|
+
const userId = await resolveCurrentUserId();
|
|
1058
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/detect-duplicates', {
|
|
1059
|
+
user_id: userId,
|
|
1060
|
+
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.88'))),
|
|
1061
|
+
max_pairs: Math.max(10, Math.min(500, parseInt(options.maxPairs || '100', 10))),
|
|
1062
|
+
response_format: 'json',
|
|
1063
|
+
});
|
|
1064
|
+
spinner.stop();
|
|
1065
|
+
printIntelligenceResult('🧬 Duplicate Detection', result, options);
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1069
|
+
console.error(chalk.red('✖ Failed to detect duplicates:'), errorMessage);
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
intelligence
|
|
1074
|
+
.command('extract-insights')
|
|
1075
|
+
.description('Extract insights from memory collection')
|
|
1076
|
+
.option('--topic <topic>', 'Optional topic filter')
|
|
1077
|
+
.option('--type <type>', `Optional memory type filter (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1078
|
+
.option('--max-memories <number>', 'Maximum memories to analyze', '50')
|
|
1079
|
+
.option('--json', 'Output raw JSON payload')
|
|
1080
|
+
.action(async (options) => {
|
|
1081
|
+
try {
|
|
1082
|
+
const spinner = ora('Extracting insights...').start();
|
|
1083
|
+
const transport = await createIntelligenceTransport();
|
|
1084
|
+
const userId = await resolveCurrentUserId();
|
|
1085
|
+
const memoryType = options.type ? coerceMemoryType(options.type) : undefined;
|
|
1086
|
+
if (options.type && !memoryType) {
|
|
1087
|
+
throw new Error(`Invalid type "${options.type}". Expected one of: ${MEMORY_TYPE_CHOICES.join(', ')}`);
|
|
1088
|
+
}
|
|
1089
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/extract-insights', {
|
|
1090
|
+
user_id: userId,
|
|
1091
|
+
topic: options.topic,
|
|
1092
|
+
memory_type: memoryType,
|
|
1093
|
+
max_memories: Math.max(5, Math.min(200, parseInt(options.maxMemories || '50', 10))),
|
|
1094
|
+
response_format: 'json',
|
|
1095
|
+
});
|
|
1096
|
+
spinner.stop();
|
|
1097
|
+
printIntelligenceResult('💡 Memory Insights', result, options);
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1101
|
+
console.error(chalk.red('✖ Failed to extract insights:'), errorMessage);
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
intelligence
|
|
1106
|
+
.command('analyze-patterns')
|
|
1107
|
+
.description('Analyze memory usage patterns')
|
|
1108
|
+
.option('--days <number>', 'Days to include in analysis', '30')
|
|
1109
|
+
.option('--json', 'Output raw JSON payload')
|
|
1110
|
+
.action(async (options) => {
|
|
1111
|
+
try {
|
|
1112
|
+
const spinner = ora('Analyzing memory patterns...').start();
|
|
1113
|
+
const transport = await createIntelligenceTransport();
|
|
1114
|
+
const userId = await resolveCurrentUserId();
|
|
1115
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/analyze-patterns', {
|
|
1116
|
+
user_id: userId,
|
|
1117
|
+
time_range_days: Math.max(1, Math.min(365, parseInt(options.days || '30', 10))),
|
|
1118
|
+
response_format: 'json',
|
|
1119
|
+
});
|
|
1120
|
+
spinner.stop();
|
|
1121
|
+
printIntelligenceResult('📈 Pattern Analysis', result, options);
|
|
1122
|
+
}
|
|
1123
|
+
catch (error) {
|
|
1124
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1125
|
+
console.error(chalk.red('✖ Failed to analyze patterns:'), errorMessage);
|
|
1126
|
+
process.exit(1);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
// Behavior commands powered by @lanonasis/mem-intel-sdk
|
|
1130
|
+
const behavior = program
|
|
1131
|
+
.command('behavior')
|
|
1132
|
+
.description('Behavior pattern intelligence operations');
|
|
1133
|
+
behavior
|
|
1134
|
+
.command('record')
|
|
1135
|
+
.description('Record a behavior pattern from successful workflow steps')
|
|
1136
|
+
.requiredOption('--trigger <text>', 'Behavior trigger description')
|
|
1137
|
+
.requiredOption('--final-outcome <result>', 'Final outcome: success | partial | failed')
|
|
1138
|
+
.requiredOption('--actions <json>', 'Actions JSON array. Example: [{"tool":"memory.search","params":{"query":"auth fix"}}]')
|
|
1139
|
+
.option('--context <json>', 'Context JSON object')
|
|
1140
|
+
.option('--confidence <number>', 'Confidence score (0-1)', '0.7')
|
|
1141
|
+
.option('--json', 'Output raw JSON payload')
|
|
1142
|
+
.action(async (options) => {
|
|
1143
|
+
try {
|
|
1144
|
+
const spinner = ora('Recording behavior pattern...').start();
|
|
1145
|
+
const transport = await createIntelligenceTransport();
|
|
1146
|
+
const userId = await resolveCurrentUserId();
|
|
1147
|
+
const parsedActions = parseJsonOption(options.actions, '--actions');
|
|
1148
|
+
const actions = ensureBehaviorActions(parsedActions, '--actions');
|
|
1149
|
+
const parsedContext = parseJsonOption(options.context, '--context');
|
|
1150
|
+
const context = ensureJsonObject(parsedContext, '--context');
|
|
1151
|
+
const finalOutcome = options.finalOutcome;
|
|
1152
|
+
if (!['success', 'partial', 'failed'].includes(finalOutcome)) {
|
|
1153
|
+
throw new Error('--final-outcome must be one of: success, partial, failed');
|
|
1154
|
+
}
|
|
1155
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-record', {
|
|
1156
|
+
user_id: userId,
|
|
1157
|
+
trigger: options.trigger,
|
|
1158
|
+
context: context || {},
|
|
1159
|
+
actions,
|
|
1160
|
+
final_outcome: finalOutcome,
|
|
1161
|
+
confidence: Math.max(0, Math.min(1, parseFloat(options.confidence || '0.7'))),
|
|
1162
|
+
});
|
|
1163
|
+
spinner.stop();
|
|
1164
|
+
printIntelligenceResult('🧠 Behavior Recorded', result, options);
|
|
1165
|
+
}
|
|
1166
|
+
catch (error) {
|
|
1167
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1168
|
+
console.error(chalk.red('✖ Failed to record behavior pattern:'), errorMessage);
|
|
1169
|
+
process.exit(1);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
behavior
|
|
1173
|
+
.command('recall')
|
|
1174
|
+
.description('Recall behavior patterns relevant to the current task')
|
|
1175
|
+
.requiredOption('--task <text>', 'Current task description')
|
|
1176
|
+
.option('--context <json>', 'Additional context JSON object')
|
|
1177
|
+
.option('--limit <number>', 'Maximum patterns to return', '5')
|
|
1178
|
+
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
|
|
1179
|
+
.option('--json', 'Output raw JSON payload')
|
|
1180
|
+
.action(async (options) => {
|
|
1181
|
+
try {
|
|
1182
|
+
const spinner = ora('Recalling behavior patterns...').start();
|
|
1183
|
+
const transport = await createIntelligenceTransport();
|
|
1184
|
+
const userId = await resolveCurrentUserId();
|
|
1185
|
+
const parsedContext = parseJsonOption(options.context, '--context');
|
|
1186
|
+
const context = ensureJsonObject(parsedContext, '--context') || {};
|
|
1187
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-recall', {
|
|
1188
|
+
user_id: userId,
|
|
1189
|
+
context: {
|
|
1190
|
+
...context,
|
|
1191
|
+
current_task: options.task,
|
|
1192
|
+
},
|
|
1193
|
+
limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
|
|
1194
|
+
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
|
|
1195
|
+
});
|
|
1196
|
+
spinner.stop();
|
|
1197
|
+
printIntelligenceResult('🔁 Behavior Recall', result, options);
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1201
|
+
console.error(chalk.red('✖ Failed to recall behavior patterns:'), errorMessage);
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
behavior
|
|
1206
|
+
.command('suggest')
|
|
1207
|
+
.description('Suggest next actions from learned behavior patterns')
|
|
1208
|
+
.requiredOption('--task <text>', 'Current task description')
|
|
1209
|
+
.option('--state <json>', 'Additional current state JSON object')
|
|
1210
|
+
.option('--completed-steps <json>', 'Completed steps JSON array')
|
|
1211
|
+
.option('--max-suggestions <number>', 'Maximum suggestions', '3')
|
|
1212
|
+
.option('--json', 'Output raw JSON payload')
|
|
1213
|
+
.action(async (options) => {
|
|
1214
|
+
try {
|
|
1215
|
+
const spinner = ora('Generating behavior suggestions...').start();
|
|
1216
|
+
const transport = await createIntelligenceTransport();
|
|
1217
|
+
const userId = await resolveCurrentUserId();
|
|
1218
|
+
const parsedState = parseJsonOption(options.state, '--state');
|
|
1219
|
+
const state = ensureJsonObject(parsedState, '--state') || {};
|
|
1220
|
+
const parsedCompletedSteps = parseJsonOption(options.completedSteps, '--completed-steps');
|
|
1221
|
+
const completedSteps = parsedCompletedSteps === undefined
|
|
1222
|
+
? undefined
|
|
1223
|
+
: ensureBehaviorActions(parsedCompletedSteps, '--completed-steps', { allowEmpty: true });
|
|
1224
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-suggest', {
|
|
1225
|
+
user_id: userId,
|
|
1226
|
+
current_state: {
|
|
1227
|
+
...state,
|
|
1228
|
+
task_description: options.task,
|
|
1229
|
+
completed_steps: completedSteps,
|
|
1230
|
+
},
|
|
1231
|
+
max_suggestions: Math.max(1, Math.min(10, parseInt(options.maxSuggestions || '3', 10))),
|
|
1232
|
+
});
|
|
1233
|
+
spinner.stop();
|
|
1234
|
+
printIntelligenceResult('🎯 Behavior Suggestions', result, options);
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1238
|
+
console.error(chalk.red('✖ Failed to suggest actions:'), errorMessage);
|
|
1239
|
+
process.exit(1);
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
709
1242
|
}
|
package/dist/core/dashboard.js
CHANGED
|
@@ -280,7 +280,7 @@ export class InteractiveMemoryCreator {
|
|
|
280
280
|
console.log(chalk.cyan(`📎 I noticed this looks like ${suggestions.contentType}. Would you like to:`));
|
|
281
281
|
const { topicChoice } = await inquirer.prompt([
|
|
282
282
|
{
|
|
283
|
-
type: '
|
|
283
|
+
type: 'select',
|
|
284
284
|
name: 'topicChoice',
|
|
285
285
|
message: 'Select topic:',
|
|
286
286
|
choices: [
|
|
@@ -336,7 +336,7 @@ export class InteractiveMemoryCreator {
|
|
|
336
336
|
// Memory type selection
|
|
337
337
|
const { memoryType } = await inquirer.prompt([
|
|
338
338
|
{
|
|
339
|
-
type: '
|
|
339
|
+
type: 'select',
|
|
340
340
|
name: 'memoryType',
|
|
341
341
|
message: 'Memory Type:',
|
|
342
342
|
choices: [
|
|
@@ -358,7 +358,7 @@ export class InteractiveMemoryCreator {
|
|
|
358
358
|
// Confirm save
|
|
359
359
|
const { action } = await inquirer.prompt([
|
|
360
360
|
{
|
|
361
|
-
type: '
|
|
361
|
+
type: 'select',
|
|
362
362
|
name: 'action',
|
|
363
363
|
message: 'Ready to save?',
|
|
364
364
|
choices: [
|
|
@@ -459,7 +459,7 @@ export class InteractiveSearch {
|
|
|
459
459
|
// Result actions
|
|
460
460
|
const { action } = await inquirer.prompt([
|
|
461
461
|
{
|
|
462
|
-
type: '
|
|
462
|
+
type: 'select',
|
|
463
463
|
name: 'action',
|
|
464
464
|
message: 'Actions:',
|
|
465
465
|
choices: [
|
package/dist/index.js
CHANGED
|
@@ -91,7 +91,14 @@ program
|
|
|
91
91
|
actionCommand.parent?.name?.() === 'mcp-server';
|
|
92
92
|
const isConfigFlow = actionCommand.name() === 'config' ||
|
|
93
93
|
actionCommand.parent?.name?.() === 'config';
|
|
94
|
-
|
|
94
|
+
// Memory, topic, org, and key commands use the direct REST API — skip MCP auto-connect
|
|
95
|
+
const isDirectApiFlow = actionCommand.name() === 'memory' ||
|
|
96
|
+
actionCommand.parent?.name?.() === 'memory' ||
|
|
97
|
+
actionCommand.name() === 'topic' ||
|
|
98
|
+
actionCommand.parent?.name?.() === 'topic' ||
|
|
99
|
+
actionCommand.name() === 'org' ||
|
|
100
|
+
actionCommand.parent?.name?.() === 'org';
|
|
101
|
+
if (!forceDirectApi && !isMcpFlow && !isConfigFlow && !isDirectApiFlow && !['init', 'auth', 'login', 'health', 'status'].includes(actionCommand.name())) {
|
|
95
102
|
try {
|
|
96
103
|
const client = getMCPClient();
|
|
97
104
|
if (!client.isConnectedToServer()) {
|