@tryglen/cli 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.
Files changed (61) hide show
  1. package/dist/_utils/build-connect-url.d.ts +8 -0
  2. package/dist/_utils/build-connect-url.js +9 -0
  3. package/dist/_utils/build-connect-url.js.map +1 -0
  4. package/dist/_utils/build-turn-body.d.ts +8 -0
  5. package/dist/_utils/build-turn-body.js +13 -0
  6. package/dist/_utils/build-turn-body.js.map +1 -0
  7. package/dist/_utils/config.d.ts +10 -0
  8. package/dist/_utils/config.js +12 -0
  9. package/dist/_utils/config.js.map +1 -0
  10. package/dist/_utils/credentials.d.ts +15 -0
  11. package/dist/_utils/credentials.js +60 -0
  12. package/dist/_utils/credentials.js.map +1 -0
  13. package/dist/_utils/format-memory.d.ts +2 -0
  14. package/dist/_utils/format-memory.js +11 -0
  15. package/dist/_utils/format-memory.js.map +1 -0
  16. package/dist/_utils/http.d.ts +15 -0
  17. package/dist/_utils/http.js +41 -0
  18. package/dist/_utils/http.js.map +1 -0
  19. package/dist/_utils/idempotency-key.d.ts +2 -0
  20. package/dist/_utils/idempotency-key.js +6 -0
  21. package/dist/_utils/idempotency-key.js.map +1 -0
  22. package/dist/_utils/login-lock.d.ts +3 -0
  23. package/dist/_utils/login-lock.js +32 -0
  24. package/dist/_utils/login-lock.js.map +1 -0
  25. package/dist/_utils/loopback-server.d.ts +10 -0
  26. package/dist/_utils/loopback-server.js +48 -0
  27. package/dist/_utils/loopback-server.js.map +1 -0
  28. package/dist/_utils/open-browser.d.ts +2 -0
  29. package/dist/_utils/open-browser.js +17 -0
  30. package/dist/_utils/open-browser.js.map +1 -0
  31. package/dist/_utils/parse-transcript.d.ts +6 -0
  32. package/dist/_utils/parse-transcript.js +40 -0
  33. package/dist/_utils/parse-transcript.js.map +1 -0
  34. package/dist/_utils/render-callback-page.d.ts +2 -0
  35. package/dist/_utils/render-callback-page.js +97 -0
  36. package/dist/_utils/render-callback-page.js.map +1 -0
  37. package/dist/_utils/result.d.ts +14 -0
  38. package/dist/_utils/result.js +12 -0
  39. package/dist/_utils/result.js.map +1 -0
  40. package/dist/bin.d.ts +2 -0
  41. package/dist/bin.js +65 -0
  42. package/dist/bin.js.map +1 -0
  43. package/dist/commands/ingest.d.ts +7 -0
  44. package/dist/commands/ingest.js +73 -0
  45. package/dist/commands/ingest.js.map +1 -0
  46. package/dist/commands/install.d.ts +3 -0
  47. package/dist/commands/install.js +78 -0
  48. package/dist/commands/install.js.map +1 -0
  49. package/dist/commands/login.d.ts +3 -0
  50. package/dist/commands/login.js +61 -0
  51. package/dist/commands/login.js.map +1 -0
  52. package/dist/commands/logout.d.ts +3 -0
  53. package/dist/commands/logout.js +11 -0
  54. package/dist/commands/logout.js.map +1 -0
  55. package/dist/commands/search.d.ts +6 -0
  56. package/dist/commands/search.js +41 -0
  57. package/dist/commands/search.js.map +1 -0
  58. package/dist/commands/status.d.ts +3 -0
  59. package/dist/commands/status.js +14 -0
  60. package/dist/commands/status.js.map +1 -0
  61. package/package.json +33 -0
