usemycontext 1.0.0 → 1.0.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/README.md +121 -0
- package/dist/client-mXQsH2mZ.d.cts +217 -0
- package/dist/client-mXQsH2mZ.d.ts +217 -0
- package/dist/index.cjs +444 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +430 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +526 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +49 -0
- package/dist/react.d.ts +49 -0
- package/dist/react.js +503 -0
- package/dist/react.js.map +1 -0
- package/package.json +52 -16
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { C as ClientOptions, d as ConnectState, U as UseMyContextClient, c as ConnectArgs } from './client-mXQsH2mZ.cjs';
|
|
3
|
+
|
|
4
|
+
/** What the React hook returns: the live state + the client's bound methods. */
|
|
5
|
+
interface UseMyContextReactValue {
|
|
6
|
+
/** The current connect state (re-renders on change). */
|
|
7
|
+
state: ConnectState;
|
|
8
|
+
/** Convenience: true iff `state === "connected"`. */
|
|
9
|
+
connected: boolean;
|
|
10
|
+
/** The held token when connected, else null. */
|
|
11
|
+
token: string | null;
|
|
12
|
+
/** The underlying framework-free client (the 7 wrappers live here). */
|
|
13
|
+
client: UseMyContextClient;
|
|
14
|
+
/** v1 TOKEN-IN connect. */
|
|
15
|
+
connect: (args: ConnectArgs) => Promise<ConnectState>;
|
|
16
|
+
/** Clear the token; -> `disconnected`. */
|
|
17
|
+
disconnect: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The React hook. Creates ONE stable client per component instance and
|
|
21
|
+
* subscribes to its state so the component re-renders on every transition.
|
|
22
|
+
*/
|
|
23
|
+
declare function useMyContextReact(options?: ClientOptions): UseMyContextReactValue;
|
|
24
|
+
/** Props for the drop-in button. */
|
|
25
|
+
interface ConnectMyContextProps {
|
|
26
|
+
/**
|
|
27
|
+
* v1 is TOKEN-IN: supply the user's token (the full browser-OAuth popup is
|
|
28
|
+
* v2). When given, the button calls `connect({ token })` on click.
|
|
29
|
+
*/
|
|
30
|
+
token?: string;
|
|
31
|
+
/** Forwarded to the underlying client (endpoint / transport / storage). */
|
|
32
|
+
options?: ClientOptions;
|
|
33
|
+
/** Called after a successful connect. */
|
|
34
|
+
onConnect?: (state: ConnectState) => void;
|
|
35
|
+
/** Called when a connect fails. */
|
|
36
|
+
onError?: (error: unknown) => void;
|
|
37
|
+
/** Override the button label per state. */
|
|
38
|
+
label?: Partial<Record<ConnectState, string>>;
|
|
39
|
+
/** Extra class name(s) for the button. */
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A zero-config connect button. Renders a single <button> whose label tracks the
|
|
44
|
+
* connect state; clicking it runs the v1 token-in connect. Unstyled beyond a
|
|
45
|
+
* stable class name (`umc-connect-button`) so the host owns the look.
|
|
46
|
+
*/
|
|
47
|
+
declare function ConnectMyContext(props: ConnectMyContextProps): React.ReactElement;
|
|
48
|
+
|
|
49
|
+
export { ConnectMyContext, type ConnectMyContextProps, type UseMyContextReactValue, useMyContextReact };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { C as ClientOptions, d as ConnectState, U as UseMyContextClient, c as ConnectArgs } from './client-mXQsH2mZ.js';
|
|
3
|
+
|
|
4
|
+
/** What the React hook returns: the live state + the client's bound methods. */
|
|
5
|
+
interface UseMyContextReactValue {
|
|
6
|
+
/** The current connect state (re-renders on change). */
|
|
7
|
+
state: ConnectState;
|
|
8
|
+
/** Convenience: true iff `state === "connected"`. */
|
|
9
|
+
connected: boolean;
|
|
10
|
+
/** The held token when connected, else null. */
|
|
11
|
+
token: string | null;
|
|
12
|
+
/** The underlying framework-free client (the 7 wrappers live here). */
|
|
13
|
+
client: UseMyContextClient;
|
|
14
|
+
/** v1 TOKEN-IN connect. */
|
|
15
|
+
connect: (args: ConnectArgs) => Promise<ConnectState>;
|
|
16
|
+
/** Clear the token; -> `disconnected`. */
|
|
17
|
+
disconnect: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The React hook. Creates ONE stable client per component instance and
|
|
21
|
+
* subscribes to its state so the component re-renders on every transition.
|
|
22
|
+
*/
|
|
23
|
+
declare function useMyContextReact(options?: ClientOptions): UseMyContextReactValue;
|
|
24
|
+
/** Props for the drop-in button. */
|
|
25
|
+
interface ConnectMyContextProps {
|
|
26
|
+
/**
|
|
27
|
+
* v1 is TOKEN-IN: supply the user's token (the full browser-OAuth popup is
|
|
28
|
+
* v2). When given, the button calls `connect({ token })` on click.
|
|
29
|
+
*/
|
|
30
|
+
token?: string;
|
|
31
|
+
/** Forwarded to the underlying client (endpoint / transport / storage). */
|
|
32
|
+
options?: ClientOptions;
|
|
33
|
+
/** Called after a successful connect. */
|
|
34
|
+
onConnect?: (state: ConnectState) => void;
|
|
35
|
+
/** Called when a connect fails. */
|
|
36
|
+
onError?: (error: unknown) => void;
|
|
37
|
+
/** Override the button label per state. */
|
|
38
|
+
label?: Partial<Record<ConnectState, string>>;
|
|
39
|
+
/** Extra class name(s) for the button. */
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A zero-config connect button. Renders a single <button> whose label tracks the
|
|
44
|
+
* connect state; clicking it runs the v1 token-in connect. Unstyled beyond a
|
|
45
|
+
* stable class name (`umc-connect-button`) so the host owns the look.
|
|
46
|
+
*/
|
|
47
|
+
declare function ConnectMyContext(props: ConnectMyContextProps): React.ReactElement;
|
|
48
|
+
|
|
49
|
+
export { ConnectMyContext, type ConnectMyContextProps, type UseMyContextReactValue, useMyContextReact };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
// src/react.tsx
|
|
4
|
+
|
|
5
|
+
// src/jsonrpc.ts
|
|
6
|
+
var UseMyContextError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "UseMyContextError";
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var NotConnectedError = class extends UseMyContextError {
|
|
14
|
+
constructor(message = "Not connected. Call connect({ token }) first.") {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "NotConnectedError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var TokenExpiredError = class extends UseMyContextError {
|
|
20
|
+
constructor(message = "The connection token was rejected (expired or revoked).", wwwAuthenticate) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "TokenExpiredError";
|
|
23
|
+
this.wwwAuthenticate = wwwAuthenticate;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var ScopeDeniedError = class extends UseMyContextError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = -32604;
|
|
30
|
+
this.name = "ScopeDeniedError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var ToolCallError = class extends UseMyContextError {
|
|
34
|
+
constructor(message, code) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "ToolCallError";
|
|
37
|
+
this.code = code;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var TransportError = class extends UseMyContextError {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "TransportError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var nextId = 1;
|
|
47
|
+
function initializeRequest() {
|
|
48
|
+
return {
|
|
49
|
+
jsonrpc: "2.0",
|
|
50
|
+
id: nextId++,
|
|
51
|
+
method: "initialize",
|
|
52
|
+
params: {
|
|
53
|
+
protocolVersion: "2025-06-18",
|
|
54
|
+
capabilities: {},
|
|
55
|
+
clientInfo: { name: "usemycontext-sdk", version: "0.1.0" }
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function toolCallRequest(name, args) {
|
|
60
|
+
return {
|
|
61
|
+
jsonrpc: "2.0",
|
|
62
|
+
id: nextId++,
|
|
63
|
+
method: "tools/call",
|
|
64
|
+
params: { name, arguments: args }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function asJsonRpcResponse(body) {
|
|
68
|
+
if (body === null || typeof body !== "object") {
|
|
69
|
+
throw new TransportError("Membrane returned a non-object response body.");
|
|
70
|
+
}
|
|
71
|
+
const b = body;
|
|
72
|
+
if (b.jsonrpc !== "2.0") {
|
|
73
|
+
throw new TransportError("Membrane response was not JSON-RPC 2.0.");
|
|
74
|
+
}
|
|
75
|
+
return b;
|
|
76
|
+
}
|
|
77
|
+
function isScopeDenied(code) {
|
|
78
|
+
return code === -32604;
|
|
79
|
+
}
|
|
80
|
+
function resultText(result) {
|
|
81
|
+
for (const block of result.content ?? []) {
|
|
82
|
+
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
83
|
+
}
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/machine.ts
|
|
88
|
+
var INITIAL_STATE = "idle";
|
|
89
|
+
function reduce(state, event) {
|
|
90
|
+
switch (event.type) {
|
|
91
|
+
case "CONNECT":
|
|
92
|
+
return state === "connecting" ? state : "connecting";
|
|
93
|
+
case "CONNECT_SUCCESS":
|
|
94
|
+
return state === "connecting" ? "connected" : state;
|
|
95
|
+
case "CONNECT_DECLINED":
|
|
96
|
+
return state === "connecting" ? "declined" : state;
|
|
97
|
+
case "ERROR":
|
|
98
|
+
return state === "connecting" ? "error" : state;
|
|
99
|
+
case "TOKEN_EXPIRED":
|
|
100
|
+
return state === "connecting" || state === "connected" ? "expired" : state;
|
|
101
|
+
case "DISCONNECT":
|
|
102
|
+
return state === "disconnected" ? state : "disconnected";
|
|
103
|
+
default: {
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isConnected(state) {
|
|
109
|
+
return state === "connected";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/storage.ts
|
|
113
|
+
var DEFAULT_STORAGE_KEY = "usemycontext.token";
|
|
114
|
+
function memoryStorage() {
|
|
115
|
+
const map = /* @__PURE__ */ new Map();
|
|
116
|
+
return {
|
|
117
|
+
get: (k) => map.has(k) ? map.get(k) : null,
|
|
118
|
+
set: (k, v) => void map.set(k, v),
|
|
119
|
+
remove: (k) => void map.delete(k)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function webStorage(store) {
|
|
123
|
+
return {
|
|
124
|
+
get: (k) => {
|
|
125
|
+
try {
|
|
126
|
+
return store.getItem(k);
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
set: (k, v) => {
|
|
132
|
+
try {
|
|
133
|
+
store.setItem(k, v);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
remove: (k) => {
|
|
138
|
+
try {
|
|
139
|
+
store.removeItem(k);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function resolveStorage(opt) {
|
|
146
|
+
if (opt === null) {
|
|
147
|
+
return { get: () => null, set: () => {
|
|
148
|
+
}, remove: () => {
|
|
149
|
+
} };
|
|
150
|
+
}
|
|
151
|
+
if (opt) return opt;
|
|
152
|
+
try {
|
|
153
|
+
if (typeof globalThis !== "undefined") {
|
|
154
|
+
const ls = globalThis.localStorage;
|
|
155
|
+
if (ls) {
|
|
156
|
+
const probe = "__umc_probe__";
|
|
157
|
+
ls.setItem(probe, "1");
|
|
158
|
+
ls.removeItem(probe);
|
|
159
|
+
return webStorage(ls);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
return memoryStorage();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/transport.ts
|
|
168
|
+
var fetchTransport = async ({ url, token, body }) => {
|
|
169
|
+
const res = await fetch(url, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"content-type": "application/json",
|
|
173
|
+
accept: "application/json",
|
|
174
|
+
authorization: `Bearer ${token}`
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify(body)
|
|
177
|
+
});
|
|
178
|
+
let parsed = null;
|
|
179
|
+
try {
|
|
180
|
+
parsed = await res.json();
|
|
181
|
+
} catch {
|
|
182
|
+
parsed = null;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
status: res.status,
|
|
186
|
+
body: parsed,
|
|
187
|
+
wwwAuthenticate: res.headers.get("www-authenticate")
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/client.ts
|
|
192
|
+
var DEFAULT_ENDPOINT = "https://mcp.usemycontext.ai/mcp";
|
|
193
|
+
function useMyContext(options = {}) {
|
|
194
|
+
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
195
|
+
const transport = options.transport ?? fetchTransport;
|
|
196
|
+
const storage = resolveStorage(options.storage);
|
|
197
|
+
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
198
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
199
|
+
let state = INITIAL_STATE;
|
|
200
|
+
let token = null;
|
|
201
|
+
const persisted = storage.get(storageKey);
|
|
202
|
+
if (persisted) {
|
|
203
|
+
token = persisted;
|
|
204
|
+
state = "connected";
|
|
205
|
+
}
|
|
206
|
+
function setState(next) {
|
|
207
|
+
if (next === state) return;
|
|
208
|
+
state = next;
|
|
209
|
+
options.onStateChange?.(state);
|
|
210
|
+
for (const l of listeners) l(state);
|
|
211
|
+
}
|
|
212
|
+
function dispatch(event) {
|
|
213
|
+
setState(reduce(state, event));
|
|
214
|
+
}
|
|
215
|
+
async function rpc(body) {
|
|
216
|
+
if (!token) throw new NotConnectedError();
|
|
217
|
+
let res;
|
|
218
|
+
try {
|
|
219
|
+
res = await transport({ url: endpoint, token, body });
|
|
220
|
+
} catch (e) {
|
|
221
|
+
throw new TransportError(
|
|
222
|
+
`Transport failed: ${e instanceof Error ? e.message : String(e)}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (res.status === 401) {
|
|
226
|
+
dispatch({ type: "TOKEN_EXPIRED" });
|
|
227
|
+
throw new TokenExpiredError(void 0, res.wwwAuthenticate ?? void 0);
|
|
228
|
+
}
|
|
229
|
+
const parsed = asJsonRpcResponse(res.body);
|
|
230
|
+
if (parsed.error) {
|
|
231
|
+
if (isScopeDenied(parsed.error.code)) {
|
|
232
|
+
throw new ScopeDeniedError(parsed.error.message);
|
|
233
|
+
}
|
|
234
|
+
throw new ToolCallError(parsed.error.message, parsed.error.code);
|
|
235
|
+
}
|
|
236
|
+
return parsed.result;
|
|
237
|
+
}
|
|
238
|
+
async function callTool(name, args) {
|
|
239
|
+
if (!isConnected(state)) throw new NotConnectedError();
|
|
240
|
+
const result = await rpc(toolCallRequest(name, args));
|
|
241
|
+
if (result.isError) {
|
|
242
|
+
throw new ToolCallError(resultText(result) || `${name} failed.`);
|
|
243
|
+
}
|
|
244
|
+
return resultText(result);
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
get state() {
|
|
248
|
+
return state;
|
|
249
|
+
},
|
|
250
|
+
get connected() {
|
|
251
|
+
return state === "connected";
|
|
252
|
+
},
|
|
253
|
+
get token() {
|
|
254
|
+
return state === "connected" ? token : null;
|
|
255
|
+
},
|
|
256
|
+
subscribe(listener) {
|
|
257
|
+
listeners.add(listener);
|
|
258
|
+
return () => void listeners.delete(listener);
|
|
259
|
+
},
|
|
260
|
+
async connect({ token: t }) {
|
|
261
|
+
if (typeof t !== "string" || t.length === 0) {
|
|
262
|
+
throw new UseMyContextConnectArgError();
|
|
263
|
+
}
|
|
264
|
+
dispatch({ type: "CONNECT" });
|
|
265
|
+
token = t;
|
|
266
|
+
try {
|
|
267
|
+
const res = await transport({ url: endpoint, token: t, body: initializeRequest() });
|
|
268
|
+
if (res.status === 401) {
|
|
269
|
+
token = null;
|
|
270
|
+
dispatch({ type: "TOKEN_EXPIRED" });
|
|
271
|
+
throw new TokenExpiredError(void 0, res.wwwAuthenticate ?? void 0);
|
|
272
|
+
}
|
|
273
|
+
const parsed = asJsonRpcResponse(res.body);
|
|
274
|
+
if (parsed.error) {
|
|
275
|
+
token = null;
|
|
276
|
+
dispatch({ type: "ERROR" });
|
|
277
|
+
throw new ToolCallError(parsed.error.message, parsed.error.code);
|
|
278
|
+
}
|
|
279
|
+
dispatch({ type: "CONNECT_SUCCESS" });
|
|
280
|
+
if (storage) storage.set(storageKey, t);
|
|
281
|
+
return state;
|
|
282
|
+
} catch (e) {
|
|
283
|
+
token = null;
|
|
284
|
+
if (!(e instanceof TokenExpiredError)) {
|
|
285
|
+
dispatch({ type: "ERROR" });
|
|
286
|
+
if (e instanceof TransportError || e instanceof ToolCallError) throw e;
|
|
287
|
+
throw new TransportError(
|
|
288
|
+
`Connect failed: ${e instanceof Error ? e.message : String(e)}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
disconnect() {
|
|
295
|
+
token = null;
|
|
296
|
+
storage.remove(storageKey);
|
|
297
|
+
dispatch({ type: "DISCONNECT" });
|
|
298
|
+
},
|
|
299
|
+
// --- profile ---------------------------------------------------------
|
|
300
|
+
async profile() {
|
|
301
|
+
const text = await callTool("profile", {});
|
|
302
|
+
const facts = tryParseFacts(text);
|
|
303
|
+
if (facts) return { composite: text, facts };
|
|
304
|
+
return { composite: text };
|
|
305
|
+
},
|
|
306
|
+
// --- list_files / search_files (JSON array of FileMeta) --------------
|
|
307
|
+
async listFiles() {
|
|
308
|
+
return parseFileList(await callTool("list_files", {}));
|
|
309
|
+
},
|
|
310
|
+
async searchFiles(query) {
|
|
311
|
+
if (typeof query !== "string") throw new UseMyContextConnectArgError("search query must be a string");
|
|
312
|
+
return parseFileList(await callTool("search_files", { query }));
|
|
313
|
+
},
|
|
314
|
+
// --- get_file --------------------------------------------------------
|
|
315
|
+
async getFile(fileId) {
|
|
316
|
+
if (typeof fileId !== "string" || !fileId) {
|
|
317
|
+
throw new UseMyContextConnectArgError("fileId must be a non-empty string");
|
|
318
|
+
}
|
|
319
|
+
const text = await callTool("get_file", { fileId });
|
|
320
|
+
return { text, downloadUrl: parseDownloadUrl(text) };
|
|
321
|
+
},
|
|
322
|
+
// --- ask_docs --------------------------------------------------------
|
|
323
|
+
async ask(query, opts = {}) {
|
|
324
|
+
if (typeof query !== "string" || !query) {
|
|
325
|
+
throw new UseMyContextConnectArgError("ask query must be a non-empty string");
|
|
326
|
+
}
|
|
327
|
+
const args = { query };
|
|
328
|
+
if (typeof opts.k === "number") args.k = opts.k;
|
|
329
|
+
if (typeof opts.projectId === "string") args.projectId = opts.projectId;
|
|
330
|
+
const text = await callTool("ask_docs", args);
|
|
331
|
+
return { passages: parseAskPassages(text), text };
|
|
332
|
+
},
|
|
333
|
+
// --- suggest_update (the one write) ----------------------------------
|
|
334
|
+
async suggestUpdate(suggestion) {
|
|
335
|
+
if (typeof suggestion !== "string" || !suggestion.trim()) {
|
|
336
|
+
throw new UseMyContextConnectArgError("suggestion must be a non-empty string");
|
|
337
|
+
}
|
|
338
|
+
const text = await callTool("suggest_update", { suggestion });
|
|
339
|
+
const m = text.match(/\(id:\s*([^)]+)\)/);
|
|
340
|
+
return { id: m ? m[1].trim() : void 0, text };
|
|
341
|
+
},
|
|
342
|
+
// --- shared_context --------------------------------------------------
|
|
343
|
+
async sharedContext(from) {
|
|
344
|
+
const args = {};
|
|
345
|
+
if (typeof from === "string" && from) args.from = from;
|
|
346
|
+
const text = await callTool("shared_context", args);
|
|
347
|
+
return from ? parseSharedRead(text) : parseSharedList(text);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
var UseMyContextConnectArgError = class extends UseMyContextError {
|
|
352
|
+
constructor(message = "connect() requires a non-empty token string.") {
|
|
353
|
+
super(message);
|
|
354
|
+
this.name = "UseMyContextConnectArgError";
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
function tryParseFacts(text) {
|
|
358
|
+
const trimmed = text.trimStart();
|
|
359
|
+
if (!trimmed.startsWith("{")) return void 0;
|
|
360
|
+
try {
|
|
361
|
+
const obj = JSON.parse(text);
|
|
362
|
+
if (Array.isArray(obj.facts)) return obj.facts.filter((f) => typeof f === "string");
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
367
|
+
function parseFileList(text) {
|
|
368
|
+
try {
|
|
369
|
+
const arr = JSON.parse(text);
|
|
370
|
+
if (!Array.isArray(arr)) return [];
|
|
371
|
+
return arr.map((r) => {
|
|
372
|
+
const o = r ?? {};
|
|
373
|
+
return {
|
|
374
|
+
id: String(o.id ?? ""),
|
|
375
|
+
name: String(o.name ?? ""),
|
|
376
|
+
size: typeof o.size === "number" ? o.size : 0,
|
|
377
|
+
contentType: String(o.contentType ?? ""),
|
|
378
|
+
modified: String(o.modified ?? ""),
|
|
379
|
+
...typeof o.status === "string" ? { status: o.status } : {}
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
} catch {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function parseDownloadUrl(text) {
|
|
387
|
+
const m = text.match(/Download link[^:]*:\s*(\S+)/);
|
|
388
|
+
return m ? m[1] : void 0;
|
|
389
|
+
}
|
|
390
|
+
function parseAskPassages(text) {
|
|
391
|
+
if (/^No relevant passages/i.test(text.trimStart())) return [];
|
|
392
|
+
const blocks = text.split("\n\n");
|
|
393
|
+
const out = [];
|
|
394
|
+
for (const block of blocks) {
|
|
395
|
+
const nl = block.indexOf("\n");
|
|
396
|
+
const citation = nl === -1 ? block : block.slice(0, nl);
|
|
397
|
+
const body = nl === -1 ? "" : block.slice(nl + 1);
|
|
398
|
+
const m = citation.match(/^\[(\d+)\]\s*(.*)$/);
|
|
399
|
+
if (!m) continue;
|
|
400
|
+
const index = Number(m[1]);
|
|
401
|
+
const rest = m[2];
|
|
402
|
+
const parts = rest.split(" \xB7 ");
|
|
403
|
+
const passage = { index, text: body };
|
|
404
|
+
if (parts[0]) passage.file = parts[0].trim();
|
|
405
|
+
if (parts[1]) passage.scope = parts[1].trim();
|
|
406
|
+
const scoreM = rest.match(/score\s+([\d.]+)/);
|
|
407
|
+
if (scoreM) passage.score = Number(scoreM[1]);
|
|
408
|
+
out.push(passage);
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
function parseSharedList(text) {
|
|
413
|
+
if (/^No one has shared/i.test(text.trimStart())) {
|
|
414
|
+
return { mode: "list", shares: [], composite: null, text };
|
|
415
|
+
}
|
|
416
|
+
const shares = [];
|
|
417
|
+
for (const line of text.split("\n")) {
|
|
418
|
+
const m = line.match(/^-\s*(.*?)\s*\(from:\s*([^)]+)\)\s*$/);
|
|
419
|
+
if (m) shares.push({ ownerLabel: m[1].trim(), grantId: m[2].trim() });
|
|
420
|
+
}
|
|
421
|
+
return { mode: "list", shares, composite: null, text };
|
|
422
|
+
}
|
|
423
|
+
function parseSharedRead(text) {
|
|
424
|
+
if (/not available/i.test(text)) {
|
|
425
|
+
return { mode: "read", shares: [], composite: null, text };
|
|
426
|
+
}
|
|
427
|
+
const m = text.match(/^Shared context from [^:]*:\n\n([\s\S]*)$/);
|
|
428
|
+
const composite = m ? m[1] : text;
|
|
429
|
+
return { mode: "read", shares: [], composite, text };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/react.tsx
|
|
433
|
+
function useMyContextReact(options = {}) {
|
|
434
|
+
const clientRef = React.useRef(null);
|
|
435
|
+
if (clientRef.current === null) {
|
|
436
|
+
clientRef.current = useMyContext(options);
|
|
437
|
+
}
|
|
438
|
+
const client = clientRef.current;
|
|
439
|
+
const [state, setState] = React.useState(client.state);
|
|
440
|
+
React.useEffect(() => {
|
|
441
|
+
setState(client.state);
|
|
442
|
+
const unsub = client.subscribe(setState);
|
|
443
|
+
return unsub;
|
|
444
|
+
}, [client]);
|
|
445
|
+
const connect = React.useCallback((args) => client.connect(args), [client]);
|
|
446
|
+
const disconnect = React.useCallback(() => client.disconnect(), [client]);
|
|
447
|
+
return {
|
|
448
|
+
state,
|
|
449
|
+
connected: state === "connected",
|
|
450
|
+
token: client.token,
|
|
451
|
+
client,
|
|
452
|
+
connect,
|
|
453
|
+
disconnect
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
var DEFAULT_LABELS = {
|
|
457
|
+
idle: "Connect my context",
|
|
458
|
+
connecting: "Connecting...",
|
|
459
|
+
connected: "Connected",
|
|
460
|
+
declined: "Connect my context",
|
|
461
|
+
expired: "Reconnect my context",
|
|
462
|
+
error: "Retry connect",
|
|
463
|
+
disconnected: "Connect my context"
|
|
464
|
+
};
|
|
465
|
+
function ConnectMyContext(props) {
|
|
466
|
+
const { token, options, onConnect, onError, label, className } = props;
|
|
467
|
+
const { state, connect, disconnect } = useMyContextReact(options);
|
|
468
|
+
const text = label && label[state] || DEFAULT_LABELS[state];
|
|
469
|
+
const busy = state === "connecting";
|
|
470
|
+
const isConnected2 = state === "connected";
|
|
471
|
+
const onClick = React.useCallback(async () => {
|
|
472
|
+
if (isConnected2) {
|
|
473
|
+
disconnect();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (!token) {
|
|
477
|
+
onError?.(new Error("ConnectMyContext: no `token` prop (v1 is token-in only)."));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const next = await connect({ token });
|
|
482
|
+
onConnect?.(next);
|
|
483
|
+
} catch (e) {
|
|
484
|
+
onError?.(e);
|
|
485
|
+
}
|
|
486
|
+
}, [isConnected2, disconnect, token, connect, onConnect, onError]);
|
|
487
|
+
return React.createElement(
|
|
488
|
+
"button",
|
|
489
|
+
{
|
|
490
|
+
type: "button",
|
|
491
|
+
className: ["umc-connect-button", className].filter(Boolean).join(" "),
|
|
492
|
+
"data-umc-state": state,
|
|
493
|
+
disabled: busy,
|
|
494
|
+
"aria-busy": busy || void 0,
|
|
495
|
+
onClick
|
|
496
|
+
},
|
|
497
|
+
isConnected2 ? "Disconnect" : text
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export { ConnectMyContext, useMyContextReact };
|
|
502
|
+
//# sourceMappingURL=react.js.map
|
|
503
|
+
//# sourceMappingURL=react.js.map
|