@mem0/cli 0.1.0
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/README.md +75 -0
- package/development.md +91 -0
- package/dist/chunk-EJ5AQPMT.js +120 -0
- package/dist/chunk-I7ABQZUR.js +111 -0
- package/dist/chunk-J7DYZDMM.js +187 -0
- package/dist/chunk-O3XZVUUX.js +252 -0
- package/dist/config-WKOCXNAS.js +86 -0
- package/dist/entities-XPRXH4X4.js +119 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +523 -0
- package/dist/init-N25QFHYP.js +161 -0
- package/dist/memory-JYJGE4VO.js +387 -0
- package/dist/utils-BAMFZ5H5.js +124 -0
- package/package.json +42 -0
- package/src/backend/base.ts +115 -0
- package/src/backend/index.ts +7 -0
- package/src/backend/platform.ts +303 -0
- package/src/branding.ts +145 -0
- package/src/commands/config.ts +90 -0
- package/src/commands/entities.ts +139 -0
- package/src/commands/init.ts +182 -0
- package/src/commands/memory.ts +487 -0
- package/src/commands/utils.ts +139 -0
- package/src/config.ts +159 -0
- package/src/help.ts +374 -0
- package/src/index.ts +501 -0
- package/src/output.ts +230 -0
- package/tests/branding.test.ts +98 -0
- package/tests/cli-integration.test.ts +156 -0
- package/tests/commands.test.ts +221 -0
- package/tests/config.test.ts +113 -0
- package/tests/output.test.ts +115 -0
- package/tests/setup.ts +75 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract backend interface and factory.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Mem0Config } from "../config.js";
|
|
6
|
+
import { PlatformBackend } from "./platform.js";
|
|
7
|
+
|
|
8
|
+
export interface AddOptions {
|
|
9
|
+
userId?: string;
|
|
10
|
+
agentId?: string;
|
|
11
|
+
appId?: string;
|
|
12
|
+
runId?: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
immutable?: boolean;
|
|
15
|
+
infer?: boolean;
|
|
16
|
+
expires?: string;
|
|
17
|
+
categories?: string[];
|
|
18
|
+
enableGraph?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SearchOptions {
|
|
22
|
+
userId?: string;
|
|
23
|
+
agentId?: string;
|
|
24
|
+
appId?: string;
|
|
25
|
+
runId?: string;
|
|
26
|
+
topK?: number;
|
|
27
|
+
threshold?: number;
|
|
28
|
+
rerank?: boolean;
|
|
29
|
+
keyword?: boolean;
|
|
30
|
+
filters?: Record<string, unknown>;
|
|
31
|
+
fields?: string[];
|
|
32
|
+
enableGraph?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ListOptions {
|
|
36
|
+
userId?: string;
|
|
37
|
+
agentId?: string;
|
|
38
|
+
appId?: string;
|
|
39
|
+
runId?: string;
|
|
40
|
+
page?: number;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
category?: string;
|
|
43
|
+
after?: string;
|
|
44
|
+
before?: string;
|
|
45
|
+
enableGraph?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DeleteOptions {
|
|
49
|
+
all?: boolean;
|
|
50
|
+
userId?: string;
|
|
51
|
+
agentId?: string;
|
|
52
|
+
appId?: string;
|
|
53
|
+
runId?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EntityIds {
|
|
57
|
+
userId?: string;
|
|
58
|
+
agentId?: string;
|
|
59
|
+
appId?: string;
|
|
60
|
+
runId?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Backend {
|
|
64
|
+
add(
|
|
65
|
+
content?: string,
|
|
66
|
+
messages?: Record<string, unknown>[],
|
|
67
|
+
opts?: AddOptions,
|
|
68
|
+
): Promise<Record<string, unknown>>;
|
|
69
|
+
|
|
70
|
+
search(query: string, opts?: SearchOptions): Promise<Record<string, unknown>[]>;
|
|
71
|
+
|
|
72
|
+
get(memoryId: string): Promise<Record<string, unknown>>;
|
|
73
|
+
|
|
74
|
+
listMemories(opts?: ListOptions): Promise<Record<string, unknown>[]>;
|
|
75
|
+
|
|
76
|
+
update(
|
|
77
|
+
memoryId: string,
|
|
78
|
+
content?: string,
|
|
79
|
+
metadata?: Record<string, unknown>,
|
|
80
|
+
): Promise<Record<string, unknown>>;
|
|
81
|
+
|
|
82
|
+
delete(memoryId?: string, opts?: DeleteOptions): Promise<Record<string, unknown>>;
|
|
83
|
+
|
|
84
|
+
deleteEntities(opts: EntityIds): Promise<Record<string, unknown>>;
|
|
85
|
+
|
|
86
|
+
status(opts?: { userId?: string; agentId?: string }): Promise<Record<string, unknown>>;
|
|
87
|
+
|
|
88
|
+
entities(entityType: string): Promise<Record<string, unknown>[]>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class AuthError extends Error {
|
|
92
|
+
constructor(message = "Authentication failed. Your API key may be invalid or expired.") {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "AuthError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class NotFoundError extends Error {
|
|
99
|
+
constructor(path: string) {
|
|
100
|
+
super(`Resource not found: ${path}`);
|
|
101
|
+
this.name = "NotFoundError";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class APIError extends Error {
|
|
106
|
+
constructor(path: string, detail: string) {
|
|
107
|
+
super(`Bad request to ${path}: ${detail}`);
|
|
108
|
+
this.name = "APIError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getBackend(config: Mem0Config): Backend {
|
|
113
|
+
return new PlatformBackend(config.platform);
|
|
114
|
+
}
|
|
115
|
+
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform (SaaS) backend — communicates with api.mem0.ai.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PlatformConfig } from "../config.js";
|
|
6
|
+
import {
|
|
7
|
+
type AddOptions,
|
|
8
|
+
APIError,
|
|
9
|
+
AuthError,
|
|
10
|
+
type Backend,
|
|
11
|
+
type DeleteOptions,
|
|
12
|
+
type EntityIds,
|
|
13
|
+
type ListOptions,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
type SearchOptions,
|
|
16
|
+
} from "./base.js";
|
|
17
|
+
|
|
18
|
+
export class PlatformBackend implements Backend {
|
|
19
|
+
private baseUrl: string;
|
|
20
|
+
private headers: Record<string, string>;
|
|
21
|
+
|
|
22
|
+
constructor(config: PlatformConfig) {
|
|
23
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
24
|
+
this.headers = {
|
|
25
|
+
Authorization: `Token ${config.apiKey}`,
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async _request(
|
|
31
|
+
method: string,
|
|
32
|
+
path: string,
|
|
33
|
+
opts?: { json?: unknown; params?: Record<string, string> },
|
|
34
|
+
): Promise<unknown> {
|
|
35
|
+
let url = `${this.baseUrl}${path}`;
|
|
36
|
+
if (opts?.params) {
|
|
37
|
+
const qs = new URLSearchParams(opts.params).toString();
|
|
38
|
+
url += `?${qs}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const fetchOpts: RequestInit = {
|
|
42
|
+
method,
|
|
43
|
+
headers: this.headers,
|
|
44
|
+
signal: AbortSignal.timeout(30_000),
|
|
45
|
+
};
|
|
46
|
+
if (opts?.json) {
|
|
47
|
+
fetchOpts.body = JSON.stringify(opts.json);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const resp = await fetch(url, fetchOpts);
|
|
51
|
+
|
|
52
|
+
if (resp.status === 401) {
|
|
53
|
+
throw new AuthError();
|
|
54
|
+
}
|
|
55
|
+
if (resp.status === 404) {
|
|
56
|
+
throw new NotFoundError(path);
|
|
57
|
+
}
|
|
58
|
+
if (resp.status === 400) {
|
|
59
|
+
let detail: string;
|
|
60
|
+
try {
|
|
61
|
+
const body = await resp.json();
|
|
62
|
+
detail = (body as Record<string, string>).detail ?? resp.statusText;
|
|
63
|
+
} catch {
|
|
64
|
+
detail = resp.statusText;
|
|
65
|
+
}
|
|
66
|
+
throw new APIError(path, detail);
|
|
67
|
+
}
|
|
68
|
+
if (!resp.ok) {
|
|
69
|
+
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
if (resp.status === 204) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
return resp.json();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async add(
|
|
78
|
+
content?: string,
|
|
79
|
+
messages?: Record<string, unknown>[],
|
|
80
|
+
opts: AddOptions = {},
|
|
81
|
+
): Promise<Record<string, unknown>> {
|
|
82
|
+
const payload: Record<string, unknown> = {};
|
|
83
|
+
|
|
84
|
+
if (messages) {
|
|
85
|
+
payload.messages = messages;
|
|
86
|
+
} else if (content) {
|
|
87
|
+
payload.messages = [{ role: "user", content }];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (opts.userId) payload.user_id = opts.userId;
|
|
91
|
+
if (opts.agentId) payload.agent_id = opts.agentId;
|
|
92
|
+
if (opts.appId) payload.app_id = opts.appId;
|
|
93
|
+
if (opts.runId) payload.run_id = opts.runId;
|
|
94
|
+
if (opts.metadata) payload.metadata = opts.metadata;
|
|
95
|
+
if (opts.immutable) payload.immutable = true;
|
|
96
|
+
if (opts.infer === false) payload.infer = false;
|
|
97
|
+
if (opts.expires) payload.expiration_date = opts.expires;
|
|
98
|
+
if (opts.categories) payload.categories = opts.categories;
|
|
99
|
+
if (opts.enableGraph) payload.enable_graph = true;
|
|
100
|
+
|
|
101
|
+
return (await this._request("POST", "/v1/memories/", { json: payload })) as Record<
|
|
102
|
+
string,
|
|
103
|
+
unknown
|
|
104
|
+
>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private _buildFilters(opts: {
|
|
108
|
+
userId?: string;
|
|
109
|
+
agentId?: string;
|
|
110
|
+
appId?: string;
|
|
111
|
+
runId?: string;
|
|
112
|
+
extraFilters?: Record<string, unknown>;
|
|
113
|
+
}): Record<string, unknown> | undefined {
|
|
114
|
+
// If caller passed a pre-built filter structure, use it directly
|
|
115
|
+
if (opts.extraFilters && ("AND" in opts.extraFilters || "OR" in opts.extraFilters)) {
|
|
116
|
+
return opts.extraFilters;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const andConditions: Record<string, unknown>[] = [];
|
|
120
|
+
if (opts.userId) andConditions.push({ user_id: opts.userId });
|
|
121
|
+
if (opts.agentId) andConditions.push({ agent_id: opts.agentId });
|
|
122
|
+
if (opts.appId) andConditions.push({ app_id: opts.appId });
|
|
123
|
+
if (opts.runId) andConditions.push({ run_id: opts.runId });
|
|
124
|
+
|
|
125
|
+
if (opts.extraFilters) {
|
|
126
|
+
for (const [k, v] of Object.entries(opts.extraFilters)) {
|
|
127
|
+
andConditions.push({ [k]: v });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (andConditions.length === 1) return andConditions[0];
|
|
132
|
+
if (andConditions.length > 1) return { AND: andConditions };
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async search(query: string, opts: SearchOptions = {}): Promise<Record<string, unknown>[]> {
|
|
137
|
+
const payload: Record<string, unknown> = {
|
|
138
|
+
query,
|
|
139
|
+
top_k: opts.topK ?? 10,
|
|
140
|
+
threshold: opts.threshold ?? 0.3,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const apiFilters = this._buildFilters({
|
|
144
|
+
userId: opts.userId,
|
|
145
|
+
agentId: opts.agentId,
|
|
146
|
+
appId: opts.appId,
|
|
147
|
+
runId: opts.runId,
|
|
148
|
+
extraFilters: opts.filters,
|
|
149
|
+
});
|
|
150
|
+
if (apiFilters) payload.filters = apiFilters;
|
|
151
|
+
if (opts.rerank) payload.rerank = true;
|
|
152
|
+
if (opts.keyword) payload.keyword_search = true;
|
|
153
|
+
if (opts.fields) payload.fields = opts.fields;
|
|
154
|
+
if (opts.enableGraph) payload.enable_graph = true;
|
|
155
|
+
|
|
156
|
+
const result = (await this._request("POST", "/v2/memories/search/", {
|
|
157
|
+
json: payload,
|
|
158
|
+
})) as unknown;
|
|
159
|
+
if (Array.isArray(result)) return result;
|
|
160
|
+
const obj = result as Record<string, unknown>;
|
|
161
|
+
return (obj.results ?? obj.memories ?? []) as Record<string, unknown>[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async get(memoryId: string): Promise<Record<string, unknown>> {
|
|
165
|
+
return (await this._request("GET", `/v1/memories/${memoryId}/`)) as Record<string, unknown>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async listMemories(opts: ListOptions = {}): Promise<Record<string, unknown>[]> {
|
|
169
|
+
const payload: Record<string, unknown> = {};
|
|
170
|
+
const params: Record<string, string> = {
|
|
171
|
+
page: String(opts.page ?? 1),
|
|
172
|
+
page_size: String(opts.pageSize ?? 100),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const extra: Record<string, unknown> = {};
|
|
176
|
+
if (opts.category) {
|
|
177
|
+
extra.categories = { contains: opts.category };
|
|
178
|
+
}
|
|
179
|
+
if (opts.after) {
|
|
180
|
+
extra.created_at = { ...(extra.created_at as Record<string, unknown> | undefined), gte: opts.after };
|
|
181
|
+
}
|
|
182
|
+
if (opts.before) {
|
|
183
|
+
extra.created_at = { ...(extra.created_at as Record<string, unknown> | undefined), lte: opts.before };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const apiFilters = this._buildFilters({
|
|
187
|
+
userId: opts.userId,
|
|
188
|
+
agentId: opts.agentId,
|
|
189
|
+
appId: opts.appId,
|
|
190
|
+
runId: opts.runId,
|
|
191
|
+
extraFilters: Object.keys(extra).length > 0 ? extra : undefined,
|
|
192
|
+
});
|
|
193
|
+
if (apiFilters) payload.filters = apiFilters;
|
|
194
|
+
if (opts.enableGraph) payload.enable_graph = true;
|
|
195
|
+
|
|
196
|
+
const result = (await this._request("POST", "/v2/memories/", { json: payload, params })) as unknown;
|
|
197
|
+
if (Array.isArray(result)) return result;
|
|
198
|
+
const obj = result as Record<string, unknown>;
|
|
199
|
+
return (obj.results ?? obj.memories ?? []) as Record<string, unknown>[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async update(
|
|
203
|
+
memoryId: string,
|
|
204
|
+
content?: string,
|
|
205
|
+
metadata?: Record<string, unknown>,
|
|
206
|
+
): Promise<Record<string, unknown>> {
|
|
207
|
+
const payload: Record<string, unknown> = {};
|
|
208
|
+
if (content) payload.text = content;
|
|
209
|
+
if (metadata) payload.metadata = metadata;
|
|
210
|
+
return (await this._request("PUT", `/v1/memories/${memoryId}/`, {
|
|
211
|
+
json: payload,
|
|
212
|
+
})) as Record<string, unknown>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async delete(
|
|
216
|
+
memoryId?: string,
|
|
217
|
+
opts: DeleteOptions = {},
|
|
218
|
+
): Promise<Record<string, unknown>> {
|
|
219
|
+
if (opts.all) {
|
|
220
|
+
const params: Record<string, string> = {};
|
|
221
|
+
if (opts.userId) params.user_id = opts.userId;
|
|
222
|
+
if (opts.agentId) params.agent_id = opts.agentId;
|
|
223
|
+
if (opts.appId) params.app_id = opts.appId;
|
|
224
|
+
if (opts.runId) params.run_id = opts.runId;
|
|
225
|
+
return (await this._request("DELETE", "/v1/memories/", { params })) as Record<
|
|
226
|
+
string,
|
|
227
|
+
unknown
|
|
228
|
+
>;
|
|
229
|
+
}
|
|
230
|
+
if (memoryId) {
|
|
231
|
+
return (await this._request("DELETE", `/v1/memories/${memoryId}/`)) as Record<
|
|
232
|
+
string,
|
|
233
|
+
unknown
|
|
234
|
+
>;
|
|
235
|
+
}
|
|
236
|
+
throw new Error("Either memoryId or --all is required");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async deleteEntities(opts: EntityIds): Promise<Record<string, unknown>> {
|
|
240
|
+
const params: Record<string, string> = {};
|
|
241
|
+
if (opts.userId) params.user_id = opts.userId;
|
|
242
|
+
if (opts.agentId) params.agent_id = opts.agentId;
|
|
243
|
+
if (opts.appId) params.app_id = opts.appId;
|
|
244
|
+
if (opts.runId) params.run_id = opts.runId;
|
|
245
|
+
if (Object.keys(params).length === 0) {
|
|
246
|
+
throw new Error("At least one entity ID is required for deleteEntities.");
|
|
247
|
+
}
|
|
248
|
+
return (await this._request("DELETE", "/v1/entities/", { params })) as Record<
|
|
249
|
+
string,
|
|
250
|
+
unknown
|
|
251
|
+
>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async status(
|
|
255
|
+
opts: { userId?: string; agentId?: string } = {},
|
|
256
|
+
): Promise<Record<string, unknown>> {
|
|
257
|
+
try {
|
|
258
|
+
if (opts.userId || opts.agentId) {
|
|
259
|
+
const payload: Record<string, unknown> = {};
|
|
260
|
+
const statusParams: Record<string, string> = { page: "1", page_size: "1" };
|
|
261
|
+
const apiFilters = this._buildFilters({
|
|
262
|
+
userId: opts.userId,
|
|
263
|
+
agentId: opts.agentId,
|
|
264
|
+
});
|
|
265
|
+
if (apiFilters) payload.filters = apiFilters;
|
|
266
|
+
await this._request("POST", "/v2/memories/", { json: payload, params: statusParams });
|
|
267
|
+
} else {
|
|
268
|
+
await this._request("GET", "/v1/entities/");
|
|
269
|
+
}
|
|
270
|
+
return { connected: true, backend: "platform", base_url: this.baseUrl };
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return {
|
|
273
|
+
connected: false,
|
|
274
|
+
backend: "platform",
|
|
275
|
+
error: e instanceof Error ? e.message : String(e),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async entities(entityType: string): Promise<Record<string, unknown>[]> {
|
|
281
|
+
const result = (await this._request("GET", "/v1/entities/")) as unknown;
|
|
282
|
+
let items: Record<string, unknown>[];
|
|
283
|
+
if (Array.isArray(result)) {
|
|
284
|
+
items = result;
|
|
285
|
+
} else {
|
|
286
|
+
items = ((result as Record<string, unknown>).results ?? []) as Record<string, unknown>[];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const typeMap: Record<string, string> = {
|
|
290
|
+
users: "user",
|
|
291
|
+
agents: "agent",
|
|
292
|
+
apps: "app",
|
|
293
|
+
runs: "run",
|
|
294
|
+
};
|
|
295
|
+
const targetType = typeMap[entityType];
|
|
296
|
+
if (targetType) {
|
|
297
|
+
items = items.filter(
|
|
298
|
+
(e) => (e.type as string | undefined)?.toLowerCase() === targetType,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return items;
|
|
302
|
+
}
|
|
303
|
+
}
|
package/src/branding.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branding and ASCII art for mem0 CLI.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora, { type Ora } from "ora";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
|
|
9
|
+
const _require = createRequire(import.meta.url);
|
|
10
|
+
const PKG_VERSION: string = _require("../package.json").version;
|
|
11
|
+
|
|
12
|
+
export const LOGO = `
|
|
13
|
+
███╗ ███╗███████╗███╗ ███╗ ██████╗ ██████╗██╗ ██╗
|
|
14
|
+
████╗ ████║██╔════╝████╗ ████║██╔═████╗ ██╔════╝██║ ██║
|
|
15
|
+
██╔████╔██║█████╗ ██╔████╔██║██║██╔██║ ██║ ██║ ██║
|
|
16
|
+
██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║████╔╝██║ ██║ ██║ ██║
|
|
17
|
+
██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║╚██████╔╝ ╚██████╗███████╗██║
|
|
18
|
+
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚═╝
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
export const LOGO_MINI = "◆ mem0";
|
|
22
|
+
export const TAGLINE = "The Memory Layer for AI Agents";
|
|
23
|
+
|
|
24
|
+
export const BRAND_COLOR = "#8b5cf6";
|
|
25
|
+
export const ACCENT_COLOR = "#a78bfa";
|
|
26
|
+
export const SUCCESS_COLOR = "#22c55e";
|
|
27
|
+
export const ERROR_COLOR = "#ef4444";
|
|
28
|
+
export const WARNING_COLOR = "#f59e0b";
|
|
29
|
+
export const DIM_COLOR = "#6b7280";
|
|
30
|
+
|
|
31
|
+
const brand = chalk.hex(BRAND_COLOR);
|
|
32
|
+
const accent = chalk.hex(ACCENT_COLOR);
|
|
33
|
+
const success = chalk.hex(SUCCESS_COLOR);
|
|
34
|
+
const error = chalk.hex(ERROR_COLOR);
|
|
35
|
+
const warning = chalk.hex(WARNING_COLOR);
|
|
36
|
+
const dim = chalk.hex(DIM_COLOR);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Choose a symbol based on TTY/NO_COLOR. Fancy for interactive terminals,
|
|
40
|
+
* plain-text for piped/non-TTY or NO_COLOR environments.
|
|
41
|
+
*/
|
|
42
|
+
export function sym(fancy: string, plain: string): string {
|
|
43
|
+
if (!process.stdout.isTTY || process.env.NO_COLOR) return plain;
|
|
44
|
+
return fancy;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function printBanner(): void {
|
|
48
|
+
const pad = 3; // horizontal padding each side (matches Rich's padding=(0, 2))
|
|
49
|
+
const logoLines = LOGO.trimEnd().split("\n");
|
|
50
|
+
const tagline = ` ${TAGLINE}`;
|
|
51
|
+
const subtitle = `Node.js SDK · v${PKG_VERSION}`;
|
|
52
|
+
const contentLines = ["", ...logoLines, "", tagline, ""];
|
|
53
|
+
|
|
54
|
+
// Compute inner width from longest content line + padding both sides
|
|
55
|
+
const maxContent = Math.max(...contentLines.map((l) => l.length));
|
|
56
|
+
const innerWidth = maxContent + pad * 2;
|
|
57
|
+
const totalWidth = innerWidth + 2; // + 2 for │ borders
|
|
58
|
+
|
|
59
|
+
const topBorder = brand(`╭${"─".repeat(totalWidth - 2)}╮`);
|
|
60
|
+
const subtitleFill = totalWidth - 2 - subtitle.length - 3; // 3 = "─ " before subtitle + "─" after
|
|
61
|
+
const bottomBorder = brand(`╰${"─".repeat(subtitleFill)} ${dim(subtitle)} ${"─"}╯`);
|
|
62
|
+
|
|
63
|
+
const body = contentLines.map((line) => {
|
|
64
|
+
const rightPad = innerWidth - pad - line.length;
|
|
65
|
+
return `${brand("│")}${" ".repeat(pad)}${brand.bold(line)}${" ".repeat(Math.max(rightPad, 0))}${brand("│")}`;
|
|
66
|
+
});
|
|
67
|
+
// Re-color tagline line with accent instead of brand.bold
|
|
68
|
+
const taglineIdx = body.length - 2; // second-to-last (before trailing empty line)
|
|
69
|
+
const taglineRightPad = innerWidth - pad - tagline.length;
|
|
70
|
+
body[taglineIdx] = `${brand("│")}${" ".repeat(pad)}${accent(tagline)}${" ".repeat(Math.max(taglineRightPad, 0))}${brand("│")}`;
|
|
71
|
+
|
|
72
|
+
console.log(topBorder);
|
|
73
|
+
for (const line of body) console.log(line);
|
|
74
|
+
console.log(bottomBorder);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function printSuccess(message: string): void {
|
|
78
|
+
console.log(`${success(sym("✓", "[ok]"))} ${message}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function printError(message: string, hint?: string): void {
|
|
82
|
+
console.error(`${error(sym("✗", "[error]") + " Error:")} ${message}`);
|
|
83
|
+
if (hint) {
|
|
84
|
+
console.error(` ${dim(hint)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function printWarning(message: string): void {
|
|
89
|
+
console.error(`${warning(sym("⚠", "[warn]"))} ${message}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function printInfo(message: string): void {
|
|
93
|
+
console.log(`${brand(sym("◆", "*"))} ${message}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function printScope(ids: Record<string, string | undefined>): void {
|
|
97
|
+
const parts: string[] = [];
|
|
98
|
+
for (const [key, val] of Object.entries(ids)) {
|
|
99
|
+
if (val) {
|
|
100
|
+
const label = key.replace(/_/g, " ").replace("id", "ID").trim();
|
|
101
|
+
parts.push(`${label}=${val}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (parts.length > 0) {
|
|
105
|
+
console.log(` ${dim(`Scope: ${parts.join(", ")}`)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface TimedStatusContext {
|
|
110
|
+
successMsg: string;
|
|
111
|
+
errorMsg: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run an async function with a spinner, timing the operation.
|
|
116
|
+
* Equivalent to Python's timed_status context manager.
|
|
117
|
+
*/
|
|
118
|
+
export async function timedStatus<T>(
|
|
119
|
+
message: string,
|
|
120
|
+
fn: (ctx: TimedStatusContext) => Promise<T>,
|
|
121
|
+
): Promise<T> {
|
|
122
|
+
const ctx: TimedStatusContext = { successMsg: "", errorMsg: "" };
|
|
123
|
+
const spinner = ora({ text: dim(message), color: "magenta", stream: process.stderr }).start();
|
|
124
|
+
const start = performance.now();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const result = await fn(ctx);
|
|
128
|
+
const elapsed = ((performance.now() - start) / 1000).toFixed(2);
|
|
129
|
+
spinner.stop();
|
|
130
|
+
if (ctx.successMsg) {
|
|
131
|
+
console.error(`${success("✓")} ${ctx.successMsg} (${elapsed}s)`);
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
const elapsed = ((performance.now() - start) / 1000).toFixed(2);
|
|
136
|
+
spinner.stop();
|
|
137
|
+
if (ctx.errorMsg) {
|
|
138
|
+
console.error(`${error("✗ Error:")} ${ctx.errorMsg} (${elapsed}s)`);
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Format helpers using brand colors for external use. */
|
|
145
|
+
export const colors = { brand, accent, success, error, warning, dim };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config management commands: show, set, get.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Table from "cli-table3";
|
|
6
|
+
import { printError, printSuccess, colors } from "../branding.js";
|
|
7
|
+
import {
|
|
8
|
+
getNestedValue,
|
|
9
|
+
loadConfig,
|
|
10
|
+
redactKey,
|
|
11
|
+
saveConfig,
|
|
12
|
+
setNestedValue,
|
|
13
|
+
} from "../config.js";
|
|
14
|
+
import { formatJsonEnvelope } from "../output.js";
|
|
15
|
+
|
|
16
|
+
const { brand, accent, dim } = colors;
|
|
17
|
+
|
|
18
|
+
export function cmdConfigShow(opts: { output?: string } = {}): void {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
|
|
21
|
+
if (opts.output === "json") {
|
|
22
|
+
formatJsonEnvelope({
|
|
23
|
+
command: "config show",
|
|
24
|
+
data: {
|
|
25
|
+
defaults: {
|
|
26
|
+
user_id: config.defaults.userId || null,
|
|
27
|
+
agent_id: config.defaults.agentId || null,
|
|
28
|
+
app_id: config.defaults.appId || null,
|
|
29
|
+
run_id: config.defaults.runId || null,
|
|
30
|
+
enable_graph: config.defaults.enableGraph,
|
|
31
|
+
},
|
|
32
|
+
platform: {
|
|
33
|
+
api_key: redactKey(config.platform.apiKey),
|
|
34
|
+
base_url: config.platform.baseUrl,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(` ${brand("◆ mem0 Configuration")}\n`);
|
|
43
|
+
|
|
44
|
+
const table = new Table({
|
|
45
|
+
head: [accent("Key"), accent("Value")],
|
|
46
|
+
style: { head: [], border: [] },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Defaults
|
|
50
|
+
table.push(["defaults.user_id", config.defaults.userId || dim("(not set)")]);
|
|
51
|
+
table.push(["defaults.agent_id", config.defaults.agentId || dim("(not set)")]);
|
|
52
|
+
table.push(["defaults.app_id", config.defaults.appId || dim("(not set)")]);
|
|
53
|
+
table.push(["defaults.run_id", config.defaults.runId || dim("(not set)")]);
|
|
54
|
+
table.push(["defaults.enable_graph", String(config.defaults.enableGraph)]);
|
|
55
|
+
table.push(["", ""]);
|
|
56
|
+
|
|
57
|
+
// Platform
|
|
58
|
+
table.push(["platform.api_key", redactKey(config.platform.apiKey)]);
|
|
59
|
+
table.push(["platform.base_url", config.platform.baseUrl]);
|
|
60
|
+
|
|
61
|
+
console.log(table.toString());
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function cmdConfigGet(key: string): void {
|
|
66
|
+
const config = loadConfig();
|
|
67
|
+
const value = getNestedValue(config, key);
|
|
68
|
+
|
|
69
|
+
if (value === undefined) {
|
|
70
|
+
printError(`Unknown config key: ${key}`);
|
|
71
|
+
} else {
|
|
72
|
+
// Redact secrets
|
|
73
|
+
if (key.includes("api_key") || key.split(".").pop() === "key") {
|
|
74
|
+
console.log(redactKey(String(value)));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(String(value));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function cmdConfigSet(key: string, value: string): void {
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
if (setNestedValue(config, key, value)) {
|
|
84
|
+
saveConfig(config);
|
|
85
|
+
const display = key.includes("key") ? redactKey(value) : value;
|
|
86
|
+
printSuccess(`${key} = ${display}`);
|
|
87
|
+
} else {
|
|
88
|
+
printError(`Unknown config key: ${key}`);
|
|
89
|
+
}
|
|
90
|
+
}
|