@gentbajko/slopify 1.1.0 → 1.2.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.
@@ -25,6 +25,7 @@ const responseSchema = z.object({
25
25
  models: z
26
26
  .array(z.object({
27
27
  value: modelId,
28
+ resolvedModel: modelId.optional(),
28
29
  displayName: modelName,
29
30
  supportedEffortLevels: z.array(z.string()).max(10).optional(),
30
31
  }))
@@ -72,14 +73,24 @@ function choices(event, requestId) {
72
73
  throw unavailable();
73
74
  const models = new Map();
74
75
  for (const row of parsed.data.response.response.models) {
75
- if (models.has(row.value))
76
- continue;
77
76
  const modes = row.supportedEffortLevels?.filter((value) => supportedEfforts.includes(value));
78
- models.set(row.value, {
79
- id: row.value,
80
- name: row.displayName,
81
- ...(modes?.length ? { thinkingModes: modes } : {}),
82
- });
77
+ const thinking = modes?.length ? { thinkingModes: modes } : {};
78
+ if (!models.has(row.value))
79
+ models.set(row.value, {
80
+ id: row.value,
81
+ name: row.resolvedModel === undefined || row.resolvedModel === row.value
82
+ ? row.displayName
83
+ : `${row.displayName} → ${row.resolvedModel}`,
84
+ ...thinking,
85
+ });
86
+ // Claude advertises stable aliases and their current concrete targets.
87
+ // Offer both so the user can choose a moving alias or pin a version.
88
+ if (row.resolvedModel !== undefined && !models.has(row.resolvedModel))
89
+ models.set(row.resolvedModel, {
90
+ id: row.resolvedModel,
91
+ name: `${row.resolvedModel} (exact model)`,
92
+ ...thinking,
93
+ });
83
94
  }
84
95
  return [...models.values()];
85
96
  }
@@ -30,20 +30,49 @@ export async function nodeGeminiModels(binary) {
30
30
  // A candidate is an optional package layout, not the final discovery result.
31
31
  }
32
32
  }
33
+ // Recent Gemini CLI releases ship only bundled chunks. Follow the entry
34
+ // point's literal chunk imports and read their model constants as text.
35
+ const bundled = await bundledModels(entry);
36
+ if (bundled.length > 1)
37
+ return bundled;
33
38
  throw new Error("No installed model metadata");
34
39
  }
35
40
  catch {
36
41
  throw new Error("Gemini CLI model metadata is unavailable. Use a documented CLI alias or enter a custom model ID.");
37
42
  }
38
43
  }
44
+ async function bundledModels(entry) {
45
+ const source = await readCatalogueFile(entry, 256 * 1024);
46
+ const imports = new Set();
47
+ for (const match of source.matchAll(/\b(?:from\s+|import\s*)["']\.\/(chunk-[A-Za-z0-9-]+\.js)["']/g)) {
48
+ if (match[1] !== undefined)
49
+ imports.add(match[1]);
50
+ }
51
+ if (imports.size === 0 || imports.size > 32)
52
+ return [];
53
+ for (const name of imports) {
54
+ try {
55
+ const source = await readCatalogueFile(join(dirname(entry), name), 24 * 1024 * 1024);
56
+ if (!source.includes("packages/core/dist/src/config/models.js"))
57
+ continue;
58
+ const models = parseGeminiModels(source);
59
+ if (models.length > 1)
60
+ return models;
61
+ }
62
+ catch {
63
+ // Other chunks can be absent or too large; do not execute them.
64
+ }
65
+ }
66
+ return [];
67
+ }
39
68
  function parseGeminiModels(source) {
40
69
  const models = new Map();
41
70
  let pro = false;
42
71
  let flash = false;
43
72
  let flashLite = false;
44
- // Read literal exported model constants only. Never import or execute an
73
+ // Read literal model constants only. Never import or execute an
45
74
  // installed package, parse comments as entries, or inspect login/settings.
46
- const declarations = /^export (?:const|let) ([A-Z][A-Z0-9_]*)\s*=\s*(['"])([^'"\r\n]+)\2\s*;/gm;
75
+ const declarations = /^(?:export\s+)?(?:const|let|var)\s+([A-Z][A-Z0-9_]*)\s*=\s*(['"])([^'"\r\n]+)\2\s*;/gm;
47
76
  const uncommented = source.replace(/\/\*[\s\S]*?\*\//g, "");
48
77
  for (const match of uncommented.matchAll(declarations)) {
49
78
  const name = match[1];
package/dist/main.js CHANGED
@@ -220,12 +220,16 @@ export async function boot(config) {
220
220
  currentVersion: version,
221
221
  previousUpdateFailed: process.env.SLOPIFY_UPDATE_FAILED === "1",
222
222
  now: () => Date.now(),
223
- latest: () => publishedVersion(globalThis.fetch),
224
- unsupported: () => !existsSync(oldEntry) || !existsSync(workerEntry)
225
- ? "Run Slopify from its installed package to use in-app updates."
226
- : npm === undefined
227
- ? "npm is unavailable. Install Node.js with npm to use in-app updates."
228
- : undefined,
223
+ latest: () => process.env.SLOPIFY_DISABLE_UPDATES === "1"
224
+ ? Promise.resolve(version)
225
+ : publishedVersion(globalThis.fetch),
226
+ unsupported: () => process.env.SLOPIFY_DISABLE_UPDATES === "1"
227
+ ? "This container is updated by pulling a new image and recreating it."
228
+ : !existsSync(oldEntry) || !existsSync(workerEntry)
229
+ ? "Run Slopify from its installed package to use in-app updates."
230
+ : npm === undefined
231
+ ? "npm is unavailable. Install Node.js with npm to use in-app updates."
232
+ : undefined,
229
233
  busy: () => updateDb.prepare("SELECT 1 FROM stages WHERE state = 'running' LIMIT 1").get() !==
230
234
  undefined ||
231
235
  updateDb
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gentbajko/slopify",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Turn a prompt into a finished video, locally, with your own provider keys",
5
5
  "license": "MIT",
6
6
  "author": "Gent Bajko",