oortmem 0.0.0-stage → 0.1.1
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/LICENSE +202 -0
- package/README.md +21 -2
- package/dist/cloud-remote.d.ts +9 -0
- package/dist/cloud-remote.js +72 -0
- package/dist/cloud.d.ts +139 -0
- package/dist/cloud.js +223 -0
- package/dist/commands/cloud.d.ts +34 -0
- package/dist/commands/cloud.js +263 -0
- package/dist/commands/connect.d.ts +14 -0
- package/dist/commands/connect.js +99 -0
- package/dist/commands/consolidate.d.ts +3 -0
- package/dist/commands/consolidate.js +24 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/doctor.js +482 -0
- package/dist/commands/explain.d.ts +5 -0
- package/dist/commands/explain.js +67 -0
- package/dist/commands/export.d.ts +22 -0
- package/dist/commands/export.js +83 -0
- package/dist/commands/forget.d.ts +5 -0
- package/dist/commands/forget.js +24 -0
- package/dist/commands/hook.d.ts +12 -0
- package/dist/commands/hook.js +169 -0
- package/dist/commands/import.d.ts +23 -0
- package/dist/commands/import.js +70 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +86 -0
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.js +166 -0
- package/dist/commands/install.d.ts +28 -0
- package/dist/commands/install.js +122 -0
- package/dist/commands/keys.d.ts +16 -0
- package/dist/commands/keys.js +69 -0
- package/dist/commands/login.d.ts +74 -0
- package/dist/commands/login.js +326 -0
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.js +42 -0
- package/dist/commands/observe.d.ts +15 -0
- package/dist/commands/observe.js +88 -0
- package/dist/commands/pin.d.ts +3 -0
- package/dist/commands/pin.js +28 -0
- package/dist/commands/project.d.ts +15 -0
- package/dist/commands/project.js +60 -0
- package/dist/commands/recall.d.ts +11 -0
- package/dist/commands/recall.js +58 -0
- package/dist/commands/setup.d.ts +50 -0
- package/dist/commands/setup.js +532 -0
- package/dist/commands/stats.d.ts +25 -0
- package/dist/commands/stats.js +143 -0
- package/dist/connectors.d.ts +83 -0
- package/dist/connectors.js +615 -0
- package/dist/credentials-file.d.ts +37 -0
- package/dist/credentials-file.js +94 -0
- package/dist/credentials.d.ts +47 -0
- package/dist/credentials.js +46 -0
- package/dist/embed-cache-disk.d.ts +13 -0
- package/dist/embed-cache-disk.js +81 -0
- package/dist/io.d.ts +36 -0
- package/dist/io.js +117 -0
- package/dist/keychain.d.ts +70 -0
- package/dist/keychain.js +218 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +28 -0
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.js +301 -0
- package/dist/program.d.ts +14 -0
- package/dist/program.js +250 -0
- package/dist/relay.d.ts +57 -0
- package/dist/relay.js +119 -0
- package/dist/remember.d.ts +27 -0
- package/dist/remember.js +42 -0
- package/dist/ui.d.ts +42 -0
- package/dist/ui.js +63 -0
- package/dist/util.d.ts +84 -0
- package/dist/util.js +213 -0
- package/dist/wire.d.ts +77 -0
- package/dist/wire.js +281 -0
- package/package.json +67 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type CloudClient } from '../cloud.js';
|
|
2
|
+
import { type Ctx } from '../util.js';
|
|
3
|
+
/** One import request: at most this many memory lines and bytes (the API takes 1,000 and 10 MB). */
|
|
4
|
+
export declare const IMPORT_CHUNK_LINES = 500;
|
|
5
|
+
export declare const IMPORT_CHUNK_BYTES: number;
|
|
6
|
+
/**
|
|
7
|
+
* Uploads one chunk. The API imports at most 1,000 lines a call and answers `next` (an index into
|
|
8
|
+
* the posted lines) when it stopped early; the same lines are posted again with `from = next`
|
|
9
|
+
* until it is done. `progress` gets how many of the chunk's lines are in after each call.
|
|
10
|
+
*/
|
|
11
|
+
export declare function uploadChunk(client: CloudClient, header: string, lines: string[], progress: (inChunk: number) => void): Promise<{
|
|
12
|
+
imported: number;
|
|
13
|
+
skipped: number;
|
|
14
|
+
reembedded: number;
|
|
15
|
+
calls: number;
|
|
16
|
+
}>;
|
|
17
|
+
/** `oort cloud use [--local]`: switch this machine's store. */
|
|
18
|
+
export declare function cloudUseCommand(opts: {
|
|
19
|
+
local?: boolean;
|
|
20
|
+
}, ctx: Ctx): Promise<number>;
|
|
21
|
+
/** `oort cloud status [--json]` */
|
|
22
|
+
export declare function cloudStatusCommand(opts: {
|
|
23
|
+
json?: boolean;
|
|
24
|
+
}, ctx: Ctx): Promise<number>;
|
|
25
|
+
/** `oort cloud import [--dry-run] [--yes]`: uploads the local store, resumable and idempotent. */
|
|
26
|
+
export declare function cloudImportCommand(opts: {
|
|
27
|
+
dryRun?: boolean;
|
|
28
|
+
yes?: boolean;
|
|
29
|
+
}, ctx: Ctx): Promise<number>;
|
|
30
|
+
/** `oort cloud export [--out <file>] [--force]`: the whole cloud account in the v1 format. */
|
|
31
|
+
export declare function cloudExportCommand(opts: {
|
|
32
|
+
out?: string;
|
|
33
|
+
force?: boolean;
|
|
34
|
+
}, ctx: Ctx): Promise<number>;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { principalGrant } from '@oortmem/core';
|
|
3
|
+
import { OortCloudError, cloudUrl, createCloudClient, readCloudSettings, writeCloudSettings, } from '../cloud.js';
|
|
4
|
+
import { keyLookup } from '../credentials.js';
|
|
5
|
+
import { uiFor } from '../ui.js';
|
|
6
|
+
import { Refusal, confirm, toJson, withWired, writeOwnerOnly } from '../util.js';
|
|
7
|
+
import { EXPORT_VERSION, decoded, scopeKey } from './export.js';
|
|
8
|
+
/** One import request: at most this many memory lines and bytes (the API takes 1,000 and 10 MB). */
|
|
9
|
+
export const IMPORT_CHUNK_LINES = 500;
|
|
10
|
+
export const IMPORT_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
/** A client for this machine's Oort key, whichever store mode is set. */
|
|
12
|
+
async function clientFor(ctx) {
|
|
13
|
+
const url = cloudUrl(ctx.io.env);
|
|
14
|
+
if ('error' in url)
|
|
15
|
+
throw new Refusal(url.error);
|
|
16
|
+
const key = await keyLookup(ctx.io.env, ctx.keychain.read)('oort');
|
|
17
|
+
if (key === undefined) {
|
|
18
|
+
throw new Refusal('Oort Cloud needs the Oort key: run `oort login` first.');
|
|
19
|
+
}
|
|
20
|
+
return createCloudClient({
|
|
21
|
+
url: url.url,
|
|
22
|
+
key: key.key,
|
|
23
|
+
...(ctx.fetch !== undefined && { fetch: ctx.fetch }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Every memory in the local store (all projects of this machine's principal), oldest first. */
|
|
27
|
+
async function localExport(w, ctx) {
|
|
28
|
+
const s = w.scope;
|
|
29
|
+
const grant = principalGrant({ orgId: s.orgId, customerId: s.customerId, agentId: s.agentId });
|
|
30
|
+
const all = await w.store.list(grant, { validOnly: false });
|
|
31
|
+
all.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt) || (a.id < b.id ? -1 : 1));
|
|
32
|
+
const lines = [];
|
|
33
|
+
const scopes = new Map();
|
|
34
|
+
let withEmbedding = 0;
|
|
35
|
+
for (const m of all) {
|
|
36
|
+
const full = await decoded(m, w.store, grant, ctx);
|
|
37
|
+
if (full.embedding !== undefined && full.embedding.length === w.config.embed.dimension)
|
|
38
|
+
withEmbedding++;
|
|
39
|
+
scopes.set(scopeKey(full.scope), full.scope);
|
|
40
|
+
lines.push(JSON.stringify(toJson(full)));
|
|
41
|
+
}
|
|
42
|
+
const manifest = {
|
|
43
|
+
oort: 'export',
|
|
44
|
+
version: EXPORT_VERSION,
|
|
45
|
+
exportedAt: new Date().toISOString(),
|
|
46
|
+
memories: lines.length,
|
|
47
|
+
scopes: [...scopes.values()],
|
|
48
|
+
};
|
|
49
|
+
return { manifest, lines, withEmbedding };
|
|
50
|
+
}
|
|
51
|
+
/** Chunks of export lines, each within the line and byte caps. */
|
|
52
|
+
function chunks(lines, from) {
|
|
53
|
+
const out = [];
|
|
54
|
+
let cur = [];
|
|
55
|
+
let bytes = 0;
|
|
56
|
+
let start = from;
|
|
57
|
+
for (let i = from; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
const b = Buffer.byteLength(line) + 1;
|
|
60
|
+
if (cur.length > 0 && (cur.length >= IMPORT_CHUNK_LINES || bytes + b > IMPORT_CHUNK_BYTES)) {
|
|
61
|
+
out.push({ start, lines: cur });
|
|
62
|
+
cur = [];
|
|
63
|
+
bytes = 0;
|
|
64
|
+
start = i;
|
|
65
|
+
}
|
|
66
|
+
cur.push(line);
|
|
67
|
+
bytes += b;
|
|
68
|
+
}
|
|
69
|
+
if (cur.length > 0)
|
|
70
|
+
out.push({ start, lines: cur });
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Uploads one chunk. The API imports at most 1,000 lines a call and answers `next` (an index into
|
|
75
|
+
* the posted lines) when it stopped early; the same lines are posted again with `from = next`
|
|
76
|
+
* until it is done. `progress` gets how many of the chunk's lines are in after each call.
|
|
77
|
+
*/
|
|
78
|
+
export async function uploadChunk(client, header, lines, progress) {
|
|
79
|
+
const body = [header, ...lines];
|
|
80
|
+
const total = { imported: 0, skipped: 0, reembedded: 0, calls: 0 };
|
|
81
|
+
let from = 0;
|
|
82
|
+
for (;;) {
|
|
83
|
+
const r = await client.importLines(body, from);
|
|
84
|
+
total.calls++;
|
|
85
|
+
total.imported += r.imported;
|
|
86
|
+
total.skipped += r.skipped;
|
|
87
|
+
total.reembedded += r.reembedded;
|
|
88
|
+
if (r.next === undefined) {
|
|
89
|
+
progress(lines.length);
|
|
90
|
+
return total;
|
|
91
|
+
}
|
|
92
|
+
// Line 0 is the manifest, so line `next` of the body is line `next - 1` of the chunk.
|
|
93
|
+
if (!Number.isInteger(r.next) || r.next <= Math.max(from, 1) || r.next > lines.length) {
|
|
94
|
+
throw new Refusal('Oort Cloud stopped making progress on the import; run it again later.');
|
|
95
|
+
}
|
|
96
|
+
progress(r.next - 1);
|
|
97
|
+
from = r.next;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const fingerprintOf = (lines) => {
|
|
101
|
+
const id = (l) => l === undefined ? '' : (JSON.parse(l).id ?? '');
|
|
102
|
+
return `${lines.length}:${id(lines[0])}:${id(lines.at(-1))}`;
|
|
103
|
+
};
|
|
104
|
+
/** `oort cloud use [--local]`: switch this machine's store. */
|
|
105
|
+
export async function cloudUseCommand(opts, ctx) {
|
|
106
|
+
const home = (await withWired(ctx, async (w) => w.config.home));
|
|
107
|
+
const settings = readCloudSettings(home);
|
|
108
|
+
const ui = uiFor(ctx.io);
|
|
109
|
+
if (opts.local === true) {
|
|
110
|
+
writeCloudSettings(home, { ...settings, store: 'local' });
|
|
111
|
+
ctx.io.out(ui.header('cloud', 'local store'));
|
|
112
|
+
ctx.io.out('This machine now uses the local store. Memories already in Oort Cloud stay there; `oort cloud export` downloads them.\n');
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
const client = await clientFor(ctx);
|
|
116
|
+
const me = await client.whoami();
|
|
117
|
+
const local = await withWired(ctx, async (w) => (await localExport(w, ctx)).lines.length);
|
|
118
|
+
writeCloudSettings(home, { ...settings, store: 'cloud' });
|
|
119
|
+
ctx.io.out(ui.header('cloud', 'Oort Cloud'));
|
|
120
|
+
ctx.io.out(ui.kv([
|
|
121
|
+
['account', me.account],
|
|
122
|
+
['plan', me.plan],
|
|
123
|
+
['store', `Oort Cloud (${client.url}), stored in the EU`],
|
|
124
|
+
]));
|
|
125
|
+
ctx.io.out(`\nHooks, MCP and the oort commands now read and save in Oort Cloud.\n` +
|
|
126
|
+
(local > 0
|
|
127
|
+
? `${local} ${local === 1 ? 'memory stays' : 'memories stay'} in the local store until you run \`oort cloud import\`.\n`
|
|
128
|
+
: '') +
|
|
129
|
+
'Switch back any time with `oort cloud use --local`.\n');
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
/** `oort cloud status [--json]` */
|
|
133
|
+
export async function cloudStatusCommand(opts, ctx) {
|
|
134
|
+
return withWired(ctx, async (w) => {
|
|
135
|
+
const settings = readCloudSettings(w.config.home);
|
|
136
|
+
let account;
|
|
137
|
+
let stats;
|
|
138
|
+
try {
|
|
139
|
+
const client = await clientFor(ctx);
|
|
140
|
+
account = await client.whoami();
|
|
141
|
+
stats = (await client.stats());
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
if (!(e instanceof Refusal))
|
|
145
|
+
throw e;
|
|
146
|
+
account = { error: e.message };
|
|
147
|
+
}
|
|
148
|
+
const out = {
|
|
149
|
+
store: settings.store,
|
|
150
|
+
url: (() => {
|
|
151
|
+
const u = cloudUrl(ctx.io.env);
|
|
152
|
+
return 'url' in u ? u.url : u.error;
|
|
153
|
+
})(),
|
|
154
|
+
account,
|
|
155
|
+
...(stats !== undefined && { cloud: stats }),
|
|
156
|
+
...(settings.import !== undefined && { import: settings.import }),
|
|
157
|
+
};
|
|
158
|
+
if (opts.json === true) {
|
|
159
|
+
ctx.io.out(`${JSON.stringify(out, null, 2)}\n`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
const ui = uiFor(ctx.io);
|
|
163
|
+
const acct = 'error' in account
|
|
164
|
+
? ui.yellow(String(account['error']))
|
|
165
|
+
: `${String(account['account'])} (${String(account['plan'])})`;
|
|
166
|
+
ctx.io.out(ui.header('cloud', settings.store === 'cloud' ? 'Oort Cloud' : 'local store'));
|
|
167
|
+
ctx.io.out(ui.kv([
|
|
168
|
+
['store', settings.store === 'cloud' ? `Oort Cloud (${out.url})` : 'local (this machine)'],
|
|
169
|
+
['account', acct],
|
|
170
|
+
...(stats !== undefined
|
|
171
|
+
? [['cloud memories', String(stats['memories'])]]
|
|
172
|
+
: []),
|
|
173
|
+
...(settings.import !== undefined
|
|
174
|
+
? [
|
|
175
|
+
[
|
|
176
|
+
'import',
|
|
177
|
+
`${settings.import.done}/${settings.import.total} uploaded${settings.import.done < settings.import.total ? ' (run `oort cloud import` to resume)' : ''}`,
|
|
178
|
+
],
|
|
179
|
+
]
|
|
180
|
+
: []),
|
|
181
|
+
]));
|
|
182
|
+
return 0;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** `oort cloud import [--dry-run] [--yes]`: uploads the local store, resumable and idempotent. */
|
|
186
|
+
export async function cloudImportCommand(opts, ctx) {
|
|
187
|
+
return withWired(ctx, async (w) => {
|
|
188
|
+
const ui = uiFor(ctx.io);
|
|
189
|
+
const { manifest, lines, withEmbedding } = await localExport(w, ctx);
|
|
190
|
+
const settings = readCloudSettings(w.config.home);
|
|
191
|
+
const fingerprint = fingerprintOf(lines);
|
|
192
|
+
const resume = settings.import?.fingerprint === fingerprint
|
|
193
|
+
? Math.min(settings.import.done, lines.length)
|
|
194
|
+
: 0;
|
|
195
|
+
const plan = chunks(lines, resume);
|
|
196
|
+
const bytes = lines.slice(resume).reduce((n, l) => n + Buffer.byteLength(l) + 1, 0);
|
|
197
|
+
ctx.io.out(ui.header('cloud import', `${lines.length} local memories`));
|
|
198
|
+
ctx.io.out(ui.kv([
|
|
199
|
+
['to upload', `${lines.length - resume}${resume > 0 ? ` (resuming after ${resume})` : ''}`],
|
|
200
|
+
['size', `${(bytes / (1024 * 1024)).toFixed(2)} MB in ${plan.length} requests`],
|
|
201
|
+
[
|
|
202
|
+
'embeddings',
|
|
203
|
+
`${withEmbedding} reused; ${lines.length - withEmbedding} re-embedded in the cloud (counted toward your Pro embed use)`,
|
|
204
|
+
],
|
|
205
|
+
]));
|
|
206
|
+
if (opts.dryRun === true || lines.length === resume) {
|
|
207
|
+
if (lines.length === resume && lines.length > 0)
|
|
208
|
+
ctx.io.out('Already uploaded.\n');
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
const go = await confirm(ctx.io, `Upload ${lines.length - resume} memories to Oort Cloud (EU)?`, opts.yes === true, 'upload them');
|
|
212
|
+
if (!go) {
|
|
213
|
+
ctx.io.out('Nothing uploaded.\n');
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
const client = w.cloud ?? (await clientFor(ctx));
|
|
217
|
+
const header = JSON.stringify(manifest);
|
|
218
|
+
let done = resume;
|
|
219
|
+
let imported = 0;
|
|
220
|
+
let skipped = 0;
|
|
221
|
+
let reembedded = 0;
|
|
222
|
+
for (const c of plan) {
|
|
223
|
+
const r = await uploadChunk(client, header, c.lines, (inChunk) => {
|
|
224
|
+
done = c.start + inChunk;
|
|
225
|
+
writeCloudSettings(w.config.home, {
|
|
226
|
+
...readCloudSettings(w.config.home),
|
|
227
|
+
import: { fingerprint, done, total: lines.length, updatedAt: new Date().toISOString() },
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
imported += r.imported;
|
|
231
|
+
skipped += r.skipped;
|
|
232
|
+
reembedded += r.reembedded;
|
|
233
|
+
ctx.io.out(` uploaded ${done}/${lines.length}\n`);
|
|
234
|
+
}
|
|
235
|
+
ctx.io.out(`${ui.ok('done')}: ${imported} imported, ${skipped} already in the cloud, ${reembedded} re-embedded.\n`);
|
|
236
|
+
if (w.mode !== 'cloud')
|
|
237
|
+
ctx.io.out('Run `oort cloud use` to start using them.\n');
|
|
238
|
+
return 0;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** `oort cloud export [--out <file>] [--force]`: the whole cloud account in the v1 format. */
|
|
242
|
+
export async function cloudExportCommand(opts, ctx) {
|
|
243
|
+
const client = await clientFor(ctx);
|
|
244
|
+
let data;
|
|
245
|
+
try {
|
|
246
|
+
data = await client.exportText();
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
if (e instanceof OortCloudError)
|
|
250
|
+
throw e;
|
|
251
|
+
throw new Refusal('Oort Cloud export failed; try again.');
|
|
252
|
+
}
|
|
253
|
+
const memories = Math.max(0, data.split('\n').filter((l) => l.length > 0).length - 1);
|
|
254
|
+
if (opts.out === undefined)
|
|
255
|
+
ctx.io.out(data);
|
|
256
|
+
else {
|
|
257
|
+
const file = path.resolve(ctx.io.cwd, opts.out);
|
|
258
|
+
writeOwnerOnly(file, data, opts.force === true);
|
|
259
|
+
ctx.io.out(`Exported ${memories} memories from Oort Cloud to ${file}\n`);
|
|
260
|
+
}
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=cloud.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Change, type Host, type HostContext } from '../connectors.js';
|
|
2
|
+
import { type Ui } from '../ui.js';
|
|
3
|
+
import { type Ctx } from '../util.js';
|
|
4
|
+
export declare function hostContext(ctx: Ctx): HostContext;
|
|
5
|
+
export declare function hostArgs(args: string[]): Host[];
|
|
6
|
+
/** The lines a change prints: header, key diff, what was written and how to undo it. */
|
|
7
|
+
export declare function describeChange(ui: Ui, c: Change, cmd: string, opts?: {
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
home?: string;
|
|
10
|
+
}): string;
|
|
11
|
+
/** `oort connect <host...> [--dry-run]` / `oort disconnect <host...> [--dry-run]` */
|
|
12
|
+
export declare function connectCommand(action: 'connect' | 'disconnect', args: string[], opts: {
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
}, ctx: Ctx): Promise<number>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import { HOSTS, HOST_LABEL, connectHost, disconnectHost, isHost, } from '../connectors.js';
|
|
3
|
+
import { uiFor } from '../ui.js';
|
|
4
|
+
import { Refusal, echo, tilde } from '../util.js';
|
|
5
|
+
import { hookCommandPath } from './install.js';
|
|
6
|
+
/** Where the host starts stdio servers, which decides the memory scope. */
|
|
7
|
+
function scopeNote(host) {
|
|
8
|
+
if (host === 'vscode' || host === 'cursor') {
|
|
9
|
+
return `the open workspace folder (${HOST_LABEL[host]} fills in --project \${workspaceFolder}), plus global memories`;
|
|
10
|
+
}
|
|
11
|
+
if (host === 'claude-desktop')
|
|
12
|
+
return 'the directory Claude Desktop starts it in (not a project, so usually global)';
|
|
13
|
+
return `the directory ${HOST_LABEL[host]} starts it in (usually your project), plus global memories; add "--project", "<dir>" to its args to pin one`;
|
|
14
|
+
}
|
|
15
|
+
const RELOAD = {
|
|
16
|
+
'claude-code': 'start a new Claude Code session; `/mcp` lists oort',
|
|
17
|
+
'claude-desktop': 'quit and reopen Claude Desktop',
|
|
18
|
+
codex: 'start a new Codex session; `codex mcp list` shows oort',
|
|
19
|
+
cursor: 'reload Cursor; Settings > MCP lists oort',
|
|
20
|
+
gemini: 'restart Gemini CLI; `/mcp` lists oort',
|
|
21
|
+
vscode: 'run "MCP: List Servers" and start oort',
|
|
22
|
+
};
|
|
23
|
+
export function hostContext(ctx) {
|
|
24
|
+
return { home: ctx.io.env['HOME'] || os.homedir(), env: ctx.io.env, platform: process.platform };
|
|
25
|
+
}
|
|
26
|
+
export function hostArgs(args) {
|
|
27
|
+
const hosts = [];
|
|
28
|
+
for (const a of args) {
|
|
29
|
+
if (!isHost(a)) {
|
|
30
|
+
throw new Refusal(`unknown host ${echo(a)}; expected one of ${HOSTS.join(', ')}. ChatGPT and claude.ai web need a remote MCP URL, which arrives with Oort Cloud.`);
|
|
31
|
+
}
|
|
32
|
+
if (!hosts.includes(a))
|
|
33
|
+
hosts.push(a);
|
|
34
|
+
}
|
|
35
|
+
return hosts;
|
|
36
|
+
}
|
|
37
|
+
/** The lines a change prints: header, key diff, what was written and how to undo it. */
|
|
38
|
+
export function describeChange(ui, c, cmd, opts = {}) {
|
|
39
|
+
const label = HOST_LABEL[c.host];
|
|
40
|
+
const dryRun = opts.dryRun === true;
|
|
41
|
+
const show = (p) => tilde(p, opts.home ?? '');
|
|
42
|
+
let out = ui.header(`${c.action} ${c.host}`, show(c.file));
|
|
43
|
+
if (!c.changed) {
|
|
44
|
+
out +=
|
|
45
|
+
c.action === 'connect'
|
|
46
|
+
? `${ui.ok(`${label} is already connected; nothing changed.`)}\n`
|
|
47
|
+
: `${ui.ok(`No oort entry in ${label}'s config; nothing changed.`)}\n`;
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
for (const line of c.diff) {
|
|
51
|
+
out += `${line.startsWith('+') ? ui.green(line) : line.startsWith('-') ? ui.red(line) : ui.yellow(line)}\n`;
|
|
52
|
+
}
|
|
53
|
+
if (dryRun)
|
|
54
|
+
return `${out}${ui.dim('(dry run: nothing written)')}\n`;
|
|
55
|
+
const rows = [];
|
|
56
|
+
if (c.action === 'connect') {
|
|
57
|
+
const how = c.via === 'claude-cli' ? ' (through `claude mcp add --scope user`)' : '';
|
|
58
|
+
out += `${ui.ok(`${c.created ? 'Created' : 'Updated'} ${show(c.file)}${how}: ${label} starts \`${cmd} mcp\`.`)}\n`;
|
|
59
|
+
rows.push(['scope', scopeNote(c.host)], ['next', RELOAD[c.host]]);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const how = c.via === 'claude-cli' ? ' (through `claude mcp remove --scope user`)' : '';
|
|
63
|
+
out += `${ui.ok(`Removed the oort entry from ${show(c.file)}${how}.`)}\n`;
|
|
64
|
+
}
|
|
65
|
+
if (c.backup !== undefined)
|
|
66
|
+
rows.push(['backup', `${show(c.backup)} (before Oort first changed it)`]);
|
|
67
|
+
if (c.previous !== undefined)
|
|
68
|
+
rows.push(['previous', `${show(c.previous)} (just before this change)`]);
|
|
69
|
+
rows.push([
|
|
70
|
+
'undo',
|
|
71
|
+
c.action === 'connect' ? `oort disconnect ${c.host}` : `oort connect ${c.host}`,
|
|
72
|
+
]);
|
|
73
|
+
return out + ui.kv(rows, { indent: 2 });
|
|
74
|
+
}
|
|
75
|
+
/** `oort connect <host...> [--dry-run]` / `oort disconnect <host...> [--dry-run]` */
|
|
76
|
+
export async function connectCommand(action, args, opts, ctx) {
|
|
77
|
+
const hosts = hostArgs(args);
|
|
78
|
+
// Disconnect also removes an entry from an older install path, so it never needs the bin.
|
|
79
|
+
let cmd;
|
|
80
|
+
try {
|
|
81
|
+
cmd = hookCommandPath(ctx.io.entry);
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
if (action === 'connect')
|
|
85
|
+
throw e;
|
|
86
|
+
cmd = 'oort';
|
|
87
|
+
}
|
|
88
|
+
const hc = hostContext(ctx);
|
|
89
|
+
const ui = uiFor(ctx.io);
|
|
90
|
+
const dryRun = opts.dryRun === true;
|
|
91
|
+
for (const host of hosts) {
|
|
92
|
+
const c = action === 'connect'
|
|
93
|
+
? connectHost(host, hc, cmd, { dryRun })
|
|
94
|
+
: disconnectHost(host, hc, cmd, { dryRun });
|
|
95
|
+
ctx.io.out(describeChange(ui, c, cmd, { dryRun, home: hc.home }));
|
|
96
|
+
}
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { filterFor } from '@oortmem/claude-code';
|
|
2
|
+
import { consolidate } from '@oortmem/core';
|
|
3
|
+
import { uiFor } from '../ui.js';
|
|
4
|
+
import { Refusal, scopeLabel, withWired } from '../util.js';
|
|
5
|
+
/** `oort consolidate`: expire, re-tier and evict the memories visible from the cwd. */
|
|
6
|
+
export async function consolidateCommand(ctx) {
|
|
7
|
+
return withWired(ctx, async (w) => {
|
|
8
|
+
if (w.mode === 'cloud') {
|
|
9
|
+
throw new Refusal('In cloud mode Oort Cloud consolidates your memories itself (daily).');
|
|
10
|
+
}
|
|
11
|
+
const r = await consolidate(filterFor(w.scope), { store: w.store, config: w.config });
|
|
12
|
+
const ui = uiFor(ctx.io);
|
|
13
|
+
ctx.io.out(ui.header('consolidate', scopeLabel(w)));
|
|
14
|
+
ctx.io.out(ui.kv([
|
|
15
|
+
['promoted', String(r.promoted.length)],
|
|
16
|
+
['demoted', String(r.demoted.length)],
|
|
17
|
+
['evicted', String(r.evicted.length)],
|
|
18
|
+
['expired', String(r.expired.length)],
|
|
19
|
+
['skipped', String(r.skipped)],
|
|
20
|
+
]));
|
|
21
|
+
return 0;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=consolidate.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Ctx } from '../util.js';
|
|
2
|
+
export type CheckStatus = 'ok' | 'warn' | 'fail' | 'info' | 'unknown';
|
|
3
|
+
export interface Check {
|
|
4
|
+
group: 'keys' | 'hooks' | 'mcp' | 'store' | 'relay' | 'cloud';
|
|
5
|
+
name: string;
|
|
6
|
+
status: CheckStatus;
|
|
7
|
+
/** One line; never a key or a memory. */
|
|
8
|
+
detail: string;
|
|
9
|
+
/** One command or sentence that fixes a `warn` or `fail`. */
|
|
10
|
+
fix?: string;
|
|
11
|
+
}
|
|
12
|
+
/** `oort doctor [--json]`: every check, with a one-line fix per problem. Exits 1 if one fails. */
|
|
13
|
+
export declare function doctorCommand(opts: {
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}, ctx: Ctx): Promise<number>;
|