@amenophis1er/foreman 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 (65) hide show
  1. package/DESIGN.md +408 -0
  2. package/LICENSE +15 -0
  3. package/README.md +133 -0
  4. package/bin/foreman.mjs +58 -0
  5. package/package.json +68 -0
  6. package/scripts/prepare.mjs +48 -0
  7. package/skills/director/SKILL.md +65 -0
  8. package/src/anthropic-models.ts +54 -0
  9. package/src/ask.test.ts +88 -0
  10. package/src/ask.ts +95 -0
  11. package/src/attachments.test.ts +33 -0
  12. package/src/attachments.ts +60 -0
  13. package/src/cli.test.ts +27 -0
  14. package/src/cli.ts +297 -0
  15. package/src/codex.test.ts +328 -0
  16. package/src/codex.ts +196 -0
  17. package/src/cost-basis.test.ts +76 -0
  18. package/src/deck.test.ts +402 -0
  19. package/src/deck.ts +892 -0
  20. package/src/fork.test.ts +31 -0
  21. package/src/gateway/ledger.cjs +326 -0
  22. package/src/gateway/ledger.test.ts +255 -0
  23. package/src/gateway/llm-gateway.cjs +1411 -0
  24. package/src/gateway/llm-gateway.test.ts +478 -0
  25. package/src/gateway.test.ts +226 -0
  26. package/src/gateway.ts +309 -0
  27. package/src/instance.ts +124 -0
  28. package/src/models.test.ts +147 -0
  29. package/src/models.ts +158 -0
  30. package/src/notify/commands.test.ts +28 -0
  31. package/src/notify/commands.ts +73 -0
  32. package/src/notify/telegram.ts +259 -0
  33. package/src/notify.test.ts +343 -0
  34. package/src/notify.ts +495 -0
  35. package/src/ollama.test.ts +49 -0
  36. package/src/ollama.ts +49 -0
  37. package/src/openai-prices.test.ts +58 -0
  38. package/src/openai-prices.ts +106 -0
  39. package/src/orchestrator.test.ts +1147 -0
  40. package/src/orchestrator.ts +2325 -0
  41. package/src/planner.test.ts +60 -0
  42. package/src/planner.ts +505 -0
  43. package/src/policy.test.ts +411 -0
  44. package/src/policy.ts +599 -0
  45. package/src/preflight.ts +348 -0
  46. package/src/prices.test.ts +69 -0
  47. package/src/prices.ts +90 -0
  48. package/src/provider.test.ts +366 -0
  49. package/src/provider.ts +502 -0
  50. package/src/secrets.test.ts +143 -0
  51. package/src/secrets.ts +66 -0
  52. package/src/server.ts +1992 -0
  53. package/src/services.test.ts +53 -0
  54. package/src/services.ts +102 -0
  55. package/src/sse-events.test.ts +83 -0
  56. package/src/store.test.ts +119 -0
  57. package/src/store.ts +346 -0
  58. package/src/tailscale.test.ts +32 -0
  59. package/src/tailscale.ts +79 -0
  60. package/src/title.ts +138 -0
  61. package/src/types.ts +442 -0
  62. package/ui/dist/assets/index-LAj0Dy9p.css +1 -0
  63. package/ui/dist/assets/index-lcBy-uRZ.js +65 -0
  64. package/ui/dist/favicon.svg +8 -0
  65. package/ui/dist/index.html +14 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * OpenAI list prices — the one table Foreman keeps, by decision.
