@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.
- package/README.md +42 -26
- package/apps/desktop/README.md +1 -1
- package/apps/desktop/application/html-overlay-renderer.ts +6 -0
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/args.ts +63 -1
- package/apps/desktop/cli/command-host-resources.ts +5 -0
- package/apps/desktop/cli/commands.ts +27 -1
- package/apps/desktop/cli/help.ts +43 -21
- package/apps/desktop/cli/html-film-init.ts +158 -0
- package/apps/desktop/cli/html-film-names.ts +17 -0
- package/apps/desktop/cli/html-film.ts +471 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +29 -2
- package/apps/desktop/cli/html-scene.ts +3 -3
- package/apps/desktop/cli/main.ts +1 -0
- package/apps/desktop/cli/menubar-status.ts +156 -0
- package/apps/desktop/cli/menubar.ts +88 -136
- package/apps/desktop/cli/portable-surface.ts +55 -1
- package/apps/desktop/dist/cli/main.js +353 -328
- package/apps/desktop/html-overlay/film.ts +418 -0
- package/dist/cli.js +19 -9
- package/dist/generate.js +1 -1
- package/dist/{index-8bhp7cj2.js → index-842t02pp.js} +1 -1
- package/dist/{index-jab2mg0s.js → index-nv3byqa1.js} +20 -4
- package/dist/index-p74xy76d.js +3 -0
- package/dist/{index-24w75s6a.js → index-y8spxs77.js} +10 -7
- package/dist/index.js +1 -1
- package/dist/operations.js +1 -1
- package/dist/workflow.js +1 -1
- package/docs/README.md +5 -5
- package/examples/html/launch-film/README.md +51 -0
- package/examples/html/launch-film/build.ts +158 -0
- package/examples/html/launch-film/film.css +593 -0
- package/examples/html/launch-film/film.html +85 -0
- package/examples/html/launch-film/film.js +410 -0
- package/examples/html/launch-film/film.json +63 -0
- package/examples/html/launch-film/mockups.tsx +74 -0
- package/examples/html/launch-film/timeline.ts +57 -0
- package/package.json +14 -8
- package/skills/slopcamera/SKILL.md +3 -2
- package/skills/slopcamera/references/install.md +3 -3
- package/skills/slopcamera/references/launch-films.md +154 -0
- package/skills/slopcamera/references/visual-communication.md +1 -1
- package/src/cli.ts +138 -19
- package/src/generate.ts +44 -1
- package/src/generation-pricing.ts +237 -0
- package/src/icon-set.ts +1086 -0
- package/src/icon.ts +396 -59
- package/src/index.ts +1 -0
- package/src/operations.ts +52 -0
- package/src/support.ts +6 -0
- package/src/version.ts +1 -1
- 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
|
+
}
|