oortmem 0.1.0 → 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/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/consolidate.js +4 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +73 -1
- package/dist/commands/export.d.ts +4 -1
- package/dist/commands/export.js +17 -3
- package/dist/commands/forget.js +10 -3
- package/dist/commands/hook.js +5 -1
- package/dist/commands/import.js +3 -0
- package/dist/commands/inspect.js +46 -6
- package/dist/commands/observe.js +16 -1
- package/dist/commands/pin.js +13 -1
- package/dist/commands/recall.js +14 -2
- package/dist/commands/stats.js +31 -1
- package/dist/mcp.d.ts +9 -0
- package/dist/mcp.js +135 -2
- package/dist/program.js +26 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.js +11 -0
- package/dist/wire.d.ts +13 -1
- package/dist/wire.js +24 -0
- package/package.json +9 -9
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RemoteMemory } from '@oortmem/claude-code';
|
|
2
|
+
import { type CloudClient } from './cloud.js';
|
|
3
|
+
export declare function cloudTurnRef(ref: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* The hooks' view of Oort Cloud. Recall and save go to the API; every failure rejects (the hooks
|
|
6
|
+
* then inject nothing, and the drain keeps account-level refusals queued). With no client (no Oort
|
|
7
|
+
* key, a bad URL), every call rejects the same way.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cloudRemote(client: CloudClient | undefined, problem?: string): RemoteMemory;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { OortCloudError } from './cloud.js';
|
|
3
|
+
/** The API's turnRef cap; a longer ref is replaced by its hash (still stable across retries). */
|
|
4
|
+
const TURN_REF_MAX = 128;
|
|
5
|
+
export function cloudTurnRef(ref) {
|
|
6
|
+
return ref.length <= TURN_REF_MAX && /^[\x20-\x7e]+$/.test(ref)
|
|
7
|
+
? ref
|
|
8
|
+
: createHash('sha256').update(ref).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
/** Pinned and hot memories SessionStart injects: at most this many of each from the API. */
|
|
11
|
+
const SESSION_LIMIT = 100;
|
|
12
|
+
/**
|
|
13
|
+
* The hooks' view of Oort Cloud. Recall and save go to the API; every failure rejects (the hooks
|
|
14
|
+
* then inject nothing, and the drain keeps account-level refusals queued). With no client (no Oort
|
|
15
|
+
* key, a bad URL), every call rejects the same way.
|
|
16
|
+
*/
|
|
17
|
+
export function cloudRemote(client, problem) {
|
|
18
|
+
const missing = () => {
|
|
19
|
+
throw Object.assign(new Error(problem ?? 'Oort Cloud is not set up on this machine.'), {
|
|
20
|
+
holdQueue: true,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
if (client === undefined) {
|
|
24
|
+
return {
|
|
25
|
+
recall: async () => missing(),
|
|
26
|
+
sessionMemories: async () => missing(),
|
|
27
|
+
observe: async () => missing(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
recall: ({ text, projectId }) => client.recall({ text, ...(projectId !== undefined && { project: projectId }), k: 10 }),
|
|
32
|
+
sessionMemories: async ({ projectId }) => {
|
|
33
|
+
const project = projectId !== undefined && { project: projectId };
|
|
34
|
+
// Pinned first (any tier), then the hot tier; a memory in both appears once.
|
|
35
|
+
const [pinned, hot] = await Promise.all([
|
|
36
|
+
client.list({ ...project, pinned: true, limit: SESSION_LIMIT }),
|
|
37
|
+
client.list({ ...project, tier: 'hot', limit: SESSION_LIMIT }),
|
|
38
|
+
]);
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const m of [...pinned.memories, ...hot.memories]) {
|
|
42
|
+
if (seen.has(m.id))
|
|
43
|
+
continue;
|
|
44
|
+
seen.add(m.id);
|
|
45
|
+
out.push(m);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
},
|
|
49
|
+
observe: async (obs, projectId, turnRef) => {
|
|
50
|
+
try {
|
|
51
|
+
// A repeat of the same turnRef answers `duplicate: true`: already saved, so success.
|
|
52
|
+
await client.observe({
|
|
53
|
+
...(projectId !== undefined && { project: projectId }),
|
|
54
|
+
...(turnRef !== undefined && { turnRef: cloudTurnRef(turnRef) }),
|
|
55
|
+
observation: {
|
|
56
|
+
user: obs.userText,
|
|
57
|
+
assistant: obs.assistantText,
|
|
58
|
+
at: obs.at,
|
|
59
|
+
...(obs.sessionId !== undefined && { session: obs.sessionId }),
|
|
60
|
+
...(obs.toolSummaries.length > 0 && { tools: obs.toolSummaries }),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
if (e instanceof OortCloudError)
|
|
66
|
+
throw e; // carries holdQueue
|
|
67
|
+
throw Object.assign(new Error('Oort Cloud save failed.'), { holdQueue: true });
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=cloud-remote.js.map
|
package/dist/cloud.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Memory, RecallResult, Tier } from '@oortmem/core';
|
|
2
|
+
import type { Env } from './io.js';
|
|
3
|
+
import { Refusal } from './util.js';
|
|
4
|
+
/**
|
|
5
|
+
* Oort Cloud, as the CLI sees it: where the API is, which store this machine uses, and what the
|
|
6
|
+
* API's refusals mean. The API itself lives in the private oortcloud repo (apps/cloud); the routes
|
|
7
|
+
* and shapes the CLI relies on are pinned in `test/cloud-contract.json`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_CLOUD_URL = "https://api.oortcloud.dev";
|
|
10
|
+
/**
|
|
11
|
+
* The API root: `OORT_CLOUD_URL` when set and non-empty (local dev against `wrangler dev`), else
|
|
12
|
+
* `DEFAULT_CLOUD_URL`. Only https, or http to a loopback host: the Oort key travels in every
|
|
13
|
+
* request.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cloudUrl(env: Env): {
|
|
16
|
+
url: string;
|
|
17
|
+
} | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
export type StoreMode = 'local' | 'cloud';
|
|
21
|
+
export interface ImportProgress {
|
|
22
|
+
/** A fingerprint of the export being uploaded (count and first and last id). */
|
|
23
|
+
fingerprint: string;
|
|
24
|
+
/** Memory lines already accepted by the cloud. */
|
|
25
|
+
done: number;
|
|
26
|
+
total: number;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CloudSettings {
|
|
30
|
+
store: StoreMode;
|
|
31
|
+
import?: ImportProgress;
|
|
32
|
+
}
|
|
33
|
+
export declare const cloudSettingsPath: (home: string) => string;
|
|
34
|
+
export declare function readCloudSettings(home: string): CloudSettings;
|
|
35
|
+
export declare function writeCloudSettings(home: string, s: CloudSettings): void;
|
|
36
|
+
export type CloudErrorKind = 'unauthorized' | 'payment-required' | 'not-enabled' | 'rate-limit' | 'too-large' | 'not-found' | 'ambiguous-prefix' | 'bad-request' | 'busy' | 'network';
|
|
37
|
+
/**
|
|
38
|
+
* A refusal from Oort Cloud the user can act on: one line saying what happened and the fix. It
|
|
39
|
+
* never carries the key or the request. The API's own message is used where it explains a limit
|
|
40
|
+
* (the monthly cap says which and when it resets).
|
|
41
|
+
*/
|
|
42
|
+
export declare class OortCloudError extends Refusal {
|
|
43
|
+
readonly kind: CloudErrorKind;
|
|
44
|
+
readonly status: number;
|
|
45
|
+
/** The drain keeps the line queued instead of counting an attempt. */
|
|
46
|
+
readonly holdQueue: boolean;
|
|
47
|
+
constructor(kind: CloudErrorKind, status: number, apiMessage?: string);
|
|
48
|
+
}
|
|
49
|
+
/** The kind an API status (and `{ error: { code } }`) stands for. */
|
|
50
|
+
export declare function cloudErrorKind(status: number, code?: string): CloudErrorKind;
|
|
51
|
+
export type CloudMemory = Omit<Memory, 'embedding'>;
|
|
52
|
+
export interface CloudStats {
|
|
53
|
+
tiers: Record<Tier, number>;
|
|
54
|
+
memories: number;
|
|
55
|
+
databaseBytes: number;
|
|
56
|
+
lastConsolidatedAt: string | null;
|
|
57
|
+
}
|
|
58
|
+
export interface CloudObservation {
|
|
59
|
+
user: string;
|
|
60
|
+
assistant: string;
|
|
61
|
+
at?: string;
|
|
62
|
+
session?: string;
|
|
63
|
+
tools?: string[];
|
|
64
|
+
}
|
|
65
|
+
export interface CloudObserveResult {
|
|
66
|
+
outcome: string;
|
|
67
|
+
memories: {
|
|
68
|
+
id?: string;
|
|
69
|
+
action: string;
|
|
70
|
+
}[];
|
|
71
|
+
counts: Record<string, number>;
|
|
72
|
+
/** The turnRef was seen before: the turn is already saved and nothing was done. */
|
|
73
|
+
duplicate?: true;
|
|
74
|
+
}
|
|
75
|
+
export interface CloudImportResult {
|
|
76
|
+
imported: number;
|
|
77
|
+
skipped: number;
|
|
78
|
+
reembedded: number;
|
|
79
|
+
next?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface CloudClient {
|
|
82
|
+
readonly url: string;
|
|
83
|
+
whoami(): Promise<{
|
|
84
|
+
account: string;
|
|
85
|
+
plan: string;
|
|
86
|
+
via: string;
|
|
87
|
+
}>;
|
|
88
|
+
recall(q: {
|
|
89
|
+
text: string;
|
|
90
|
+
project?: string;
|
|
91
|
+
k?: number;
|
|
92
|
+
asOf?: string;
|
|
93
|
+
}): Promise<RecallResult>;
|
|
94
|
+
/** `turnRef` makes a retry safe: the API saves each turn once and answers `duplicate`. */
|
|
95
|
+
observe(b: {
|
|
96
|
+
project?: string;
|
|
97
|
+
observation: CloudObservation;
|
|
98
|
+
turnRef?: string;
|
|
99
|
+
}): Promise<CloudObserveResult>;
|
|
100
|
+
list(q?: {
|
|
101
|
+
project?: string;
|
|
102
|
+
tier?: Tier;
|
|
103
|
+
type?: string;
|
|
104
|
+
q?: string;
|
|
105
|
+
pinned?: boolean;
|
|
106
|
+
cursor?: string;
|
|
107
|
+
limit?: number;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
memories: CloudMemory[];
|
|
110
|
+
next?: string;
|
|
111
|
+
}>;
|
|
112
|
+
/** By id, or by a unique prefix of at least 8 characters (409 `ambiguous` when several match). */
|
|
113
|
+
get(ref: string): Promise<CloudMemory>;
|
|
114
|
+
forget(id: string): Promise<void>;
|
|
115
|
+
pin(id: string, pinned: boolean): Promise<void>;
|
|
116
|
+
stats(): Promise<CloudStats>;
|
|
117
|
+
/** The v1 export, as JSON Lines text. */
|
|
118
|
+
exportText(): Promise<string>;
|
|
119
|
+
/**
|
|
120
|
+
* Posts export lines (the first is the manifest). One call imports at most 1,000 lines; when
|
|
121
|
+
* `next` is set, post the same lines again with `from = next` to continue.
|
|
122
|
+
*/
|
|
123
|
+
importLines(lines: string[], from?: number): Promise<CloudImportResult>;
|
|
124
|
+
}
|
|
125
|
+
export interface CloudClientOptions {
|
|
126
|
+
url: string;
|
|
127
|
+
key: string;
|
|
128
|
+
fetch?: typeof fetch;
|
|
129
|
+
/** Per-request timeout; the hooks pass their own budget. */
|
|
130
|
+
timeoutMs?: number;
|
|
131
|
+
}
|
|
132
|
+
export declare function createCloudClient(opts: CloudClientOptions): CloudClient;
|
|
133
|
+
/** The shortest id prefix the API looks up (it answers 404 below this). */
|
|
134
|
+
export declare const MIN_ID_PREFIX = 8;
|
|
135
|
+
/**
|
|
136
|
+
* A memory by id or unique id prefix (what `oort inspect` prints), looked up by the API itself:
|
|
137
|
+
* a prefix that matches several memories is refused with a clear message.
|
|
138
|
+
*/
|
|
139
|
+
export declare function findCloudMemory(client: CloudClient, ref: string): Promise<CloudMemory>;
|
package/dist/cloud.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { ACCOUNT_URL } from './relay.js';
|
|
4
|
+
import { Refusal, writeOwnerOnly } from './util.js';
|
|
5
|
+
/**
|
|
6
|
+
* Oort Cloud, as the CLI sees it: where the API is, which store this machine uses, and what the
|
|
7
|
+
* API's refusals mean. The API itself lives in the private oortcloud repo (apps/cloud); the routes
|
|
8
|
+
* and shapes the CLI relies on are pinned in `test/cloud-contract.json`.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_CLOUD_URL = 'https://api.oortcloud.dev';
|
|
11
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
12
|
+
/**
|
|
13
|
+
* The API root: `OORT_CLOUD_URL` when set and non-empty (local dev against `wrangler dev`), else
|
|
14
|
+
* `DEFAULT_CLOUD_URL`. Only https, or http to a loopback host: the Oort key travels in every
|
|
15
|
+
* request.
|
|
16
|
+
*/
|
|
17
|
+
export function cloudUrl(env) {
|
|
18
|
+
const raw = env['OORT_CLOUD_URL'];
|
|
19
|
+
if (raw === undefined || raw === '')
|
|
20
|
+
return { url: DEFAULT_CLOUD_URL };
|
|
21
|
+
let u;
|
|
22
|
+
try {
|
|
23
|
+
u = new URL(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { error: 'OORT_CLOUD_URL is not a URL; unset it to use Oort Cloud.' };
|
|
27
|
+
}
|
|
28
|
+
const ok = u.protocol === 'https:' || (u.protocol === 'http:' && LOOPBACK.has(u.hostname));
|
|
29
|
+
if (!ok || u.username !== '' || u.password !== '' || u.search !== '' || u.hash !== '') {
|
|
30
|
+
return {
|
|
31
|
+
error: 'OORT_CLOUD_URL must be an https URL (or http to localhost) with no credentials or query; unset it to use Oort Cloud.',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return { url: `${u.origin}${u.pathname.replace(/\/+$/, '')}` };
|
|
35
|
+
}
|
|
36
|
+
export const cloudSettingsPath = (home) => path.join(home, 'cloud.json');
|
|
37
|
+
export function readCloudSettings(home) {
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = JSON.parse(fs.readFileSync(cloudSettingsPath(home), 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { store: 'local' };
|
|
44
|
+
}
|
|
45
|
+
const r = raw;
|
|
46
|
+
const store = r?.store === 'cloud' ? 'cloud' : 'local';
|
|
47
|
+
const imp = r?.import;
|
|
48
|
+
const progress = imp !== undefined &&
|
|
49
|
+
typeof imp.fingerprint === 'string' &&
|
|
50
|
+
typeof imp.done === 'number' &&
|
|
51
|
+
typeof imp.total === 'number'
|
|
52
|
+
? {
|
|
53
|
+
fingerprint: imp.fingerprint,
|
|
54
|
+
done: imp.done,
|
|
55
|
+
total: imp.total,
|
|
56
|
+
updatedAt: typeof imp.updatedAt === 'string' ? imp.updatedAt : '',
|
|
57
|
+
}
|
|
58
|
+
: undefined;
|
|
59
|
+
return { store, ...(progress !== undefined && { import: progress }) };
|
|
60
|
+
}
|
|
61
|
+
export function writeCloudSettings(home, s) {
|
|
62
|
+
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
63
|
+
writeOwnerOnly(cloudSettingsPath(home), `${JSON.stringify(s, null, 2)}\n`, true);
|
|
64
|
+
}
|
|
65
|
+
/** Kinds the account has to fix, or that pass with time: a hook's queue waits instead. */
|
|
66
|
+
const HOLD = new Set([
|
|
67
|
+
'unauthorized',
|
|
68
|
+
'payment-required',
|
|
69
|
+
'not-enabled',
|
|
70
|
+
'rate-limit',
|
|
71
|
+
'busy',
|
|
72
|
+
'network',
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* A refusal from Oort Cloud the user can act on: one line saying what happened and the fix. It
|
|
76
|
+
* never carries the key or the request. The API's own message is used where it explains a limit
|
|
77
|
+
* (the monthly cap says which and when it resets).
|
|
78
|
+
*/
|
|
79
|
+
export class OortCloudError extends Refusal {
|
|
80
|
+
kind;
|
|
81
|
+
status;
|
|
82
|
+
/** The drain keeps the line queued instead of counting an attempt. */
|
|
83
|
+
holdQueue;
|
|
84
|
+
constructor(kind, status, apiMessage) {
|
|
85
|
+
super(messageFor(kind, status, apiMessage));
|
|
86
|
+
this.name = 'OortCloudError';
|
|
87
|
+
this.kind = kind;
|
|
88
|
+
this.status = status;
|
|
89
|
+
this.holdQueue = HOLD.has(kind);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function messageFor(kind, status, api) {
|
|
93
|
+
switch (kind) {
|
|
94
|
+
case 'unauthorized':
|
|
95
|
+
return `Oort Cloud rejected the Oort key (HTTP ${status}): it is unknown or revoked. Run \`oort login\` again.`;
|
|
96
|
+
case 'payment-required':
|
|
97
|
+
return `Oort Pro is not active on this account (HTTP 402). Subscribe or update billing at ${ACCOUNT_URL}, or run \`oort cloud use --local\`. \`oort cloud export\` still works.`;
|
|
98
|
+
case 'not-enabled':
|
|
99
|
+
return 'Oort Cloud is not enabled for this account yet (HTTP 403). Your memories stay on this machine; run `oort cloud use --local`.';
|
|
100
|
+
case 'rate-limit':
|
|
101
|
+
return `Oort Cloud limit reached (HTTP 429)${api !== undefined ? `: ${api}` : '.'} Usage is at ${ACCOUNT_URL}.`;
|
|
102
|
+
case 'too-large':
|
|
103
|
+
return `Oort Cloud refused a request that is too large (HTTP 413)${api !== undefined ? `: ${api}` : '.'}`;
|
|
104
|
+
case 'not-found':
|
|
105
|
+
return 'No such memory in Oort Cloud.';
|
|
106
|
+
case 'ambiguous-prefix':
|
|
107
|
+
return 'That id prefix matches more than one memory in Oort Cloud; give more of the id.';
|
|
108
|
+
case 'bad-request':
|
|
109
|
+
return `Oort Cloud refused the request (HTTP ${status})${api !== undefined ? `: ${api}` : '.'}`;
|
|
110
|
+
case 'busy':
|
|
111
|
+
return `Oort Cloud is busy or down, try again shortly (HTTP ${status}).`;
|
|
112
|
+
case 'network':
|
|
113
|
+
return 'Oort Cloud is unreachable: check your network, then try again.';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** The kind an API status (and `{ error: { code } }`) stands for. */
|
|
117
|
+
export function cloudErrorKind(status, code) {
|
|
118
|
+
if (status === 401)
|
|
119
|
+
return 'unauthorized';
|
|
120
|
+
if (status === 402)
|
|
121
|
+
return 'payment-required';
|
|
122
|
+
if (status === 403)
|
|
123
|
+
return code === 'cloud_not_enabled' ? 'not-enabled' : 'unauthorized';
|
|
124
|
+
if (status === 404)
|
|
125
|
+
return 'not-found';
|
|
126
|
+
if (status === 409 && code === 'ambiguous')
|
|
127
|
+
return 'ambiguous-prefix';
|
|
128
|
+
if (status === 413)
|
|
129
|
+
return 'too-large';
|
|
130
|
+
if (status === 429)
|
|
131
|
+
return 'rate-limit';
|
|
132
|
+
if (status >= 500)
|
|
133
|
+
return 'busy';
|
|
134
|
+
return 'bad-request';
|
|
135
|
+
}
|
|
136
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
137
|
+
export function createCloudClient(opts) {
|
|
138
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
139
|
+
const timeout = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
140
|
+
async function call(method, route, body) {
|
|
141
|
+
const abort = new AbortController();
|
|
142
|
+
const timer = setTimeout(() => abort.abort(), timeout);
|
|
143
|
+
const headers = { authorization: `Bearer ${opts.key}` };
|
|
144
|
+
let payload;
|
|
145
|
+
if (body?.json !== undefined) {
|
|
146
|
+
headers['content-type'] = 'application/json';
|
|
147
|
+
payload = JSON.stringify(body.json);
|
|
148
|
+
}
|
|
149
|
+
else if (body?.text !== undefined) {
|
|
150
|
+
headers['content-type'] = 'application/x-ndjson';
|
|
151
|
+
payload = body.text;
|
|
152
|
+
}
|
|
153
|
+
let res;
|
|
154
|
+
try {
|
|
155
|
+
res = await doFetch(`${opts.url}${route}`, {
|
|
156
|
+
method,
|
|
157
|
+
headers,
|
|
158
|
+
...(payload !== undefined && { body: payload }),
|
|
159
|
+
signal: abort.signal,
|
|
160
|
+
redirect: 'error',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw new OortCloudError('network', 0);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
}
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
const err = (await res.json().catch(() => ({})));
|
|
171
|
+
const code = typeof err.error?.code === 'string' ? err.error.code : undefined;
|
|
172
|
+
const message = typeof err.error?.message === 'string' ? err.error.message : undefined;
|
|
173
|
+
throw new OortCloudError(cloudErrorKind(res.status, code), res.status, message);
|
|
174
|
+
}
|
|
175
|
+
return res;
|
|
176
|
+
}
|
|
177
|
+
const json = async (method, route, body) => (await (await call(method, route, body === undefined ? undefined : { json: body })).json());
|
|
178
|
+
const idPath = (id) => `/v1/memories/${encodeURIComponent(id)}`;
|
|
179
|
+
return {
|
|
180
|
+
url: opts.url,
|
|
181
|
+
whoami: () => json('GET', '/v1/whoami'),
|
|
182
|
+
recall: (q) => json('POST', '/v1/recall', q),
|
|
183
|
+
observe: (b) => json('POST', '/v1/observe', b),
|
|
184
|
+
list: (q = {}) => {
|
|
185
|
+
const p = new URLSearchParams();
|
|
186
|
+
for (const [k, v] of Object.entries(q))
|
|
187
|
+
if (v !== undefined)
|
|
188
|
+
p.set(k, String(v));
|
|
189
|
+
const qs = p.toString();
|
|
190
|
+
return json('GET', `/v1/memories${qs === '' ? '' : `?${qs}`}`);
|
|
191
|
+
},
|
|
192
|
+
get: (id) => json('GET', idPath(id)),
|
|
193
|
+
forget: async (id) => {
|
|
194
|
+
await call('DELETE', idPath(id));
|
|
195
|
+
},
|
|
196
|
+
pin: async (id, pinned) => {
|
|
197
|
+
await call('POST', `${idPath(id)}/${pinned ? 'pin' : 'unpin'}`);
|
|
198
|
+
},
|
|
199
|
+
stats: () => json('GET', '/v1/stats'),
|
|
200
|
+
exportText: async () => (await call('GET', '/v1/export')).text(),
|
|
201
|
+
importLines: async (lines, from = 0) => (await (await call('POST', `/v1/import?from=${from}`, {
|
|
202
|
+
text: lines.map((l) => `${l}\n`).join(''),
|
|
203
|
+
})).json()),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** The shortest id prefix the API looks up (it answers 404 below this). */
|
|
207
|
+
export const MIN_ID_PREFIX = 8;
|
|
208
|
+
/**
|
|
209
|
+
* A memory by id or unique id prefix (what `oort inspect` prints), looked up by the API itself:
|
|
210
|
+
* a prefix that matches several memories is refused with a clear message.
|
|
211
|
+
*/
|
|
212
|
+
export async function findCloudMemory(client, ref) {
|
|
213
|
+
try {
|
|
214
|
+
return await client.get(ref);
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
if (e instanceof OortCloudError && e.kind === 'not-found' && ref.length < MIN_ID_PREFIX) {
|
|
218
|
+
throw new Refusal(`Give at least ${MIN_ID_PREFIX} characters of the memory id.`);
|
|
219
|
+
}
|
|
220
|
+
throw e;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=cloud.js.map
|
|
@@ -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>;
|