3
+ *
4
+ * The provider model's rule is that a dollar figure comes from whoever sends
5
+ * the bill. OpenAI's API publishes no rates, and the human wants direct
6
+ * OpenAI rather than a reseller that does, so this table exists — under the
7
+ * one condition that makes it honest: **provenance made visible.** It names
8
+ * its source, it names the day it was checked, and it is the only place in
9
+ * the codebase where a price is written down by a person. A model not in it
10
+ * stays `unpriced`; a wrong entry is a bug with a date on it, not a guess.
11
+ *
12
+ * SOURCE https://developers.openai.com/api/docs/pricing (Standard tier)
13
+ * VERIFIED 2026-09-05
14
+ * UNIT the page lists USD per 1M tokens; stored here per token, the unit
15
+ * `ModelPrice` uses, so the factor of a million lives in exactly one
16
+ * line (`perM`) and nowhere else.
17
+ *
18
+ * KNOWN GAPS, on purpose:
19
+ * - Long-context tiers. gpt-5.5, gpt-5.4 and their pro variants charge more
20
+ * past 272K input tokens; this table holds the base tier. A run that
21
+ * routinely exceeds that will read low — the direction that is not safe —
22
+ * so the description says so.
23
+ * - Batch and Flex tiers are cheaper and not represented. Foreman does not
24
+ * use them.
25
+ * - Dated snapshots (`gpt-5.4-2026-03-01`) resolve to their family by
26
+ * longest-prefix match. A new family not listed here is unpriced until
27
+ * someone updates this file and the VERIFIED line with it.
28
+ *
29
+ * To update: open the source URL, change the numbers, change VERIFIED. That
30
+ * is the whole procedure, and the reason it lives in its own small file.
31
+ */
32
+ import type { ModelPrice } from './prices.js';
33
+
34
+ /** USD per 1M tokens → USD per token. The only conversion in this file. */
35
+ const perM = (usdPerMillion: number): number => usdPerMillion / 1_000_000;
36
+
37
+ interface Row { input: number; cachedInput?: number; output: number }
38
+
39
+ /** Standard tier, USD per 1M tokens, exactly as listed on VERIFIED date. */
40
+ const LIST: Record<string, Row> = {
41
+ 'gpt-6-astra': { input: 10.00, cachedInput: 1.00, output: 50.00 },
42
+ 'gpt-5.6-sol': { input: 4.00, cachedInput: 0.40, output: 20.00 },
43
+ 'gpt-5.6-terra': { input: 2.00, cachedInput: 0.20, output: 12.00 },
44
+ 'gpt-5.6-luna': { input: 0.20, cachedInput: 0.02, output: 1.20 },
45
+ 'gpt-5.5': { input: 5.00, cachedInput: 0.50, output: 30.00 }, // <272K
46
+ 'gpt-5.5-pro': { input: 30.00, output: 180.00 }, // <272K
47
+ 'gpt-5.4': { input: 2.50, cachedInput: 0.25, output: 15.00 }, // <272K
48
+ 'gpt-5.4-mini': { input: 0.75, cachedInput: 0.075, output: 4.50 },
49
+ 'gpt-5.4-nano': { input: 0.20, cachedInput: 0.02, output: 1.25 },
50
+ 'gpt-5.4-pro': { input: 30.00, output: 180.00 }, // <272K
51
+ 'gpt-5.2': { input: 1.75, cachedInput: 0.175, output: 14.00 },
52
+ 'gpt-5.2-pro': { input: 21.00, output: 168.00 },
53
+ 'gpt-5.1': { input: 1.25, cachedInput: 0.125, output: 10.00 },
54
+ 'gpt-5': { input: 1.25, cachedInput: 0.125, output: 10.00 },
55
+ 'gpt-5-mini': { input: 0.25, cachedInput: 0.025, output: 2.00 },
56
+ 'gpt-5-nano': { input: 0.05, cachedInput: 0.005, output: 0.40 },
57
+ 'gpt-5-pro': { input: 15.00, output: 120.00 },
58
+ 'o1': { input: 15.00, cachedInput: 7.50, output: 60.00 },
59
+ 'o1-pro': { input: 150.00, output: 600.00 },
60
+ 'o3-pro': { input: 20.00, output: 80.00 },
61
+ 'o3': { input: 2.00, cachedInput: 0.50, output: 8.00 },
62
+ 'o4-mini': { input: 1.10, cachedInput: 0.275, output: 4.40 },
63
+ 'o3-mini': { input: 1.10, cachedInput: 0.55, output: 4.40 },
64
+ };
65
+
66
+ export const OPENAI_PRICES_VERIFIED = '2026-09-05';
67
+ export const OPENAI_PRICES_SOURCE = 'https://developers.openai.com/api/docs/pricing';
68
+
69
+ /** Hosts whose models this table prices. Nothing else — a reseller sets its own rates. */
70
+ export function isOpenAiHost(upstreamUrl: string | undefined): boolean {
71
+ try { return !!upstreamUrl && /(^|\.)api\.openai\.com$/i.test(new URL(upstreamUrl).hostname); }
72
+ catch { return false; }
73
+ }
74
+
75
+ /**
76
+ * The list price for a model id, or null if the table does not know it.
77
+ *
78
+ * Exact id first; otherwise the longest family key the id starts with
79
+ * followed by a dash (so `gpt-5.4-2026-03-01` is `gpt-5.4`, and `gpt-5.4-mini`
80
+ * is not mistaken for `gpt-5.4`). Cached-input goes into `cacheRead`; OpenAI
81
+ * has no cache-write charge, so `cacheWrite` is the input rate — which
82
+ * `priceUsage` uses as the default when the field is absent.
83
+ */
84
+ export function openaiPrice(modelId: string | undefined): ModelPrice | null {
85
+ if (!modelId) return null;
86
+ const id = modelId.trim().toLowerCase();
87
+ let key: string | undefined = LIST[id] ? id : undefined;
88
+ if (!key) {
89
+ key = Object.keys(LIST)
90
+ .filter((k) => id.startsWith(`${k}-`))
91
+ .sort((a, b) => b.length - a.length)[0];
92
+ }
93
+ if (!key) return null;
94
+ const r = LIST[key];
95
+ const price: ModelPrice = { input: perM(r.input), output: perM(r.output) };
96
+ if (r.cachedInput !== undefined) price.cacheRead = perM(r.cachedInput);
97
+ return price;
98
+ }
99
+
100
+ /** For the picker's note: "$2.50 in / $15.00 out per million tokens · OpenAI list, verified 2026-09-05". */
101
+ export function openaiPriceNote(modelId: string): string | null {
102
+ const p = openaiPrice(modelId);
103
+ if (!p) return null;
104
+ const m = (n: number) => `$${(n * 1_000_000).toFixed(2)}`;
105
+ return `${m(p.input)} in / ${m(p.output)} out per million tokens · OpenAI list price, verified ${OPENAI_PRICES_VERIFIED}`;
106
+ }