@thematica/prism-tui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +37 -2
  2. package/dist/packages/prism-tui/src/account.d.ts +4 -0
  3. package/dist/packages/prism-tui/src/account.d.ts.map +1 -0
  4. package/dist/packages/prism-tui/src/account.js +25 -0
  5. package/dist/packages/prism-tui/src/account.js.map +1 -0
  6. package/dist/packages/prism-tui/src/cli.d.ts +3 -0
  7. package/dist/packages/prism-tui/src/cli.d.ts.map +1 -0
  8. package/dist/packages/prism-tui/src/cli.js +117 -0
  9. package/dist/packages/prism-tui/src/cli.js.map +1 -0
  10. package/dist/packages/prism-tui/src/credentials.d.ts +11 -0
  11. package/dist/packages/prism-tui/src/credentials.d.ts.map +1 -0
  12. package/dist/packages/prism-tui/src/credentials.js +53 -0
  13. package/dist/packages/prism-tui/src/credentials.js.map +1 -0
  14. package/dist/packages/prism-tui/src/device-login.d.ts +14 -0
  15. package/dist/packages/prism-tui/src/device-login.d.ts.map +1 -0
  16. package/dist/packages/prism-tui/src/device-login.js +78 -0
  17. package/dist/packages/prism-tui/src/device-login.js.map +1 -0
  18. package/dist/src/cli/answer.d.ts +219 -0
  19. package/dist/src/cli/answer.d.ts.map +1 -0
  20. package/dist/src/cli/answer.js +201 -0
  21. package/dist/src/cli/answer.js.map +1 -0
  22. package/dist/src/cli/app/App.d.ts +127 -0
  23. package/dist/src/cli/app/App.d.ts.map +1 -0
  24. package/dist/src/cli/app/App.js +974 -0
  25. package/dist/src/cli/app/App.js.map +1 -0
  26. package/dist/src/cli/app/api.d.ts +130 -0
  27. package/dist/src/cli/app/api.d.ts.map +1 -0
  28. package/dist/src/cli/app/api.js +80 -0
  29. package/dist/src/cli/app/api.js.map +1 -0
  30. package/dist/src/cli/app/run-app.d.ts +9 -0
  31. package/dist/src/cli/app/run-app.d.ts.map +1 -0
  32. package/dist/src/cli/app/run-app.js +11 -0
  33. package/dist/src/cli/app/run-app.js.map +1 -0
  34. package/dist/src/facts/fresh.d.ts +15 -0
  35. package/dist/src/facts/fresh.d.ts.map +1 -0
  36. package/dist/src/facts/fresh.js +22 -0
  37. package/dist/src/facts/fresh.js.map +1 -0
  38. package/dist/src/market/amounts.d.ts +208 -0
  39. package/dist/src/market/amounts.d.ts.map +1 -0
  40. package/dist/src/market/amounts.js +522 -0
  41. package/dist/src/market/amounts.js.map +1 -0
  42. package/package.json +15 -4
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # Prism terminal app
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Prism researches an investment theme and shows four ranked lists with source quotes.
4
+ This package runs the terminal interface against Prism's cloud API. This release targets
5
+ macOS and requires Node 22 or newer.
6
+
7
+ Install from npm:
8
+
9
+ ```sh
10
+ npm install -g @thematica/prism-tui
11
+ prism login
12
+ prism whoami
13
+ prism start
14
+ ```
15
+
16
+ `prism whoami` shows your Thematica wallet balance and Prism spending limits.
17
+ Check the balance and limits before starting a research run.
18
+ It does not yet show your sign-in email. Check the email on your Thematica profile
19
+ before approving a terminal sign-in. `prism logout` revokes this computer's
20
+ credential; sign out separately in the browser before using another account.
21
+ A non-tester needs purchased credit;
22
+ an invited Prism tester can use the daily free allowance first. Prism shows
23
+ the run's measured cost when it finishes. This package does not yet display
24
+ the separate free and purchased funding amounts.
25
+ If your balance or spending-limit room is exhausted before a waiting job starts,
26
+ Prism pauses your research queue. Add credit or change the limit, then run
27
+ `prism resume` to start it again. A top-up alone does not resume jobs.
28
+
29
+ The Prism and Thematica browser sign-in services are live. Access to research
30
+ still requires a Prism invitation while paid credit purchases are being built.
31
+
32
+ `prism login` opens Thematica in your browser. Sign in, confirm the code shown in your
33
+ terminal, and return to the terminal. The app receives its own Prism credential and saves
34
+ it in a private file on this computer. You do not paste your profile API key. If the browser
35
+ does not open, copy the displayed sign-in link into your browser.
36
+
37
+ `prism logout` revokes the terminal credential at Prism and removes its local copy.
38
+ The credential also expires after seven days.
@@ -0,0 +1,4 @@
1
+ import type { AccountView } from "../../../src/cli/app/api.js";
2
+ /** Account details after browser sign-in and in `prism whoami`. Only V2's live summary is a balance. */
3
+ export declare function accountText(view: AccountView, thematicaOrigin: string): string;
4
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../../../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAU/D,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAc9E"}
@@ -0,0 +1,25 @@
1
+ const dollars = (value) => {
2
+ const negative = value.startsWith("-");
3
+ const raw = negative ? value.slice(1) : value;
4
+ const [whole, fractional = ""] = raw.split(".");
5
+ const centsOrMore = fractional.replace(/0+$/, "").padEnd(2, "0");
6
+ return `${negative ? "-" : ""}$${whole}.${centsOrMore}`;
7
+ };
8
+ /** Account details after browser sign-in and in `prism whoami`. Only V2's live summary is a balance. */
9
+ export function accountText(view, thematicaOrigin) {
10
+ const wallet = view.wallet;
11
+ if (wallet === undefined)
12
+ return `Prism account: ${view.account.name}`;
13
+ const period = { day: "day", week: "week", month: "month" }[wallet.limits.period];
14
+ const limit = (value) => value === null ? "No limit" : dollars(value);
15
+ return [
16
+ "Signed in to Prism",
17
+ `Thematica wallet balance: ${dollars(wallet.balance_usd)}`,
18
+ `Prism usage in ${wallet.prism_usage_month}: ${dollars(wallet.prism_usage_this_month_usd)}`,
19
+ ...(view.wallet_settlement_pending ? ["A finished Prism run is still being posted to your wallet."] : []),
20
+ ...(view.queue?.status === "paused" ? ["Research queue paused. Add credit or change your spending limit, then run prism resume."] : []),
21
+ `Spending limits per ${period}: Prism ${limit(wallet.limits.prism_usd)} · Overall ${limit(wallet.limits.overall_usd)}`,
22
+ `Manage Prism settings: ${thematicaOrigin}/profile?product=prism`,
23
+ ].join("\n");
24
+ }
25
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../../../src/account.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,CAAC,KAAa,EAAU,EAAE;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACjE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,WAAW,EAAE,CAAC;AAC1D,CAAC,CAAC;AAEF,wGAAwG;AACxG,MAAM,UAAU,WAAW,CAAC,IAAiB,EAAE,eAAuB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,kBAAkB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACvE,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACrF,OAAO;QACL,oBAAoB;QACpB,6BAA6B,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE;QAC1D,kBAAkB,MAAM,CAAC,iBAAiB,KAAK,OAAO,CAAC,MAAM,CAAC,0BAA0B,CAAC,EAAE;QAC3F,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,4DAA4D,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,yFAAyF,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvI,uBAAuB,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE;QACtH,0BAA0B,eAAe,wBAAwB;KAClE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { loadCredential, removeCredential, saveCredential, credentialsPath } from "./credentials.js";
4
+ import { AUTH_BASE_URL, deviceLogin, PRISM_API_URL } from "./device-login.js";
5
+ import { accountText } from "./account.js";
6
+ import { httpApi } from "../../../src/cli/app/api.js";
7
+ import { runApp } from "../../../src/cli/app/run-app.js";
8
+ function openBrowser(url) {
9
+ return new Promise((resolve, reject) => {
10
+ const command = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
11
+ if (command === null) {
12
+ resolve();
13
+ return;
14
+ }
15
+ const child = spawn(command, [url], { stdio: "ignore", detached: true });
16
+ child.once("error", reject);
17
+ child.once("spawn", () => { child.unref(); resolve(); });
18
+ });
19
+ }
20
+ function usage() {
21
+ console.log("Prism terminal app\n prism login Open browser sign-in\n prism start Research a theme\n prism whoami Check this computer's Prism account\n prism resume Resume your paused research queue\n prism logout Remove this computer's credential");
22
+ }
23
+ async function main() {
24
+ const [command, ...rest] = process.argv.slice(2);
25
+ if (command === "login") {
26
+ if (rest.length)
27
+ throw new Error("prism login takes no options");
28
+ console.log("Starting Prism sign-in…");
29
+ const key = await deviceLogin({
30
+ authBaseUrl: AUTH_BASE_URL,
31
+ show: (code, url) => console.log(`Confirm code ${code} in your browser:\n${url}\nWaiting for approval…`),
32
+ open: openBrowser,
33
+ });
34
+ const api = httpApi(PRISM_API_URL, key);
35
+ // A wallet read may fail while the new credential is valid. Verify the key on a
36
+ // Prism-only route before saving it, then treat wallet details as optional display.
37
+ try {
38
+ await api.runs(1);
39
+ }
40
+ catch {
41
+ throw new Error("Prism did not accept the new credential. Run prism login again.");
42
+ }
43
+ const previous = await loadCredential();
44
+ await saveCredential({ version: 1, api_url: PRISM_API_URL, key });
45
+ if (previous !== null && previous.key !== key) {
46
+ const revoked = await fetch(`${previous.api_url}/v1/cli/session`, { method: "DELETE", headers: { authorization: `Bearer ${previous.key}` } }).catch(() => null);
47
+ if (revoked === null || (revoked.status !== 204 && revoked.status !== 401))
48
+ console.error("The previous terminal credential could not be revoked; it will expire within seven days.");
49
+ }
50
+ try {
51
+ console.log(accountText(await api.account(), AUTH_BASE_URL));
52
+ }
53
+ catch {
54
+ console.log("Signed in to Prism. Thematica wallet details are temporarily unavailable; run prism whoami later.");
55
+ }
56
+ console.log("Run prism start to research a theme.");
57
+ return;
58
+ }
59
+ if (command === "logout") {
60
+ if (rest.length)
61
+ throw new Error("prism logout takes no options");
62
+ const credential = await loadCredential();
63
+ if (credential !== null) {
64
+ let response;
65
+ try {
66
+ response = await fetch(`${credential.api_url}/v1/cli/session`, { method: "DELETE", headers: { authorization: `Bearer ${credential.key}` } });
67
+ }
68
+ catch {
69
+ throw new Error("Cannot reach Prism to revoke this computer's credential. Try again while online.");
70
+ }
71
+ if (response.status !== 204 && response.status !== 401)
72
+ throw new Error(`Prism could not revoke this computer's credential (${response.status}).`);
73
+ }
74
+ await removeCredential();
75
+ console.log("Signed out. This computer's credential is revoked.");
76
+ return;
77
+ }
78
+ if (command === "whoami") {
79
+ if (rest.length)
80
+ throw new Error("prism whoami takes no options");
81
+ const credential = await loadCredential();
82
+ if (credential === null)
83
+ throw new Error("You are not signed in. Run prism login.");
84
+ const view = await httpApi(credential.api_url, credential.key).account();
85
+ console.log(accountText(view, AUTH_BASE_URL));
86
+ return;
87
+ }
88
+ if (command === "resume") {
89
+ if (rest.length)
90
+ throw new Error("prism resume takes no options");
91
+ const credential = await loadCredential();
92
+ if (credential === null)
93
+ throw new Error("You are not signed in. Run prism login.");
94
+ const result = await httpApi(credential.api_url, credential.key).resumeQueue();
95
+ console.log(result.resumed ? "Your Prism research queue is active again." : "Your Prism research queue is already active.");
96
+ return;
97
+ }
98
+ if (command === "start") {
99
+ if (rest.length)
100
+ throw new Error("prism start takes no options in the installed app");
101
+ if (!process.stdin.isTTY)
102
+ throw new Error("prism start needs a terminal to type in");
103
+ const credential = await loadCredential();
104
+ if (credential === null)
105
+ throw new Error("You are not signed in. Run prism login.");
106
+ await runApp({ url: credential.api_url, key: credential.key, place: "cloud", settingsFile: credentialsPath() });
107
+ return;
108
+ }
109
+ usage();
110
+ if (command && command !== "help" && command !== "--help")
111
+ process.exitCode = 1;
112
+ }
113
+ main().catch((error) => {
114
+ console.error(`prism: ${error.message}`);
115
+ process.exitCode = 1;
116
+ });
117
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface Credential {
2
+ readonly version: 1;
3
+ readonly api_url: string;
4
+ readonly key: string;
5
+ }
6
+ export declare function credentialsPath(): string;
7
+ /** Replace atomically; a failed write never erases a working login. */
8
+ export declare function saveCredential(credential: Credential, path?: string): Promise<void>;
9
+ export declare function loadCredential(path?: string): Promise<Credential | null>;
10
+ export declare function removeCredential(path?: string): Promise<void>;
11
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../../src/credentials.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AAEnG,wBAAgB,eAAe,IAAI,MAAM,CAExC;AASD,uEAAuE;AACvE,wBAAsB,cAAc,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,SAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUpG;AAED,wBAAsB,cAAc,CAAC,IAAI,SAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAczF;AAED,wBAAsB,gBAAgB,CAAC,IAAI,SAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9E"}
@@ -0,0 +1,53 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ export function credentialsPath() {
6
+ return join(homedir(), ".config", "thematica-prism", "credentials.json");
7
+ }
8
+ async function privateDirectory(path) {
9
+ const directory = dirname(path);
10
+ await mkdir(directory, { recursive: true, mode: 0o700 });
11
+ const info = await lstat(directory);
12
+ if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 0o077) !== 0)
13
+ throw new Error("Prism's credential directory must be private (mode 0700).");
14
+ }
15
+ /** Replace atomically; a failed write never erases a working login. */
16
+ export async function saveCredential(credential, path = credentialsPath()) {
17
+ await privateDirectory(path);
18
+ const temporary = `${path}.${randomUUID()}.tmp`;
19
+ try {
20
+ await writeFile(temporary, `${JSON.stringify(credential)}\n`, { mode: 0o600, flag: "wx" });
21
+ await chmod(temporary, 0o600);
22
+ await rename(temporary, path);
23
+ }
24
+ finally {
25
+ await rm(temporary, { force: true });
26
+ }
27
+ }
28
+ export async function loadCredential(path = credentialsPath()) {
29
+ let info;
30
+ try {
31
+ info = await lstat(path);
32
+ }
33
+ catch (error) {
34
+ if (error.code === "ENOENT")
35
+ return null;
36
+ throw error;
37
+ }
38
+ if (!info.isFile() || (info.mode & 0o077) !== 0)
39
+ throw new Error("Prism's credential file must be private (mode 0600).");
40
+ const value = JSON.parse(await readFile(path, "utf8"));
41
+ if (typeof value !== "object" || value === null || value.version !== 1 ||
42
+ typeof value.api_url !== "string" || typeof value.key !== "string" ||
43
+ !/^prism_[A-Za-z0-9_-]{43}$/.test(value.key))
44
+ throw new Error("Prism's saved credential is invalid. Run prism login again.");
45
+ const url = new URL(value.api_url);
46
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname !== "/")
47
+ throw new Error("Prism's saved API address is invalid. Run prism login again.");
48
+ return value;
49
+ }
50
+ export async function removeCredential(path = credentialsPath()) {
51
+ await rm(path, { force: true });
52
+ }
53
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const AUTH_BASE_URL = "https://sim.thematica.ai";
2
+ export declare const PRISM_API_URL = "https://prism-jfczigc2sa-ul.a.run.app";
3
+ export interface DeviceLoginOptions {
4
+ authBaseUrl?: string;
5
+ apiBaseUrl?: string;
6
+ fetch?: typeof globalThis.fetch;
7
+ sleep?: (milliseconds: number) => Promise<void>;
8
+ now?: () => number;
9
+ show: (code: string, url: string) => void;
10
+ open: (url: string) => Promise<void>;
11
+ }
12
+ /** RFC 8628 style device flow. The browser approves a code; the CLI alone receives the credential. */
13
+ export declare function deviceLogin(options: DeviceLoginOptions): Promise<string>;
14
+ //# sourceMappingURL=device-login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-login.d.ts","sourceRoot":"","sources":["../../../../src/device-login.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,6BAA6B,CAAC;AACxD,eAAO,MAAM,aAAa,0CAA0C,CAAC;AAWrE,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAiCD,sGAAsG;AACtG,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC9E"}
@@ -0,0 +1,78 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ export const AUTH_BASE_URL = "https://sim.thematica.ai";
3
+ export const PRISM_API_URL = "https://prism-jfczigc2sa-ul.a.run.app";
4
+ function validBase(raw) {
5
+ const url = new URL(raw);
6
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname)))) {
7
+ throw new Error("The sign-in service must be an HTTPS origin (or localhost for development).");
8
+ }
9
+ return url;
10
+ }
11
+ function parseStart(value, base) {
12
+ if (typeof value !== "object" || value === null)
13
+ throw new Error("The sign-in service returned an invalid response.");
14
+ const v = value;
15
+ if (typeof v.device_code !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(v.device_code) || typeof v.user_code !== "string" || !/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/.test(v.user_code) ||
16
+ typeof v.verification_uri !== "string" || typeof v.verification_uri_complete !== "string" ||
17
+ !Number.isInteger(v.expires_in) || (v.expires_in ?? 0) < 60 || (v.expires_in ?? 0) > 900 ||
18
+ !Number.isInteger(v.interval) || (v.interval ?? 0) < 1 || (v.interval ?? 0) > 30) {
19
+ throw new Error("The sign-in service returned an invalid device challenge.");
20
+ }
21
+ for (const link of [v.verification_uri, v.verification_uri_complete]) {
22
+ const url = new URL(link);
23
+ if (url.origin !== base.origin || url.pathname !== "/prism/device" || url.username || url.password || url.hash)
24
+ throw new Error("The sign-in link points outside Thematica.");
25
+ }
26
+ if (new URL(v.verification_uri).search !== "" || new URL(v.verification_uri_complete).searchParams.get("code") !== v.user_code)
27
+ throw new Error("The sign-in link has an invalid code.");
28
+ return v;
29
+ }
30
+ async function json(response) {
31
+ const value = await response.json().catch(() => null);
32
+ if (typeof value !== "object" || value === null || Array.isArray(value))
33
+ throw new Error("The sign-in service returned invalid JSON.");
34
+ return value;
35
+ }
36
+ /** RFC 8628 style device flow. The browser approves a code; the CLI alone receives the credential. */
37
+ export async function deviceLogin(options) {
38
+ const authBase = validBase(options.authBaseUrl ?? AUTH_BASE_URL);
39
+ const apiBase = validBase(options.apiBaseUrl ?? PRISM_API_URL);
40
+ const request = options.fetch ?? globalThis.fetch;
41
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
42
+ const now = options.now ?? Date.now;
43
+ const key = `prism_${randomBytes(32).toString("base64url")}`;
44
+ const started = await request(new URL("/v1/device/start", apiBase), {
45
+ method: "POST",
46
+ headers: { "content-type": "application/json", "cache-control": "no-store" },
47
+ body: JSON.stringify({ key_hash: createHash("sha256").update(key).digest("hex"), key_prefix: key.slice(0, 12) }),
48
+ });
49
+ if (!started.ok)
50
+ throw new Error(`Sign-in could not start (${started.status}).`);
51
+ const challenge = parseStart(await json(started), authBase);
52
+ const deadline = now() + challenge.expires_in * 1000;
53
+ options.show(challenge.user_code, challenge.verification_uri_complete);
54
+ await options.open(challenge.verification_uri_complete).catch(() => { });
55
+ let interval = challenge.interval * 1000;
56
+ while (now() + interval < deadline) {
57
+ await sleep(interval);
58
+ const response = await request(new URL("/v1/device/token", apiBase), {
59
+ method: "POST",
60
+ headers: { "content-type": "application/json", "cache-control": "no-store" },
61
+ body: JSON.stringify({ device_code: challenge.device_code }),
62
+ });
63
+ const body = await json(response);
64
+ if (response.ok && body.status === "authorized")
65
+ return key;
66
+ switch (body.error) {
67
+ case "authorization_pending": continue;
68
+ case "slow_down":
69
+ interval += 5000;
70
+ continue;
71
+ case "access_denied": throw new Error("Sign-in was denied in the browser.");
72
+ case "expired_token": throw new Error("The sign-in code expired. Run prism login again.");
73
+ default: throw new Error(`Sign-in could not finish (${response.status}).`);
74
+ }
75
+ }
76
+ throw new Error("The sign-in code expired. Run prism login again.");
77
+ }
78
+ //# sourceMappingURL=device-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-login.js","sourceRoot":"","sources":["../../../../src/device-login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,219 @@
1
+ import { type AmountToShow, rateNote, usdShort } from "../market/amounts.js";
2
+ /** How many months old a fact sheet may be and still count (src/facts/fresh.ts), for the words readers see. */
3
+ export { SHEET_MONTHS } from "../facts/fresh.js";
4
+ /** How amounts are written (src/market/amounts.ts), for the app and the command. */
5
+ export { rateNote, usdShort };
6
+ /**
7
+ * The answer a finished run returns (GET /v1/runs/{id}), as the prism command and the terminal app
8
+ * read it, and the words both show for it. src/find/run.ts writes it.
9
+ */
10
+ /** A quote and where it comes from; `period` is when its figure was true, as the sheet writes it ("fiscal 2026"; since 2026-10-21). */
11
+ export interface Quote {
12
+ text: string | null;
13
+ form: string | null;
14
+ accession: string | null;
15
+ item: string | null;
16
+ filed_at: string | null;
17
+ period?: string | null;
18
+ url: string | null;
19
+ }
20
+ /**
21
+ * The Federal Reserve's rate an amount in another currency was turned into US dollars at (since
22
+ * 2026-10-23): the amount's currency, US dollars per one unit of it, the rate's day and its source.
23
+ */
24
+ export interface Fx {
25
+ currency: string;
26
+ rate: number;
27
+ date: string;
28
+ source: string;
29
+ }
30
+ export interface Row {
31
+ ticker: string;
32
+ name: string;
33
+ market_value?: {
34
+ usd: number;
35
+ as_of: string;
36
+ source: string;
37
+ } | null;
38
+ no_market_value: boolean;
39
+ direction?: string;
40
+ tier?: string;
41
+ influence?: string | null;
42
+ /** The checker's one-line reason. */
43
+ reason?: string | null;
44
+ /**
45
+ * `text` as the filings give it; `amount_usd` and `fx`, its dollars at the Federal Reserve's rate when
46
+ * it is in another currency (since 2026-10-23); `currency`, `multiplier` and `display`, its currency,
47
+ * what its number counts in and how lists show it (later on 2026-10-23; an older answer has none).
48
+ */
49
+ revenue_from_theme: {
50
+ text: string;
51
+ usd?: number;
52
+ amount_usd?: number | null;
53
+ fx?: Fx | null;
54
+ currency?: string | null;
55
+ multiplier?: number | null;
56
+ display?: string;
57
+ period?: string | null;
58
+ quote?: Quote | null;
59
+ } | null;
60
+ no_figure_in_annual_report: boolean;
61
+ /** The filings give a figure, but it gave way to the purity step's estimate, shown instead (since 2026-10-23). */
62
+ figure_gave_way?: boolean;
63
+ /** Prism's estimate of the revenue from the theme, when the filings give no figure, or the one they give gave way (since 2026-10-20). */
64
+ revenue_estimate_usd?: number | null;
65
+ no_filings_to_quote: boolean;
66
+ /** The fact-sheet quotes behind the call. */
67
+ evidence?: Quote[];
68
+ }
69
+ export type Bands = Record<string, Row[]>;
70
+ export interface Answer {
71
+ /** `theme.description`: the run's description, the idea's text after its title, null when it has none (since 2026-10-24; an older answer has no such field). */
72
+ run_id: string;
73
+ took_seconds: number;
74
+ theme: {
75
+ title: string;
76
+ description?: string | null;
77
+ };
78
+ partial: boolean;
79
+ partial_reason?: string;
80
+ /** The day the run asks about: its sheets are from the 13 months before it, and its estimates are of it (since 2026-10-21). */
81
+ as_of?: string;
82
+ /** `from_theme`: the theme's size words that set the market-cap range, when the caller set none (since 2026-10-21). */
83
+ filters: {
84
+ min_market_value_usd: number | null;
85
+ max_market_value_usd: number | null;
86
+ exclude_etfs: boolean;
87
+ exclude_adrs: boolean;
88
+ from_theme?: string;
89
+ };
90
+ reports: {
91
+ purest: Bands;
92
+ biggest: Bands;
93
+ influential: Bands;
94
+ };
95
+ hurt: Bands;
96
+ checked: {
97
+ candidates: number;
98
+ after_filters: number;
99
+ checked: number;
100
+ exposed: number;
101
+ };
102
+ cost: {
103
+ usd: number;
104
+ llm_calls: number;
105
+ };
106
+ versions: Record<string, string>;
107
+ }
108
+ export type ListKey = "purest" | "biggest" | "influential" | "hurt";
109
+ /**
110
+ * The four lists in the order they are shown: the command's headings, the app's tab labels, what each
111
+ * list means in plain words, shown under the app's tabs (Ben, 2026-10-20: "not everybody has the
112
+ * context that I have"), worded from the checker's and the ranking steps' own definitions, and each
113
+ * list's bands.
114
+ */
115
+ export declare const REPORTS: ReadonlyArray<{
116
+ key: ListKey;
117
+ title: string;
118
+ label: string;
119
+ description: string;
120
+ bands: ReadonlyArray<readonly [string, string]>;
121
+ }>;
122
+ export declare const bandsOf: (a: Answer, key: ListKey) => Bands;
123
+ /** What a run is doing, in the words both show while it runs. */
124
+ export declare const STEPS: Record<string, string>;
125
+ /** Pasted text keeps the line breaks and indents of where it came from; one line reads better, so runs of spaces become one. */
126
+ export declare const oneLine: (s: string) => string;
127
+ /**
128
+ * A quoted amount in dollars or its own currency, or null when the text is not an amount, or when its
129
+ * number is not written plainly (since 2026-10-23): a decimal comma ("€1,5 billion"), commas that are
130
+ * not thousands ("1,271,1") and thousands split by spaces ("€3 085 million") are left as written,
131
+ * never read as another number.
132
+ */
133
+ export declare function amountValue(text: string): number | null;
134
+ /**
135
+ * A quoted amount written short from its text alone, as answers and fact sheets from before the API
136
+ * said each amount's currency (2026-10-23) are shown: "US$19,196 million" reads "US$19.2B" (it read
137
+ * "$19.2B" until Ben's ruling of 2026-10-23), "¥13,069,012 thousand" keeps its "¥", since the text
138
+ * alone does not say whose. The same number, rounded; other text is left as it is. A figure of
139
+ * several amounts shows each one, "C$2.9B + C$562M", so it matches the sum the most-money list ranks by.
140
+ */
141
+ export declare function shortAmount(text: string): string;
142
+ /**
143
+ * An amount from its text, as before the API said its currency (Ben, 2026-10-22): as written, short;
144
+ * or, when Prism turned it into US dollars at the Federal Reserve's rate, the dollars first and the
145
+ * original in brackets, "US$1.2B (R$6.5B)". A word such as "over" stays with both. The rate is named
146
+ * once, after the figure's period (rateNote). The fact itself is never changed: its number stays with
147
+ * its quote. An older answer carries no currency of its own, but a rate names one: its originals are
148
+ * read in that currency, so a shared "¥" shows as "CNY" and "MXN 8,554,356 thousand" as "MXN 8.6B".
149
+ */
150
+ export declare function withUsd(text: string, usd: number | null | undefined, fx: Fx | null | undefined): string;
151
+ /** The rate an amount was converted at, when it was: its dollars and its rate both came with it. */
152
+ export declare const convertedAt: (usd: number | null | undefined, fx: Fx | null | undefined) => Fx | null;
153
+ /**
154
+ * One amount as Prism's API gives it (Ben, 2026-10-23: "Use the standard format: US$303.2B in lists,
155
+ * US$303,154 million beside the quote, ISO codes where a currency symbol is shared"): its text as the
156
+ * sheet writes it; its dollars and rate when converted; `currency`, which the API settles from the
157
+ * report and the quote, null when it is not clear, absent from an older Prism; `display`, how lists
158
+ * show it; and `scaleConfirmed` false when the fact's scale is not confirmed.
159
+ */
160
+ export interface ApiAmount {
161
+ text: string;
162
+ usd?: number | null;
163
+ fx?: Fx | null;
164
+ currency?: string | null;
165
+ display?: string | null;
166
+ scaleConfirmed?: boolean;
167
+ }
168
+ /** The amount ready to show, with the currency the API settled: the app has neither the report nor, for a figure, every quote. */
169
+ export declare function toShow(a: ApiAmount): AmountToShow;
170
+ /** An amount in lists and sentences: the API's `display`, "US$2.6B (R$14.2B)", "PEN 10.9B"; from its text, as before, when the API did not say its currency. */
171
+ export declare function amountText(a: ApiAmount): string;
172
+ /**
173
+ * An amount inside a name's brackets, so brackets never nest: "US$54.8B, or R$300.0B", a note after a
174
+ * comma, "¥700 million, currency not stated".
175
+ */
176
+ export declare function bracketedAmount(a: ApiAmount): string;
177
+ /**
178
+ * An amount beside its quote, with when it was true: "US$303,154 million in fiscal 2026", "R$14,153.8
179
+ * million in 2025 (about US$2.6B at the Federal Reserve's rate of 2025-12-31)". From an older Prism, as
180
+ * the lists show it, with the rate after the period.
181
+ */
182
+ export declare function amountBesideQuote(a: ApiAmount, period: string | null | undefined): string;
183
+ /** An amount's size, to sort by: its dollars when converted, else its number as read (as written, for an older Prism); null when it is no amount. */
184
+ export declare function amountSize(a: ApiAmount): number | null;
185
+ /** ", at the Federal Reserve's rate of 2025-12-31" to end a figure with, or "". */
186
+ export declare const rateClause: (fxs: ReadonlyArray<Fx | null | undefined>) => string;
187
+ /**
188
+ * When a figure was true, as its sheet writes it, in words that follow the figure (Ben, 2026-10-21:
189
+ * "show the year next to each figure"): "2025" reads "in 2025", "Fiscal 2026" "in fiscal 2026", "year
190
+ * ended March 31, 2026" "in the year ended March 31, 2026", a day "as of December 31, 2025". A period
191
+ * with its own preposition keeps it (2026-10-24: the light pass's "During 2023, 2024 and 2025" read
192
+ * "in During 2023, 2024 and 2025"), and "fiscal Year 2025" reads "fiscal year 2025". Empty when the
193
+ * sheet gives no period.
194
+ */
195
+ export declare function periodPhrase(period: string | null | undefined, preposition?: "in" | "for"): string;
196
+ /** A run's filters in words: "market cap US$2.0B to US$10B, from "mid-cap" in the theme · no ETFs". */
197
+ export declare function filtersText(f: Answer["filters"]): string;
198
+ /** A most-money figure as an amount from the API: its text, dollars, rate, currency and display. */
199
+ export declare const figureAmount: (f: NonNullable<Row["revenue_from_theme"]>) => ApiAmount;
200
+ /**
201
+ * A most-money figure as a list shows it, with when it was true (Ben, 2026-10-21) and its currency
202
+ * (2026-10-23): "US$18.9B in fiscal 2025", or Prism's estimate. In another currency, its dollars
203
+ * first: "US$1.2B (R$6.5B) in 2025, at the Federal Reserve's rate of 2025-12-31".
204
+ */
205
+ export declare function listFigure(row: Row): string;
206
+ /** US dollars rounded, for Prism's own reports and logs: "$49B", "$4.0B", "$500M". */
207
+ export declare const money: (usd: number) => string;
208
+ /** US dollars rounded as readers see a market cap, an estimate or a filter, with the currency (Ben, 2026-10-23): "US$49B", "US$4.0B", "US$500M". */
209
+ export declare const usdMoney: (usd: number) => string;
210
+ export declare const duration: (s: number) => string;
211
+ /** One line on a running run: its step, how far the step is, the time so far and the cost so far. */
212
+ export declare function progressLine(progress: {
213
+ step?: string;
214
+ done?: number;
215
+ of?: number;
216
+ } | undefined, seconds: number, usd: number): string;
217
+ /** SEC names come in capitals ("CAMECO CORP"); print them in normal case, keeping short codes. */
218
+ export declare function displayName(name: string): string;
219
+ //# sourceMappingURL=answer.d.ts.map