@hraness/slopcamera 3.5.0 → 3.7.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 (52) hide show
  1. package/README.md +42 -26
  2. package/apps/desktop/README.md +1 -1
  3. package/apps/desktop/application/html-overlay-renderer.ts +6 -0
  4. package/apps/desktop/application/operation.ts +1 -1
  5. package/apps/desktop/cli/args.ts +63 -1
  6. package/apps/desktop/cli/command-host-resources.ts +5 -0
  7. package/apps/desktop/cli/commands.ts +27 -1
  8. package/apps/desktop/cli/help.ts +43 -21
  9. package/apps/desktop/cli/html-film-init.ts +158 -0
  10. package/apps/desktop/cli/html-film-names.ts +17 -0
  11. package/apps/desktop/cli/html-film.ts +471 -0
  12. package/apps/desktop/cli/html-overlay-renderer.ts +29 -2
  13. package/apps/desktop/cli/html-scene.ts +3 -3
  14. package/apps/desktop/cli/main.ts +1 -0
  15. package/apps/desktop/cli/menubar-status.ts +156 -0
  16. package/apps/desktop/cli/menubar.ts +88 -136
  17. package/apps/desktop/cli/portable-surface.ts +55 -1
  18. package/apps/desktop/dist/cli/main.js +353 -328
  19. package/apps/desktop/html-overlay/film.ts +418 -0
  20. package/dist/cli.js +19 -9
  21. package/dist/generate.js +1 -1
  22. package/dist/{index-8bhp7cj2.js → index-842t02pp.js} +1 -1
  23. package/dist/{index-jab2mg0s.js → index-nv3byqa1.js} +20 -4
  24. package/dist/index-p74xy76d.js +3 -0
  25. package/dist/{index-24w75s6a.js → index-y8spxs77.js} +10 -7
  26. package/dist/index.js +1 -1
  27. package/dist/operations.js +1 -1
  28. package/dist/workflow.js +1 -1
  29. package/docs/README.md +5 -5
  30. package/examples/html/launch-film/README.md +51 -0
  31. package/examples/html/launch-film/build.ts +158 -0
  32. package/examples/html/launch-film/film.css +593 -0
  33. package/examples/html/launch-film/film.html +85 -0
  34. package/examples/html/launch-film/film.js +410 -0
  35. package/examples/html/launch-film/film.json +63 -0
  36. package/examples/html/launch-film/mockups.tsx +74 -0
  37. package/examples/html/launch-film/timeline.ts +57 -0
  38. package/package.json +14 -8
  39. package/skills/slopcamera/SKILL.md +3 -2
  40. package/skills/slopcamera/references/install.md +3 -3
  41. package/skills/slopcamera/references/launch-films.md +154 -0
  42. package/skills/slopcamera/references/visual-communication.md +1 -1
  43. package/src/cli.ts +138 -19
  44. package/src/generate.ts +44 -1
  45. package/src/generation-pricing.ts +237 -0
  46. package/src/icon-set.ts +1086 -0
  47. package/src/icon.ts +396 -59
  48. package/src/index.ts +1 -0
  49. package/src/operations.ts +52 -0
  50. package/src/support.ts +6 -0
  51. package/src/version.ts +1 -1
  52. package/dist/index-77fjfg9f.js +0 -3
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Provider list prices for the image models Slopcamera can bill, and the
3
+ * cost of one Vercel AI Gateway image call derived from them.
4
+ *
5
+ * The AI Gateway charges the provider's list price with no markup
6
+ * (https://vercel.com/docs/ai-gateway/pricing, checked 2026-09-26), so the
7
+ * OpenAI and Recraft list prices below are what one call costs. Every price
8
+ * is integer micro-USD.
9
+ *
10
+ * A request never sets size or quality, so OpenAI chooses them ("auto"). The
11
+ * worst case is therefore the highest quality at the largest size, plus the
12
+ * prompt's text input tokens. Slopcamera never sends reference or edit
13
+ * images, so every input token is a text token.
14
+ */
15
+
16
+ export type ProviderCostBasis = "reported" | "contractual" | "estimated" | "unknown"
17
+
18
+ /** One provider cost line, in the shape Hraness Credits accepts. */
19
+ export interface SlopcameraProviderCost {
20
+ readonly provider: string
21
+ readonly operation: string
22
+ readonly microUsd: number
23
+ readonly basis: ProviderCostBasis
24
+ }
25
+
26
+ /** Token counts a Gateway image call returned, as the AI SDK reports them. */
27
+ export interface SlopcameraImageUsage {
28
+ readonly inputTokens?: number | undefined
29
+ readonly outputTokens?: number | undefined
30
+ }
31
+
32
+ interface TokenPricedImageModel {
33
+ readonly kind: "tokens"
34
+ /** Text input tokens, micro-USD per million. */
35
+ readonly textInputPerMillion: number
36
+ /**
37
+ * Output tokens, micro-USD per million. This is the image output rate,
38
+ * which is the highest output rate the model has, so it also bounds any
39
+ * text output tokens.
40
+ */
41
+ readonly outputPerMillion: number
42
+ /** OpenAI's published price for one high-quality 1024x1536 image. */
43
+ readonly worstImage: number
44
+ readonly source: string
45
+ }
46
+
47
+ interface ImagePricedModel {
48
+ readonly kind: "image"
49
+ /** Flat price for one image in the style Slopcamera requests (none). */
50
+ readonly perImage: number
51
+ readonly source: string
52
+ }
53
+
54
+ export type SlopcameraImageModelPrice = TokenPricedImageModel | ImagePricedModel
55
+
56
+ export const slopcameraProviderPricesCheckedAt = "2026-09-27"
57
+
58
+ /**
59
+ * Cited list prices. A model without an entry here cannot be billed: the
60
+ * hosted service refuses it before any hold.
61
+ */
62
+ export const slopcameraImageModelPrices: Readonly<
63
+ Record<string, SlopcameraImageModelPrice>
64
+ > = Object.freeze({
65
+ "openai/gpt-image-1": Object.freeze({
66
+ kind: "tokens",
67
+ textInputPerMillion: 5_000_000,
68
+ outputPerMillion: 40_000_000,
69
+ worstImage: 250_000,
70
+ source: "https://developers.openai.com/api/docs/models/gpt-image-1",
71
+ }),
72
+ "openai/gpt-image-1-mini": Object.freeze({
73
+ kind: "tokens",
74
+ textInputPerMillion: 2_000_000,
75
+ outputPerMillion: 8_000_000,
76
+ worstImage: 52_000,
77
+ source: "https://developers.openai.com/api/docs/models/gpt-image-1-mini",
78
+ }),
79
+ "openai/gpt-image-1.5": Object.freeze({
80
+ kind: "tokens",
81
+ textInputPerMillion: 5_000_000,
82
+ outputPerMillion: 32_000_000,
83
+ worstImage: 200_000,
84
+ source: "https://developers.openai.com/api/docs/models/gpt-image-1.5",
85
+ }),
86
+ "recraft/recraft-v4.1-utility": Object.freeze({
87
+ kind: "image",
88
+ perImage: 35_000,
89
+ source: "https://vercel.com/ai-gateway/models/recraft-v4.1-utility",
90
+ }),
91
+ "bfl/flux-pro-1.1": Object.freeze({
92
+ kind: "image",
93
+ perImage: 40_000,
94
+ source: "https://vercel.com/ai-gateway/models/flux-pro-1.1",
95
+ }),
96
+ "bfl/flux-pro-1.1-ultra": Object.freeze({
97
+ kind: "image",
98
+ perImage: 60_000,
99
+ source: "https://vercel.com/ai-gateway/models/flux-pro-1.1-ultra",
100
+ }),
101
+ "bfl/flux-kontext-pro": Object.freeze({
102
+ kind: "image",
103
+ perImage: 40_000,
104
+ source: "https://vercel.com/ai-gateway/models/flux-kontext-pro",
105
+ }),
106
+ "bfl/flux-kontext-max": Object.freeze({
107
+ kind: "image",
108
+ perImage: 80_000,
109
+ source: "https://vercel.com/ai-gateway/models/flux-kontext-max",
110
+ }),
111
+ })
112
+
113
+ /**
114
+ * Tokens allowed on top of the prompt's UTF-8 byte count when no usage came
115
+ * back. A byte-level BPE token covers at least one byte, so the byte count
116
+ * bounds the prompt's own tokens; the allowance covers request framing.
117
+ */
118
+ export const slopcameraPromptTokenAllowance = 64
119
+
120
+ export const slopcameraCostProvider = "vercel-ai-gateway"
121
+
122
+ export function slopcameraImageModelPrice(
123
+ model: string,
124
+ ): SlopcameraImageModelPrice | undefined {
125
+ return Object.hasOwn(slopcameraImageModelPrices, model)
126
+ ? slopcameraImageModelPrices[model]
127
+ : undefined
128
+ }
129
+
130
+ function tokenCost(tokens: number, perMillion: number): number {
131
+ // Round up so a partial micro-dollar is never dropped.
132
+ return Math.ceil((tokens * perMillion) / 1_000_000)
133
+ }
134
+
135
+ function promptBytes(prompt: string): number {
136
+ return Buffer.byteLength(prompt, "utf8")
137
+ }
138
+
139
+ function validTokenCount(value: unknown): value is number {
140
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0
141
+ }
142
+
143
+ /**
144
+ * The most one image call for this model and prompt can cost: highest
145
+ * quality at the largest size, and every prompt byte counted as a token.
146
+ */
147
+ export function worstCaseImageCostMicroUsd(
148
+ model: string,
149
+ prompt: string,
150
+ ): number | undefined {
151
+ const price = slopcameraImageModelPrice(model)
152
+ if (price === undefined) return undefined
153
+ if (price.kind === "image") return price.perImage
154
+ return (
155
+ price.worstImage +
156
+ tokenCost(
157
+ promptBytes(prompt) + slopcameraPromptTokenAllowance,
158
+ price.textInputPerMillion,
159
+ )
160
+ )
161
+ }
162
+
163
+ function estimatedLines(
164
+ model: string,
165
+ prompt: string,
166
+ price: SlopcameraImageModelPrice,
167
+ ): SlopcameraProviderCost[] {
168
+ if (price.kind === "image") {
169
+ return [{
170
+ provider: slopcameraCostProvider,
171
+ operation: `${model} image`,
172
+ microUsd: price.perImage,
173
+ basis: "estimated",
174
+ }]
175
+ }
176
+ return [
177
+ {
178
+ provider: slopcameraCostProvider,
179
+ operation: `${model} image output`,
180
+ microUsd: price.worstImage,
181
+ basis: "estimated",
182
+ },
183
+ {
184
+ provider: slopcameraCostProvider,
185
+ operation: `${model} text input`,
186
+ microUsd: tokenCost(
187
+ promptBytes(prompt) + slopcameraPromptTokenAllowance,
188
+ price.textInputPerMillion,
189
+ ),
190
+ basis: "estimated",
191
+ },
192
+ ]
193
+ }
194
+
195
+ /**
196
+ * Cost lines for one image call that reached the provider. Returned usage
197
+ * prices the call at the official per-token rates (basis "reported");
198
+ * without usage, the worst case stands in (basis "estimated").
199
+ */
200
+ export function imageCallCosts(
201
+ model: string,
202
+ prompt: string,
203
+ usage: SlopcameraImageUsage | undefined,
204
+ ): SlopcameraProviderCost[] {
205
+ const price = slopcameraImageModelPrice(model)
206
+ if (price === undefined) {
207
+ return [{
208
+ provider: slopcameraCostProvider,
209
+ operation: `${model} image`,
210
+ microUsd: 0,
211
+ basis: "unknown",
212
+ }]
213
+ }
214
+ if (
215
+ price.kind === "tokens" &&
216
+ usage !== undefined &&
217
+ validTokenCount(usage.inputTokens) &&
218
+ validTokenCount(usage.outputTokens) &&
219
+ usage.outputTokens > 0
220
+ ) {
221
+ return [
222
+ {
223
+ provider: slopcameraCostProvider,
224
+ operation: `${model} image output`,
225
+ microUsd: tokenCost(usage.outputTokens, price.outputPerMillion),
226
+ basis: "reported",
227
+ },
228
+ {
229
+ provider: slopcameraCostProvider,
230
+ operation: `${model} text input`,
231
+ microUsd: tokenCost(usage.inputTokens, price.textInputPerMillion),
232
+ basis: "reported",
233
+ },
234
+ ]
235
+ }
236
+ return estimatedLines(model, prompt, price)
237
+ }