@z0ne/installer 0.1.0 → 0.1.2

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/lib/main.js CHANGED
@@ -124,6 +124,40 @@ async function install(flags) {
124
124
  const rowsById = Object.fromEntries(rows.map((r) => [r.id, r]));
125
125
  const defs = api.defaultModels(ids);
126
126
  const modelItems = ids.map((id) => ({ value: id, label: id, hint: hintOf(rowsById[id]) }));
127
+ const vendorOf = (id) => (rowsById[id] && rowsById[id].vendor) || "";
128
+ const SHOW_ALL = "__show_all__";
129
+ const showAllItem = { value: SHOW_ALL, label: "-> show all " + ids.length + " models", hint: "full catalog" };
130
+
131
+ // Novice-friendly picker: a curated shortlist (preferred ids first, then
132
+ // one model per remaining vendor, max 11 so the "show all" row still fits
133
+ // the 12-row picker window without scrolling). Non-TTY runs never see it.
134
+ const shortlistItems = (preferredIds) => {
135
+ const seen = new Set();
136
+ const items = [];
137
+ for (const id of preferredIds) {
138
+ if (ids.includes(id) && !seen.has(id)) {
139
+ seen.add(id);
140
+ items.push({ value: id, label: id, hint: hintOf(rowsById[id]) });
141
+ }
142
+ }
143
+ const vendorTaken = new Set(items.map((it) => vendorOf(it.value)));
144
+ for (const id of ids) {
145
+ if (items.length >= 11) break;
146
+ const v = vendorOf(id);
147
+ if (seen.has(id) || vendorTaken.has(v)) continue;
148
+ seen.add(id);
149
+ vendorTaken.add(v);
150
+ items.push({ value: id, label: id, hint: hintOf(rowsById[id]) });
151
+ }
152
+ return items;
153
+ };
154
+
155
+ const pickModel = async (title, shortItems, def) => {
156
+ if (!shortItems.length) return ui.selectOne(title, modelItems, def);
157
+ const first = await ui.selectOne(title, shortItems.concat([showAllItem]), def);
158
+ if (first !== SHOW_ALL) return first;
159
+ return ui.selectOne(title, modelItems, def);
160
+ };
127
161
 
128
162
  // 1. which tools
129
163
  let toolIds;
@@ -153,28 +187,28 @@ async function install(flags) {
153
187
  let model = typeof flags.model === "string" ? flags.model : defs.model;
154
188
  if (openaiTools.length && typeof flags.model !== "string" && ui.isInteractive()) {
155
189
  ui.log();
156
- model = await ui.selectOne("Default model (" + openaiTools.join(", ") + ")", modelItems, defs.model);
190
+ model = await pickModel(
191
+ "Default model for " + openaiTools.map((id) => byId(id).title).join(", "),
192
+ shortlistItems([defs.model, defs.sonnet, defs.opus]),
193
+ defs.model
194
+ );
157
195
  }
158
196
  if (model && !ids.includes(model)) {
159
197
  ui.warn('model "' + model + '" is not in the live catalog - using it anyway');
160
198
  }
161
199
 
162
- // 3. Claude Code model slots
163
- let opus = typeof flags.opus === "string" ? flags.opus : defs.opus;
164
- let sonnet = typeof flags.sonnet === "string" ? flags.sonnet : defs.sonnet;
165
- let haiku = typeof flags.haiku === "string" ? flags.haiku : defs.haiku;
166
- const slotsFlagged = ["opus", "sonnet", "haiku"].some((f) => typeof flags[f] === "string");
167
- if (toolIds.includes("claude") && !slotsFlagged && ui.isInteractive()) {
200
+ // 3. Claude Code model slots - never asked interactively: Claude Code has
201
+ // its own opus/sonnet/haiku slots and the recommended Claude models are
202
+ // the right answer for nearly everyone. Power users override via flags.
203
+ const opus = typeof flags.opus === "string" ? flags.opus : defs.opus;
204
+ const sonnet = typeof flags.sonnet === "string" ? flags.sonnet : defs.sonnet;
205
+ const haiku = typeof flags.haiku === "string" ? flags.haiku : defs.haiku;
206
+ if (toolIds.includes("claude") && ui.isInteractive()) {
168
207
  ui.log();
169
- const keep = await ui.confirm(
170
- "Claude Code slots: opus=" + opus + ", sonnet=" + sonnet + ", haiku=" + haiku + ". Use these?",
171
- true
208
+ ui.info(
209
+ "Claude Code runs on Claude models automatically: " + opus + " / " + sonnet + " / " + haiku +
210
+ fmt.gray(" (override: --opus --sonnet --haiku)")
172
211
  );
173
- if (!keep) {
174
- opus = await ui.selectOne("Opus-slot model", modelItems, opus);
175
- sonnet = await ui.selectOne("Sonnet-slot model", modelItems, sonnet);
176
- haiku = await ui.selectOne("Haiku-slot model", modelItems, haiku);
177
- }
178
212
  }
179
213
 
180
214
  // 4. statusline (Claude Code only)
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  // OpenCode — ~/.config/opencode/opencode.json
3
3
  // Registers z0ne.ai as an @ai-sdk/openai-compatible provider and sets the
4
- // active model to z0ne/<id>. Image input modality follows the live catalog.
4
+ // active model to z0ne/<id>. The full live catalog is pinned with per-model
5
+ // input modalities so vision keeps working on any model the user picks.
5
6
 
6
7
  const path = require("path");
7
8
  const fs = require("fs");
@@ -36,15 +37,26 @@ module.exports = {
36
37
  };
37
38
  existing.models = existing.models || {};
38
39
 
39
- const row = ctx.rowsById[ctx.model];
40
- const inputs =
41
- row && Array.isArray(row.input_modalities) && row.input_modalities.includes("image")
42
- ? ["text", "image"]
43
- : ["text"];
44
- existing.models[ctx.model] = {
45
- name: ctx.model,
46
- modalities: { input: inputs, output: ["text"] },
47
- };
40
+ // Pin the whole live catalog, not just the selected model. OpenCode
41
+ // treats auto-discovered openai-compatible models as text-only and
42
+ // silently strips pasted images, so every vision model must carry an
43
+ // explicit image modality here.
44
+ for (const row of Object.values(ctx.rowsById || {})) {
45
+ const hasImage =
46
+ Array.isArray(row.input_modalities) && row.input_modalities.includes("image");
47
+ existing.models[row.id] = {
48
+ ...existing.models[row.id],
49
+ name: row.id,
50
+ ...(hasImage ? { attachment: true } : {}),
51
+ modalities: { input: hasImage ? ["text", "image"] : ["text"], output: ["text"] },
52
+ };
53
+ }
54
+ if (!existing.models[ctx.model]) {
55
+ existing.models[ctx.model] = {
56
+ name: ctx.model,
57
+ modalities: { input: ["text"], output: ["text"] },
58
+ };
59
+ }
48
60
 
49
61
  config.provider.z0ne = existing;
50
62
  config.model = "z0ne/" + ctx.model;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@z0ne/installer",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Point coding agents at z0ne.ai — Claude Code, Codex, OpenCode, Cline, Hermes, OpenClaw. One key, one command. Optionally installs a Claude Code statusline.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://z0ne.ai",