@@ -0,0 +1,8 @@
1
+ type ConnectUrlParams = {
2
+ readonly connectUrl: string;
3
+ readonly callback: string;
4
+ readonly state: string;
5
+ readonly challenge: string;
6
+ };
7
+ declare const buildConnectUrl: (params: ConnectUrlParams) => string;
8
+ export { buildConnectUrl };
@@ -0,0 +1,9 @@
1
+ const buildConnectUrl = (params) => {
2
+ const url = new URL(params.connectUrl);
3
+ url.searchParams.set("callback", params.callback);
4
+ url.searchParams.set("state", params.state);
5
+ url.searchParams.set("challenge", params.challenge);
6
+ return url.toString();
7
+ };
8
+ export { buildConnectUrl };
9
+ //# sourceMappingURL=build-connect-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-connect-url.js","sourceRoot":"","sources":["../../src/_utils/build-connect-url.ts"],"names":[],"mappings":"AAOA,MAAM,eAAe,GAAG,CAAC,MAAwB,EAAU,EAAE;IAC3D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACvC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ type TurnBodyParams = {
2
+ readonly sessionId: string;
3
+ readonly prompt: string;
4
+ readonly priorAssistant: string | null;
5
+ readonly nextUserMessageIndex: number;
6
+ };
7
+ declare const buildTurnBody: (params: TurnBodyParams) => Record<string, unknown>;
8
+ export { buildTurnBody };
@@ -0,0 +1,13 @@
1
+ import { idempotencyKey } from "./idempotency-key.js";
2
+ const buildTurnBody = (params) => ({
3
+ conversationId: params.sessionId,
4
+ idempotencyKey: idempotencyKey(params.sessionId, params.nextUserMessageIndex),
5
+ messages: [
6
+ ...(params.priorAssistant
7
+ ? [{ role: "assistant", content: params.priorAssistant }]
8
+ : []),
9
+ { role: "user", content: params.prompt },
10
+ ],
11
+ });
12
+ export { buildTurnBody };
13
+ //# sourceMappingURL=build-turn-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-turn-body.js","sourceRoot":"","sources":["../../src/_utils/build-turn-body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAStD,MAAM,aAAa,GAAG,CAAC,MAAsB,EAA2B,EAAE,CAAC,CAAC;IAC1E,cAAc,EAAE,MAAM,CAAC,SAAS;IAChC,cAAc,EAAE,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,oBAAoB,CAAC;IAC7E,QAAQ,EAAE;QACR,GAAG,CAAC,MAAM,CAAC,cAAc;YACvB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;YACzD,CAAC,CAAC,EAAE,CAAC;QACP,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;KACzC;CACF,CAAC,CAAC;AAEH,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ declare const config: {
2
+ baseUrl: string;
3
+ connectUrl: string;
4
+ exchangeUrl: string;
5
+ turnUrl: string;
6
+ searchUrl: string;
7
+ ingestTimeoutMs: number;
8
+ searchTimeoutMs: number;
9
+ };
10
+ export { config };
@@ -0,0 +1,12 @@
1
+ const baseUrl = process.env.GLEN_BASE_URL ?? "https://app.tryglen.com";
2
+ const config = {
3
+ baseUrl,
4
+ connectUrl: `${baseUrl}/connect/cli`,
5
+ exchangeUrl: `${baseUrl}/api/connect/cli/exchange`,
6
+ turnUrl: `${baseUrl}/api/mcp/route/turn`,
7
+ searchUrl: `${baseUrl}/api/mcp/route/search`,
8
+ ingestTimeoutMs: 8000,
9
+ searchTimeoutMs: 15_000,
10
+ };
11
+ export { config };
12
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/_utils/config.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,yBAAyB,CAAC;AAEvE,MAAM,MAAM,GAAG;IACb,OAAO;IACP,UAAU,EAAE,GAAG,OAAO,cAAc;IACpC,WAAW,EAAE,GAAG,OAAO,2BAA2B;IAClD,OAAO,EAAE,GAAG,OAAO,qBAAqB;IACxC,SAAS,EAAE,GAAG,OAAO,uBAAuB;IAC5C,eAAe,EAAE,IAAI;IACrB,eAAe,EAAE,MAAM;CACxB,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Result } from "./result.js";
2
+ type Credentials = {
3
+ readonly type: "apikey";
4
+ readonly key: string;
5
+ };
6
+ declare const credentialsPath: () => string;
7
+ declare const readCredentials: () => Promise<Credentials | null>;
8
+ declare const writeCredentials: (creds: Credentials) => Promise<Result<void, unknown>>;
9
+ declare const clearCredentials: () => Promise<void>;
10
+ declare const resolveAuthHeader: () => Promise<{
11
+ headerName: "x-api-key";
12
+ headerValue: string;
13
+ } | null>;
14
+ export { credentialsPath, readCredentials, writeCredentials, clearCredentials, resolveAuthHeader, };
15
+ export type { Credentials };
@@ -0,0 +1,60 @@
1
+ import { chmod, mkdir, readFile, rename, unlink, writeFile, } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { fromPromise } from "./result.js";
5
+ const credentialsPath = () => {
6
+ const envPath = process.env.GLEN_CREDENTIALS_PATH;
7
+ if (envPath)
8
+ return envPath;
9
+ return join(homedir(), ".glen", "credentials.json");
10
+ };
11
+ const readCredentials = async () => {
12
+ const filePath = credentialsPath();
13
+ const result = await fromPromise(readFile(filePath, "utf-8"));
14
+ if (!result.ok)
15
+ return null;
16
+ const parsed = await fromPromise(Promise.resolve(JSON.parse(result.value)));
17
+ if (!parsed.ok)
18
+ return null;
19
+ const data = parsed.value;
20
+ if (typeof data === "object" &&
21
+ data !== null &&
22
+ "type" in data &&
23
+ "key" in data &&
24
+ data.type === "apikey" &&
25
+ typeof data.key === "string") {
26
+ return data;
27
+ }
28
+ return null;
29
+ };
30
+ const writeCredentials = async (creds) => {
31
+ const filePath = credentialsPath();
32
+ const dir = dirname(filePath);
33
+ const mkdirResult = await fromPromise(mkdir(dir, { recursive: true, mode: 0o700 }));
34
+ if (!mkdirResult.ok)
35
+ return mkdirResult;
36
+ const tmpPath = `${filePath}.tmp`;
37
+ const json = JSON.stringify(creds, null, 2);
38
+ const writeResult = await fromPromise(writeFile(tmpPath, json, { mode: 0o600 }));
39
+ if (!writeResult.ok)
40
+ return writeResult;
41
+ const chmodResult = await fromPromise(chmod(tmpPath, 0o600));
42
+ if (!chmodResult.ok)
43
+ return chmodResult;
44
+ const renameResult = await fromPromise(rename(tmpPath, filePath));
45
+ if (!renameResult.ok)
46
+ return renameResult;
47
+ return { ok: true, value: undefined };
48
+ };
49
+ const clearCredentials = async () => {
50
+ const filePath = credentialsPath();
51
+ await fromPromise(unlink(filePath));
52
+ };
53
+ const resolveAuthHeader = async () => {
54
+ const creds = await readCredentials();
55
+ if (!creds)
56
+ return null;
57
+ return { headerName: "x-api-key", headerValue: creds.key };
58
+ };
59
+ export { credentialsPath, readCredentials, writeCredentials, clearCredentials, resolveAuthHeader, };
60
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/_utils/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C,MAAM,eAAe,GAAG,GAAW,EAAE;IACnC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAClD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,KAAK,IAAiC,EAAE;IAC9D,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAY,CAAC,CACrD,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC;IAC1B,IACE,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,MAAM,IAAI,IAAI;QACd,KAAK,IAAI,IAAI;QACZ,IAA0B,CAAC,IAAI,KAAK,QAAQ;QAC7C,OAAQ,IAAyB,CAAC,GAAG,KAAK,QAAQ,EAClD,CAAC;QACD,OAAO,IAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,EAC5B,KAAkB,EACc,EAAE;IAClC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE9B,MAAM,WAAW,GAAG,MAAM,WAAW,CACnC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAC7C,CAAC;IACF,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE5C,MAAM,WAAW,GAAG,MAAM,WAAW,CACnC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAC1C,CAAC;IACF,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClE,IAAI,CAAC,YAAY,CAAC,EAAE;QAAE,OAAO,YAAY,CAAC;IAE1C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,IAAmB,EAAE;IACjD,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,MAAM,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,KAAK,IAGrB,EAAE;IACV,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;AAC7D,CAAC,CAAC;AAEF,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,GAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const formatMemory: (result: unknown) => string | null;
2
+ export { formatMemory };
@@ -0,0 +1,11 @@
1
+ const PREFIX = "Relevant glen memory (automatically retrieved for this turn — you already have this; " +
2
+ "do NOT call the glen-search skill just to re-fetch it, only use that skill if you need " +
3
+ "something more specific that isn't below):";
4
+ const formatMemory = (result) => {
5
+ const context = result?.context;
6
+ if (typeof context !== "string" || context.trim() === "")
7
+ return null;
8
+ return `${PREFIX}\n\n${context}`;
9
+ };
10
+ export { formatMemory };
11
+ //# sourceMappingURL=format-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-memory.js","sourceRoot":"","sources":["../../src/_utils/format-memory.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GACV,uFAAuF;IACvF,yFAAyF;IACzF,4CAA4C,CAAC;AAE/C,MAAM,YAAY,GAAG,CAAC,MAAe,EAAiB,EAAE;IACtD,MAAM,OAAO,GAAI,MAAyC,EAAE,OAAO,CAAC;IACpE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACtE,OAAO,GAAG,MAAM,OAAO,OAAO,EAAE,CAAC;AACnC,CAAC,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Result } from "./result.js";
2
+ type HttpResponse = {
3
+ readonly status: number;
4
+ readonly isOk: boolean;
5
+ readonly json: unknown;
6
+ };
7
+ type HttpOptions = {
8
+ readonly method?: string;
9
+ readonly headers?: Record<string, string>;
10
+ readonly body?: string;
11
+ readonly timeoutMs?: number;
12
+ };
13
+ declare const httpJson: (url: string, options?: HttpOptions) => Promise<Result<HttpResponse, unknown>>;
14
+ export { httpJson };
15
+ export type { HttpResponse, HttpOptions };
@@ -0,0 +1,41 @@
1
+ import { fromPromise } from "./result.js";
2
+ const httpJson = async (url, options = {}) => {
3
+ const { method = "GET", headers, body, timeoutMs } = options;
4
+ const controller = new AbortController();
5
+ const timeoutId = timeoutMs
6
+ ? setTimeout(() => controller.abort(), timeoutMs)
7
+ : undefined;
8
+ const fetchResult = await fromPromise(fetch(url, {
9
+ method,
10
+ ...(headers !== undefined ? { headers } : {}),
11
+ body: body ?? null,
12
+ signal: controller.signal,
13
+ }));
14
+ if (timeoutId !== undefined)
15
+ clearTimeout(timeoutId);
16
+ if (!fetchResult.ok)
17
+ return fetchResult;
18
+ const response = fetchResult.value;
19
+ const text = await fromPromise(response.text());
20
+ if (!text.ok)
21
+ return text;
22
+ const rawText = text.value.trim();
23
+ const json = (() => {
24
+ try {
25
+ return rawText.length > 0 ? JSON.parse(rawText) : null;
26
+ }
27
+ catch {
28
+ return null; // non-JSON body (e.g. "Unauthorized") — surface via status/isOk
29
+ }
30
+ })();
31
+ return {
32
+ ok: true,
33
+ value: {
34
+ status: response.status,
35
+ isOk: response.ok,
36
+ json,
37
+ },
38
+ };
39
+ };
40
+ export { httpJson };
41
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/_utils/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB1C,MAAM,QAAQ,GAAG,KAAK,EACpB,GAAW,EACX,UAAuB,EAAE,EACe,EAAE;IAC1C,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE7D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,SAAS;QACzB,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC;QACjD,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,WAAW,GAAG,MAAM,WAAW,CACnC,KAAK,CAAC,GAAG,EAAE;QACT,MAAM;QACN,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,IAAI,EAAE,IAAI,IAAI,IAAI;QAClB,MAAM,EAAE,UAAU,CAAC,MAAM;KAC1B,CAAC,CACH,CAAC;IAEF,IAAI,SAAS,KAAK,SAAS;QAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAErD,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE;QACjB,IAAI,CAAC;YACH,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,gEAAgE;QAC/E,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO;QACL,EAAE,EAAE,IAAI;QACR,KAAK,EAAE;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,IAAI,EAAE,QAAQ,CAAC,EAAE;YACjB,IAAI;SACL;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const idempotencyKey: (sessionId: string, userMessageIndex: number) => string;
2
+ export { idempotencyKey };
@@ -0,0 +1,6 @@
1
+ import { createHash } from "node:crypto";
2
+ const idempotencyKey = (sessionId, userMessageIndex) => createHash("sha256")
3
+ .update(`${sessionId}\0${userMessageIndex}`)
4
+ .digest("hex");
5
+ export { idempotencyKey };
6
+ //# sourceMappingURL=idempotency-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency-key.js","sourceRoot":"","sources":["../../src/_utils/idempotency-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,gBAAwB,EAAU,EAAE,CAC7E,UAAU,CAAC,QAAQ,CAAC;KACjB,MAAM,CAAC,GAAG,SAAS,KAAK,gBAAgB,EAAE,CAAC;KAC3C,MAAM,CAAC,KAAK,CAAC,CAAC;AAEnB,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ declare const acquireLoginLock: () => Promise<boolean>;
2
+ declare const releaseLoginLock: () => Promise<void>;
3
+ export { acquireLoginLock, releaseLoginLock };
@@ -0,0 +1,32 @@
1
+ import { stat, unlink, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { credentialsPath } from "./credentials.js";
4
+ import { fromPromise } from "./result.js";
5
+ const LOCK_TTL_MS = 10 * 1000; // 10 seconds — short so stale locks from crashed hooks don't block login
6
+ const lockPath = () => {
7
+ return join(dirname(credentialsPath()), ".login.lock");
8
+ };
9
+ const acquireLoginLock = async () => {
10
+ const path = lockPath();
11
+ // Attempt exclusive create — fails with EEXIST if lock already held.
12
+ const writeResult = await fromPromise(writeFile(path, String(Date.now()), { flag: "wx" }));
13
+ if (writeResult.ok)
14
+ return true;
15
+ // Lock file exists — check if stale.
16
+ const statResult = await fromPromise(stat(path));
17
+ if (!statResult.ok)
18
+ return false; // disappeared between write and stat — another process won
19
+ const age = Date.now() - statResult.value.mtimeMs;
20
+ if (age < LOCK_TTL_MS)
21
+ return false; // fresh lock held by another process
22
+ // Stale — remove and retry once with exclusive create.
23
+ await fromPromise(unlink(path));
24
+ const retryResult = await fromPromise(writeFile(path, String(Date.now()), { flag: "wx" }));
25
+ return retryResult.ok;
26
+ };
27
+ const releaseLoginLock = async () => {
28
+ const path = lockPath();
29
+ await fromPromise(unlink(path));
30
+ };
31
+ export { acquireLoginLock, releaseLoginLock };
32
+ //# sourceMappingURL=login-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login-lock.js","sourceRoot":"","sources":["../../src/_utils/login-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,yEAAyE;AAExG,MAAM,QAAQ,GAAG,GAAW,EAAE;IAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,IAAsB,EAAE;IACpD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IAExB,qEAAqE;IACrE,MAAM,WAAW,GAAG,MAAM,WAAW,CACnC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CACpD,CAAC;IACF,IAAI,WAAW,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAEhC,qCAAqC;IACrC,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC,CAAC,2DAA2D;IAC7F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;IAClD,IAAI,GAAG,GAAG,WAAW;QAAE,OAAO,KAAK,CAAC,CAAC,qCAAqC;IAE1E,uDAAuD;IACvD,MAAM,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,MAAM,WAAW,GAAG,MAAM,WAAW,CACnC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CACpD,CAAC;IACF,OAAO,WAAW,CAAC,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,KAAK,IAAmB,EAAE;IACjD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ type CallbackResult = {
2
+ readonly code: string | null;
3
+ readonly state: string | null;
4
+ };
5
+ type LoopbackServer = {
6
+ readonly redirectUri: string;
7
+ readonly waitForCallback: () => Promise<CallbackResult>;
8
+ };
9
+ declare const startLoopbackServer: (timeoutMs?: number, expectedState?: string) => Promise<LoopbackServer>;
10
+ export { startLoopbackServer };
@@ -0,0 +1,48 @@
1
+ import { createServer } from "node:http";
2
+ import { renderCallbackPage } from "./render-callback-page.js";
3
+ const startLoopbackServer = (timeoutMs = 300_000, expectedState) => new Promise((resolveStart) => {
4
+ const callback = {
5
+ settled: false,
6
+ resolve: (_v) => { },
7
+ reject: (_e) => { },
8
+ };
9
+ const promise = new Promise((res, rej) => {
10
+ callback.resolve = res;
11
+ callback.reject = rej;
12
+ });
13
+ const server = createServer((req, res) => {
14
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
15
+ if (url.pathname !== "/callback") {
16
+ res.writeHead(404).end();
17
+ return;
18
+ }
19
+ const code = url.searchParams.get("code");
20
+ const state = url.searchParams.get("state");
21
+ const stateOk = !expectedState || state === expectedState;
22
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
23
+ res.end(renderCallbackPage(stateOk && Boolean(code)));
24
+ if (!stateOk || callback.settled)
25
+ return;
26
+ callback.settled = true;
27
+ callback.resolve({ code, state });
28
+ server.closeAllConnections?.();
29
+ server.close();
30
+ });
31
+ const timer = setTimeout(() => {
32
+ if (callback.settled)
33
+ return;
34
+ callback.settled = true;
35
+ callback.reject(new Error("login callback timed out"));
36
+ server.close();
37
+ }, timeoutMs);
38
+ timer.unref();
39
+ server.listen(0, "127.0.0.1", () => {
40
+ const addr = server.address();
41
+ resolveStart({
42
+ redirectUri: `http://127.0.0.1:${addr.port}/callback`,
43
+ waitForCallback: () => promise,
44
+ });
45
+ });
46
+ });
47
+ export { startLoopbackServer };
48
+ //# sourceMappingURL=loopback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback-server.js","sourceRoot":"","sources":["../../src/_utils/loopback-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAW/D,MAAM,mBAAmB,GAAG,CAC1B,SAAS,GAAG,OAAO,EACnB,aAAsB,EACG,EAAE,CAC3B,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;IAC3B,MAAM,QAAQ,GAAG;QACf,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,CAAC,EAAkB,EAAE,EAAE,GAAE,CAAC;QACnC,MAAM,EAAE,CAAC,EAAS,EAAE,EAAE,GAAE,CAAC;KAC1B,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACvD,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAC;QACvB,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;YACjC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,CAAC,aAAa,IAAI,KAAK,KAAK,aAAa,CAAC;QAE1D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEtD,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO;YAAE,OAAO;QACzC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,IAAI,QAAQ,CAAC,OAAO;YAAE,OAAO;QAC7B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC,EAAE,SAAS,CAAC,CAAC;IACd,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAsB,CAAC;QAClD,YAAY,CAAC;YACX,WAAW,EAAE,oBAAoB,IAAI,CAAC,IAAI,WAAW;YACrD,eAAe,EAAE,GAAG,EAAE,CAAC,OAAO;SAC/B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const openBrowser: (url: string) => void;
2
+ export { openBrowser };
@@ -0,0 +1,17 @@
1
+ import { spawn } from "node:child_process";
2
+ const openBrowser = (url) => {
3
+ const cmd = process.platform === "darwin"
4
+ ? "open"
5
+ : process.platform === "win32"
6
+ ? "start"
7
+ : "xdg-open";
8
+ const args = process.platform === "win32" ? ["", url] : [url];
9
+ const child = spawn(cmd, args, {
10
+ stdio: "ignore",
11
+ detached: true,
12
+ shell: process.platform === "win32",
13
+ });
14
+ child.unref();
15
+ };
16
+ export { openBrowser };
17
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../../src/_utils/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,WAAW,GAAG,CAAC,GAAW,EAAQ,EAAE;IACxC,MAAM,GAAG,GACP,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,UAAU,CAAC;IACnB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;QAC7B,KAAK,EAAE,QAAQ;QACf,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;KACpC,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ type TranscriptResult = {
2
+ readonly priorAssistant: string | null;
3
+ readonly nextUserMessageIndex: number;
4
+ };
5
+ declare const parseTranscript: (lines: readonly string[]) => TranscriptResult;
6
+ export { parseTranscript };
@@ -0,0 +1,40 @@
1
+ const extractText = (content) => {
2
+ if (typeof content === "string")
3
+ return content;
4
+ if (!Array.isArray(content))
5
+ return "";
6
+ return content
7
+ .filter((p) => p?.type === "text")
8
+ .map((p) => p.text)
9
+ .join("\n");
10
+ };
11
+ const roleOf = (e) => e?.message?.role ??
12
+ e?.role;
13
+ const contentOf = (e) => extractText(e?.message?.content ?? e?.content);
14
+ const parseLine = (l) => {
15
+ const r = (() => {
16
+ try {
17
+ return JSON.parse(l);
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ })();
23
+ return r;
24
+ };
25
+ const parseTranscript = (lines) => {
26
+ const messages = lines
27
+ .map(parseLine)
28
+ .filter((e) => e !== null)
29
+ .filter((e) => roleOf(e) === "user" || roleOf(e) === "assistant");
30
+ const nextUserMessageIndex = messages.filter((e) => roleOf(e) === "user").length;
31
+ const lastAssistant = [...messages]
32
+ .reverse()
33
+ .find((e) => roleOf(e) === "assistant");
34
+ return {
35
+ priorAssistant: lastAssistant ? contentOf(lastAssistant) : null,
36
+ nextUserMessageIndex,
37
+ };
38
+ };
39
+ export { parseTranscript };
40
+ //# sourceMappingURL=parse-transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-transcript.js","sourceRoot":"","sources":["../../src/_utils/parse-transcript.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,GAAG,CAAC,OAAgB,EAAU,EAAE;IAC/C,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,CAAC,EAAuC,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;SACtE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,CAA0B,EAAsB,EAAE,CAC9D,CAAC,EAAE,OAAmC,EAAE,IAA2B;IACpE,CAAC,EAAE,IAA2B,CAAC;AAElC,MAAM,SAAS,GAAG,CAAC,CAA0B,EAAU,EAAE,CACvD,WAAW,CAAE,CAAC,EAAE,OAAmC,EAAE,OAAO,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAE9E,MAAM,SAAS,GAAG,CAAC,CAAS,EAAkC,EAAE;IAC9D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;QACd,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAA4B,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,KAAwB,EAAoB,EAAE;IACrE,MAAM,QAAQ,GAAG,KAAK;SACnB,GAAG,CAAC,SAAS,CAAC;SACd,MAAM,CAAC,CAAC,CAAC,EAAgC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SACvD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC;IACpE,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAC5B,CAAC,MAAM,CAAC;IACT,MAAM,aAAa,GAAG,CAAC,GAAG,QAAQ,CAAC;SAChC,OAAO,EAAE;SACT,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC;IAC1C,OAAO;QACL,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/D,oBAAoB;KACrB,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ declare const renderCallbackPage: (ok: boolean) => string;
2
+ export { renderCallbackPage };
@@ -0,0 +1,97 @@
1
+ // The HTML the browser lands on after the OAuth handoff redirects to our loopback.
2
+ // Themed to match the glen dashboard: Hanken Grotesk, glen green, stone neutrals,
3
+ // sharp corners (the app's --radius is 0), light/dark via prefers-color-scheme.
4
+ // Two variants: success (code received) and error (provider bounced back, no code).
5
+ // The Glen mark — base64 transparent PNG (96×96), embedded so the mark always
6
+ // renders (mirrors the dashboard/email mark). Fonts load from Google (the browser
7
+ // is online right after the OAuth round-trip) with a system fallback.
8
+ const GLEN_LOGO_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABmJLR0QA/wD/AP+gvaeTAAAE4ElEQVR4nO3bTUxcVRQH8P+5DyiM0kKdeQN0VEhMaEJI1KaYJkZAMMAMJTU6rR900aqNZWWMujA1tosuZNHEmDQ2ph/0ATUTE9NEiW60MSbGr40LVyaatCt0KXQYZt5xgd2QQpnH/XiQ81vzzrmZ//DmvnvvA4QQQgghhBBCCCGEEEIIIYQQQgghhBDbDbkewHaUvDjWqOqWBwAaBtADYO/80bnE3f62xu7AtrdkMNzpkZpgLr8C0H0buUYC0KBlZrQn5PAsGIPM1V0rAWxCy6WRVFhL58IwfBkRb+cSQESpIHs4BJ8H8MBm6kgAVcoU8g2l4uKHAL+mYw4jAVQhGQy1Li0tXCfCfl01JYANSk9nu8MQXxLhQZ11JYANSAYHH2eufE2EpO7aSnfB7cafGj2gUPkG0P/hAxLAulquDHWBwi8A7DLVQ25Ba9g9PZIJmeYA7DbZZ80A/CD7B5jmofhPZv4tDNWvjWHq+7+OXSmaHFAcpAp991NJfQXwQ6Z7rfcfkABxOxjtBOr3FGPRm7/tX81+FxJ9/k9mYQ79N8qmB2gdg9RM4hIzd9loV91vAKMBhCEF/jh1K/GDP52baC7kjd0fXfCnR04xI2+rX+QfYQL2gPlU7dK/P/pB7mRXIV+nc2AupK4OPwnQ+zZ7apgF0U6A3/u7uHjDnxo+sPl6bjQXBneRUgEAz2ZffdNQ4nYoVfCD7Js4fXrLTW9ri3XnwWi33Vf3B+UBeMt/5KcgeXGsUXNtY/xg5BAIL7nobeabyuinHeXryWCo1Uh9jVa+KPSRq/7GbhXE2KvY+8z/ZCBtqocOXu3yWQAZV/3N3qsJHdxQ/2mmMGT0aTKq1NTIY0w04XIMxn8sibmztKQu7/vlRK3pXtUijyZhedazmqXZCu2/9fvNSTu9NiYZ5HJgDLoeh7XpIhMdSV/Ljdnqt65C3lOED1wPA7C8HM3lcDIOM6N0cTEPS2s992L5gYl2EtecAzs8kccgJn7XWf9VrD+xEnFvcnb4iO2+d/hB7lkA3a76r+ZkyUCF6kxmanSPi94gfsdJ3zW4WrNpXCa2uuoIrGyuA3jCdt/1OFs0Y+LR1Ez2KZs9FVXesNlvI9yuWjKdwbd9Vvalmy4faoLFjZaNchoAMXembibGbfTaUVN6HkC9jV7VcL5uT8Rv29jWZOBF0z2icB4AQM01pYWTJjv8//DXa7JHVDEIACDGq22zfUZOngGAx94LcLzotpZYBAAgUSknXjdVPKR43n6A+AQAJhz3Z8e0b960zmQfJug7Tq5bbAIAUI/ysvbNkbBCzpec1xOnAABF47p3z5h4QGc93eIVAKOhVFTHNNYjAP3a6hkQrwAAgHC87cLBu77UXK10kOsC0KKjlinxCwDUvNxQOayjUgh+Wkcdk2IYAECKTug4XUdEPTrGY1IsAwBze7Lj580/uRIe1TAao+IZAABP8dHNXJ8p5BvA3KlrPKbENgAwBjezgV9evt2NLfAKVmwDYEKNIhX5wGwlDGNx6uFeYhvAChrHhX2RTtQRqEP3aEyIdwCMdCqRjvYkS9yudzBmxDsAAESI9mPs4GWLKGIfABi9yWvPtEW4MtbH4u+IfwCAUpW6QxGua9I+EgO2QgAA83MRrtpWr88KIYQQQgghhBBCCCGEEEIISTX9B+l2HtJhmbi6AAAAAElFTkSuQmCC";
9
+ const CHECK_PATH = "M20 6 9 17l-5-5";
10
+ const CROSS_PATH = "M18 6 6 18M6 6l12 12";
11
+ const escapeHtml = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c] ?? c);
12
+ const renderCallbackPage = (ok) => {
13
+ const title = ok ? "Connected to glen" : "Connection didn't complete";
14
+ const message = ok
15
+ ? ""
16
+ : "Something interrupted the sign-in. Head back to your terminal and run it again.";
17
+ const iconPath = ok ? CHECK_PATH : CROSS_PATH;
18
+ const badgeClass = ok ? "badge badge--ok" : "badge badge--err";
19
+ const messageHtml = message
20
+ ? `<p class="message">${escapeHtml(message)}</p>`
21
+ : "";
22
+ return `<!doctype html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="utf-8" />
26
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
27
+ <title>glen</title>
28
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
29
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
30
+ <link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
31
+ <style>
32
+ :root {
33
+ --page: oklch(0.985 0.001 106.423);
34
+ --card: oklch(1 0 0);
35
+ --fg: oklch(0.147 0.004 49.25);
36
+ --muted-fg: oklch(0.553 0.013 58.071);
37
+ --border: oklch(0.923 0.003 48.717);
38
+ --primary: oklch(0.8 0.17 152);
39
+ --primary-fg: oklch(0.16 0.04 150);
40
+ --destructive: oklch(0.577 0.245 27.325);
41
+ --on-destructive: oklch(0.985 0 0);
42
+ }
43
+ @media (prefers-color-scheme: dark) {
44
+ :root {
45
+ --page: oklch(0.147 0.004 49.25);
46
+ --card: oklch(0.216 0.006 56.043);
47
+ --fg: oklch(0.985 0.001 106.423);
48
+ --muted-fg: oklch(0.709 0.01 56.259);
49
+ --border: oklch(1 0 0 / 12%);
50
+ --destructive: oklch(0.704 0.191 22.216);
51
+ --on-destructive: oklch(0.16 0.02 25);
52
+ }
53
+ }
54
+ * { box-sizing: border-box; }
55
+ html, body { height: 100%; margin: 0; }
56
+ body {
57
+ font-family: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
58
+ background: var(--page); color: var(--fg);
59
+ display: grid; place-items: center; padding: 24px;
60
+ -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
61
+ }
62
+ .card {
63
+ width: 100%; max-width: 400px; background: var(--card);
64
+ border: 1px solid var(--border); /* sharp corners — the app's --radius is 0 */
65
+ padding: 48px 40px; text-align: center;
66
+ box-shadow: 0 1px 2px oklch(0 0 0 / 4%), 0 24px 48px -28px oklch(0 0 0 / 22%);
67
+ }
68
+ .logo { width: 38px; height: 38px; margin: 0 auto 28px; display: block; }
69
+ .badge {
70
+ width: 52px; height: 52px; margin: 0 auto 24px;
71
+ display: grid; place-items: center;
72
+ }
73
+ .badge svg { width: 26px; height: 26px; }
74
+ .badge--ok { background: var(--primary); }
75
+ .badge--ok svg { stroke: var(--primary-fg); }
76
+ .badge--err { background: var(--destructive); }
77
+ .badge--err svg { stroke: var(--on-destructive); }
78
+ h1 { font-size: 21px; font-weight: 600; letter-spacing: -0.014em; margin: 0; }
79
+ .message { font-size: 14.5px; line-height: 1.55; color: var(--muted-fg); margin: 12px 0 0; }
80
+ .hint { font-size: 13px; color: var(--muted-fg); margin: 28px 0 0; }
81
+ </style>
82
+ </head>
83
+ <body>
84
+ <main class="card" role="status" aria-live="polite">
85
+ <img class="logo" src="data:image/png;base64,${GLEN_LOGO_BASE64}" alt="glen" />
86
+ <div class="${badgeClass}">
87
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${iconPath}" /></svg>
88
+ </div>
89
+ <h1>${title}</h1>
90
+ ${messageHtml}
91
+ <p class="hint">You can close this tab.</p>
92
+ </main>
93
+ </body>
94
+ </html>`;
95
+ };
96
+ export { renderCallbackPage };
97
+ //# sourceMappingURL=render-callback-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-callback-page.js","sourceRoot":"","sources":["../../src/_utils/render-callback-page.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,kFAAkF;AAClF,gFAAgF;AAChF,oFAAoF;AAEpF,8EAA8E;AAC9E,kFAAkF;AAClF,sEAAsE;AACtE,MAAM,gBAAgB,GACpB,suDAAsuD,CAAC;AAEzuD,MAAM,UAAU,GAAG,iBAAiB,CAAC;AACrC,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CACvC,CAAC,CAAC,OAAO,CACP,QAAQ,EACR,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAC5D,CAAC;AAEJ,MAAM,kBAAkB,GAAG,CAAC,EAAW,EAAU,EAAE;IACjD,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,4BAA4B,CAAC;IACtE,MAAM,OAAO,GAAG,EAAE;QAChB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,iFAAiF,CAAC;IACtF,MAAM,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9C,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC/D,MAAM,WAAW,GAAG,OAAO;QACzB,CAAC,CAAC,sBAAsB,UAAU,CAAC,OAAO,CAAC,MAAM;QACjD,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDA+D0C,gBAAgB;kBACjD,UAAU;2IAC+G,QAAQ;;UAEzI,KAAK;MACT,WAAW;;;;QAIT,CAAC;AACT,CAAC,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,14 @@
1
+ type Ok<T> = {
2
+ readonly ok: true;
3
+ readonly value: T;
4
+ };
5
+ type Err<E> = {
6
+ readonly ok: false;
7
+ readonly error: E;
8
+ };
9
+ type Result<T, E = unknown> = Ok<T> | Err<E>;
10
+ declare const ok: <T>(value: T) => Ok<T>;
11
+ declare const err: <E>(error: E) => Err<E>;
12
+ declare const fromPromise: <T>(promise: Promise<T>) => Promise<Result<T, unknown>>;
13
+ export { ok, err, fromPromise };
14
+ export type { Result };
@@ -0,0 +1,12 @@
1
+ const ok = (value) => ({ ok: true, value });
2
+ const err = (error) => ({ ok: false, error });
3
+ const fromPromise = async (promise) => {
4
+ try {
5
+ return ok(await promise);
6
+ }
7
+ catch (e) {
8
+ return err(e);
9
+ }
10
+ };
11
+ export { ok, err, fromPromise };
12
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/_utils/result.ts"],"names":[],"mappings":"AAIA,MAAM,EAAE,GAAG,CAAI,KAAQ,EAAS,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACzD,MAAM,GAAG,GAAG,CAAI,KAAQ,EAAU,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAE5D,MAAM,WAAW,GAAG,KAAK,EACvB,OAAmB,EACU,EAAE;IAC/B,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;AACH,CAAC,CAAC;AAEF,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { cac } from "cac";
6
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
7
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
8
+ const cli = cac("glen");
9
+ cli.command("login", "Connect glen via browser sign-in").action(async () => {
10
+ const { login } = await import("./commands/login.js");
11
+ const result = await login();
12
+ if (!result.ok) {
13
+ process.stderr.write(`glen login failed: ${result.error}\n`);
14
+ process.exit(1);
15
+ }
16
+ });
17
+ cli
18
+ .command("logout", "Sign out of glen and remove credentials")
19
+ .action(async () => {
20
+ const { logout } = await import("./commands/logout.js");
21
+ const result = await logout();
22
+ if (!result.ok) {
23
+ process.stderr.write(`glen logout failed: ${result.error}\n`);
24
+ process.exit(1);
25
+ }
26
+ });
27
+ cli.command("status", "Check connection status").action(async () => {
28
+ const { status } = await import("./commands/status.js");
29
+ const result = await status();
30
+ if (!result.ok)
31
+ process.exit(1);
32
+ });
33
+ cli
34
+ .command("search <query>", "Search glen memory")
35
+ .action(async (query) => {
36
+ const { search } = await import("./commands/search.js");
37
+ const result = await search(query);
38
+ if (!result.ok) {
39
+ process.stderr.write(`${result.error.message}\n`);
40
+ process.exit(result.error.code);
41
+ }
42
+ });
43
+ cli
44
+ .command("ingest", "Ingest a turn from stdin (used by agent hooks)")
45
+ .action(async () => {
46
+ const { ingest } = await import("./commands/ingest.js");
47
+ const result = await ingest();
48
+ if (!result.ok)
49
+ process.exit(result.error.code);
50
+ });
51
+ cli
52
+ .command("install", "Detect coding agents and install glen plugins")
53
+ .action(async () => {
54
+ const { install } = await import("./commands/install.js");
55
+ const result = await install();
56
+ if (!result.ok)
57
+ process.exit(1);
58
+ });
59
+ cli.command("version", "Print the CLI version").action(() => {
60
+ process.stdout.write(`${pkg.version}\n`);
61
+ });
62
+ cli.help();
63
+ cli.version(pkg.version);
64
+ cli.parse();
65
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ type IngestError = {
3
+ readonly code: number;
4
+ readonly message: string;
5
+ };
6
+ declare const ingest: () => Promise<Result<void, IngestError>>;
7
+ export { ingest };
@@ -0,0 +1,73 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { buildTurnBody } from "../_utils/build-turn-body.js";
3
+ import { config } from "../_utils/config.js";
4
+ import { resolveAuthHeader } from "../_utils/credentials.js";
5
+ import { formatMemory } from "../_utils/format-memory.js";
6
+ import { httpJson } from "../_utils/http.js";
7
+ import { parseTranscript } from "../_utils/parse-transcript.js";
8
+ import { err, fromPromise, ok } from "../_utils/result.js";
9
+ const readStdin = async () => {
10
+ const chunks = [];
11
+ for await (const chunk of process.stdin)
12
+ chunks.push(chunk);
13
+ return Buffer.concat(chunks).toString("utf8");
14
+ };
15
+ const readTranscriptLines = async (path) => {
16
+ const r = await fromPromise(readFile(path, "utf8"));
17
+ if (!r.ok)
18
+ return [];
19
+ return r.value.split("\n").filter(Boolean);
20
+ };
21
+ const ingest = async () => {
22
+ const stdinRaw = await readStdin();
23
+ const event = (() => {
24
+ try {
25
+ return JSON.parse(stdinRaw);
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ })();
31
+ if (!event?.prompt || !event?.session_id)
32
+ return ok(undefined);
33
+ const header = await resolveAuthHeader();
34
+ if (!header)
35
+ return err({ code: 1, message: "not connected" });
36
+ const transcriptPath = typeof event.transcript_path === "string"
37
+ ? event.transcript_path
38
+ : undefined;
39
+ const { priorAssistant, nextUserMessageIndex } = parseTranscript(transcriptPath ? await readTranscriptLines(transcriptPath) : []);
40
+ const body = buildTurnBody({
41
+ sessionId: event.session_id,
42
+ prompt: event.prompt,
43
+ priorAssistant,
44
+ nextUserMessageIndex,
45
+ });
46
+ const res = await httpJson(config.turnUrl, {
47
+ method: "POST",
48
+ headers: {
49
+ "content-type": "application/json",
50
+ [header.headerName]: header.headerValue,
51
+ },
52
+ body: JSON.stringify(body),
53
+ timeoutMs: config.ingestTimeoutMs,
54
+ });
55
+ if (!res.ok)
56
+ return err({ code: 3, message: "ingest failed (network error)" });
57
+ if (res.value.status === 401 || res.value.status === 403) {
58
+ // Don't clear credentials here — a concurrent `glen login` may have just
59
+ // saved a fresh key. Return code 2 so the hook can trigger re-auth.
60
+ return err({ code: 2, message: "key rejected" });
61
+ }
62
+ if (!res.value.isOk) {
63
+ // Fail open (don't block the prompt), but surface the error so it's not invisible.
64
+ process.stderr.write(`glen: ingest failed (HTTP ${res.value.status}). Memory was not recorded this turn.\n`);
65
+ return ok(undefined);
66
+ }
67
+ const memory = formatMemory(res.value.json);
68
+ if (memory)
69
+ process.stdout.write(memory);
70
+ return ok(undefined);
71
+ };
72
+ export { ingest };
73
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/commands/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ declare const install: () => Promise<Result<void, string>>;
3
+ export { install };
@@ -0,0 +1,78 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import prompts from "prompts";
3
+ import { err, ok } from "../_utils/result.js";
4
+ const AGENTS = [
5
+ {
6
+ name: "Claude Code",
7
+ bin: "claude",
8
+ installArgs: ["plugin", "install", "tryglen/glen-claude-code-plugin"],
9
+ },
10
+ {
11
+ name: "Codex",
12
+ bin: "codex",
13
+ installArgs: ["plugin", "install", "tryglen/glen-codex-plugin"],
14
+ },
15
+ ];
16
+ const isInstalled = (bin) => {
17
+ const r = (() => {
18
+ try {
19
+ execFileSync("which", [bin], { stdio: "ignore" });
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ })();
26
+ return r;
27
+ };
28
+ const install = async () => {
29
+ process.stdout.write("\n 🌿 glen — install agent plugins\n\n");
30
+ const detected = AGENTS.filter((a) => isInstalled(a.bin));
31
+ if (detected.length === 0) {
32
+ process.stdout.write(" ⚠ Could not find Claude Code or Codex installed.\n");
33
+ process.stdout.write(" See https://tryglen.com/docs/plugins for manual install instructions.\n\n");
34
+ return err("no agents detected");
35
+ }
36
+ process.stdout.write(" Detected coding agents:\n");
37
+ for (const a of detected) {
38
+ process.stdout.write(` ✓ ${a.name} (${a.bin})\n`);
39
+ }
40
+ process.stdout.write("\n");
41
+ const choices = detected.map((a) => ({
42
+ title: `${a.name} plugin`,
43
+ value: a,
44
+ selected: true,
45
+ }));
46
+ const response = await prompts({
47
+ type: "multiselect",
48
+ name: "plugins",
49
+ message: "Select plugins to install",
50
+ choices,
51
+ instructions: false,
52
+ });
53
+ const selected = (response.plugins ?? []);
54
+ if (selected.length === 0) {
55
+ process.stdout.write(" No plugins selected.\n");
56
+ return ok(undefined);
57
+ }
58
+ for (const agent of selected) {
59
+ process.stdout.write(` Installing ${agent.name} plugin... `);
60
+ const installResult = (() => {
61
+ try {
62
+ execFileSync(agent.bin, agent.installArgs, {
63
+ stdio: "ignore",
64
+ });
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ })();
71
+ process.stdout.write(installResult ? "✓\n" : "✗ (failed)\n");
72
+ }
73
+ process.stdout.write("\n Done! Start a conversation in either agent — glen memory is active.\n");
74
+ process.stdout.write(" Run `glen login` if you haven't connected yet.\n\n");
75
+ return ok(undefined);
76
+ };
77
+ export { install };
78
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ declare const login: () => Promise<Result<void, string>>;
3
+ export { login };
@@ -0,0 +1,61 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { buildConnectUrl } from "../_utils/build-connect-url.js";
3
+ import { config } from "../_utils/config.js";
4
+ import { writeCredentials } from "../_utils/credentials.js";
5
+ import { httpJson } from "../_utils/http.js";
6
+ import { acquireLoginLock, releaseLoginLock } from "../_utils/login-lock.js";
7
+ import { startLoopbackServer } from "../_utils/loopback-server.js";
8
+ import { openBrowser } from "../_utils/open-browser.js";
9
+ import { err, ok } from "../_utils/result.js";
10
+ const WAIT_MS = 10 * 60_000;
11
+ const login = async () => {
12
+ const lockAcquired = await acquireLoginLock();
13
+ if (!lockAcquired)
14
+ return err("another login is already in progress");
15
+ const state = randomBytes(32).toString("hex");
16
+ const verifier = randomBytes(48).toString("base64url");
17
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
18
+ const { redirectUri, waitForCallback } = await startLoopbackServer(WAIT_MS, state);
19
+ const url = buildConnectUrl({
20
+ connectUrl: config.connectUrl,
21
+ callback: redirectUri,
22
+ state,
23
+ challenge,
24
+ });
25
+ process.stdout.write("Opening your browser to connect glen…\n");
26
+ process.stdout.write(`If it doesn't open, visit:\n${url}\n\n`);
27
+ openBrowser(url);
28
+ const cb = await waitForCallback().catch(() => null);
29
+ if (!cb?.code) {
30
+ await releaseLoginLock();
31
+ return err("login callback timed out or was cancelled");
32
+ }
33
+ if (cb.state !== state) {
34
+ await releaseLoginLock();
35
+ return err("login state mismatch (possible local interception)");
36
+ }
37
+ const exchangeResult = await httpJson(config.exchangeUrl, {
38
+ method: "POST",
39
+ headers: { "content-type": "application/json" },
40
+ body: JSON.stringify({ code: cb.code, verifier }),
41
+ timeoutMs: 15_000,
42
+ });
43
+ await releaseLoginLock();
44
+ if (!exchangeResult.ok)
45
+ return err("key exchange failed (network error)");
46
+ if (!exchangeResult.value.isOk)
47
+ return err(`key exchange failed (HTTP ${exchangeResult.value.status})`);
48
+ const payload = exchangeResult.value.json;
49
+ if (!payload?.apikey || typeof payload.apikey !== "string")
50
+ return err("key exchange returned no key");
51
+ const writeResult = await writeCredentials({
52
+ type: "apikey",
53
+ key: payload.apikey,
54
+ });
55
+ if (!writeResult.ok)
56
+ return err("failed to write credentials");
57
+ process.stdout.write("✓ Connected to glen. Memory is now active.\n");
58
+ return ok(undefined);
59
+ };
60
+ export { login };
61
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ declare const logout: () => Promise<Result<void, string>>;
3
+ export { logout };
@@ -0,0 +1,11 @@
1
+ import { clearCredentials } from "../_utils/credentials.js";
2
+ import { releaseLoginLock } from "../_utils/login-lock.js";
3
+ import { ok } from "../_utils/result.js";
4
+ const logout = async () => {
5
+ await clearCredentials();
6
+ await releaseLoginLock();
7
+ process.stdout.write("✓ Signed out of glen (credentials removed).\n");
8
+ return ok(undefined);
9
+ };
10
+ export { logout };
11
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAe,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,MAAM,GAAG,KAAK,IAAmC,EAAE;IACvD,MAAM,gBAAgB,EAAE,CAAC;IACzB,MAAM,gBAAgB,EAAE,CAAC;IACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACtE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ declare const search: (query: string) => Promise<Result<void, {
3
+ code: number;
4
+ message: string;
5
+ }>>;
6
+ export { search };
@@ -0,0 +1,41 @@
1
+ import { config } from "../_utils/config.js";
2
+ import { resolveAuthHeader } from "../_utils/credentials.js";
3
+ import { httpJson } from "../_utils/http.js";
4
+ import { err, ok } from "../_utils/result.js";
5
+ const search = async (query) => {
6
+ if (!query.trim())
7
+ return err({ code: 2, message: 'usage: glen search "<query>"' });
8
+ const header = await resolveAuthHeader();
9
+ if (!header)
10
+ return err({ code: 1, message: "glen: not connected — run `glen login`." });
11
+ const res = await httpJson(config.searchUrl, {
12
+ method: "POST",
13
+ headers: {
14
+ "content-type": "application/json",
15
+ [header.headerName]: header.headerValue,
16
+ },
17
+ body: JSON.stringify({ query }),
18
+ timeoutMs: config.searchTimeoutMs,
19
+ });
20
+ if (!res.ok)
21
+ return err({ code: 2, message: "glen search failed (network error)." });
22
+ if (res.value.status === 401 || res.value.status === 403) {
23
+ // Don't clear credentials — a concurrent `glen login` may have just saved a fresh key.
24
+ return err({
25
+ code: 1,
26
+ message: "glen: your connection expired — run `glen login` to reconnect.",
27
+ });
28
+ }
29
+ if (!res.value.isOk)
30
+ return err({
31
+ code: 2,
32
+ message: `glen search failed (HTTP ${res.value.status}).`,
33
+ });
34
+ const context = res.value.json?.context;
35
+ process.stdout.write(typeof context === "string" && context.trim()
36
+ ? `${context}\n`
37
+ : "glen: no relevant memory found for that query.\n");
38
+ return ok(undefined);
39
+ };
40
+ export { search };
41
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAe,GAAG,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,MAAM,GAAG,KAAK,EAClB,KAAa,EAC6C,EAAE;IAC5D,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QACf,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAEnE,MAAM,MAAM,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACzC,IAAI,CAAC,MAAM;QACT,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,yCAAyC,EAAE,CAAC,CAAC;IAE9E,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE;QAC3C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,WAAW;SACxC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC,eAAe;KAClC,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE;QACT,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC,CAAC;IAE1E,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACzD,uFAAuF;QACvF,OAAO,GAAG,CAAC;YACT,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,gEAAgE;SAC1E,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI;QACjB,OAAO,GAAG,CAAC;YACT,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,4BAA4B,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI;SAC1D,CAAC,CAAC;IAEL,MAAM,OAAO,GAAI,GAAG,CAAC,KAAK,CAAC,IAAuC,EAAE,OAAO,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE;QAC3C,CAAC,CAAC,GAAG,OAAO,IAAI;QAChB,CAAC,CAAC,kDAAkD,CACvD,CAAC;IACF,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Result } from "../_utils/result.js";
2
+ declare const status: () => Promise<Result<void, string>>;
3
+ export { status };
@@ -0,0 +1,14 @@
1
+ import { config } from "../_utils/config.js";
2
+ import { readCredentials } from "../_utils/credentials.js";
3
+ import { err, ok } from "../_utils/result.js";
4
+ const status = async () => {
5
+ const creds = await readCredentials();
6
+ if (creds?.type === "apikey" && creds.key) {
7
+ process.stdout.write(`glen: connected with an API key. Base: ${config.baseUrl}\n`);
8
+ return ok(undefined);
9
+ }
10
+ process.stdout.write("glen: not connected. Run `glen login` to connect.\n");
11
+ return err("not connected");
12
+ };
13
+ export { status };
14
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAe,GAAG,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,MAAM,GAAG,KAAK,IAAmC,EAAE;IACvD,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0CAA0C,MAAM,CAAC,OAAO,IAAI,CAC7D,CAAC;QACF,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC,eAAe,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@tryglen/cli",
3
+ "version": "0.1.1",
4
+ "description": "Glen CLI — memory for coding agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "glen": "./dist/bin.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc && chmod +x dist/bin.js",
14
+ "check": "biome check src/ && tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest"
17
+ },
18
+ "dependencies": {
19
+ "cac": "^6.7.14",
20
+ "prompts": "^2.4.2"
21
+ },
22
+ "devDependencies": {
23
+ "@biomejs/biome": "^1.9.0",
24
+ "@types/node": "^20.0.0",
25
+ "@types/prompts": "^2.4.9",
26
+ "typescript": "^5.5.0",
27
+ "vitest": "^3.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "license": "MIT"
33
+ }