@gonrocca/nodd 0.3.0 → 0.3.1
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.
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
3
6
|
import { readFileSync } from "node:fs";
|
|
4
7
|
import { MECHANISM_PLACEHOLDER } from "../src/models/slots.ts";
|
|
5
8
|
import register, { runModelsCommand, type ConfigIo } from "./nodd-models.ts";
|
|
@@ -164,6 +167,73 @@ test("quitting the picker produces zero writes; saving writes once", async () =>
|
|
|
164
167
|
assert.equal(saveIo.writes[0].unrelated, true, "the picker's save preserves unrelated keys");
|
|
165
168
|
});
|
|
166
169
|
|
|
170
|
+
test("the handler opens the picker on the saved profiles, not on an empty menu", async () => {
|
|
171
|
+
// The host built the picker from `models` and `groups` only, so the profiles
|
|
172
|
+
// on disk never reached it: a user with six saved profiles opened
|
|
173
|
+
// /nodd-models and the menu offered to create the first one.
|
|
174
|
+
const commands = new Map<string, any>();
|
|
175
|
+
register({ registerCommand: (name: string, options: unknown) => commands.set(name, options) } as never);
|
|
176
|
+
|
|
177
|
+
const config = {
|
|
178
|
+
models: { implement: "cliproxy/personal/claude-opus-5" },
|
|
179
|
+
thinking: { implement: "high" },
|
|
180
|
+
profiles: {
|
|
181
|
+
rapido: { models: { implement: "cliproxy/ds/deepseek-flash" }, thinking: { implement: "low" } },
|
|
182
|
+
lento: { models: { implement: "cliproxy/personal/claude-opus-5" }, thinking: { implement: "high" } },
|
|
183
|
+
},
|
|
184
|
+
activeProfile: "rapido",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const home = mkdtempSync(join(tmpdir(), "nodd-models-"));
|
|
188
|
+
mkdirSync(join(home, ".pi"), { recursive: true });
|
|
189
|
+
writeFileSync(join(home, ".pi", "nodd.json"), JSON.stringify(config));
|
|
190
|
+
const previousHome = process.env.HOME;
|
|
191
|
+
process.env.HOME = home;
|
|
192
|
+
|
|
193
|
+
let rendered: string[] = [];
|
|
194
|
+
try {
|
|
195
|
+
await commands.get("nodd-models").handler("", {
|
|
196
|
+
ui: {
|
|
197
|
+
notify() {},
|
|
198
|
+
// Stand in for pi's custom-UI host: build the component, render it, and
|
|
199
|
+
// quit without saving.
|
|
200
|
+
async custom(build: any) {
|
|
201
|
+
let result: unknown;
|
|
202
|
+
const component = build({ requestRender() {} }, {}, {}, (r: unknown) => { result = r; });
|
|
203
|
+
rendered = component.render(80);
|
|
204
|
+
component.handleInput?.("q");
|
|
205
|
+
return result ?? { type: "quit" };
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
modelRegistry: { getAll: () => [{ provider: "cliproxy", id: "personal/claude-opus-5" }] },
|
|
209
|
+
} as never);
|
|
210
|
+
} finally {
|
|
211
|
+
if (previousHome === undefined) delete process.env.HOME;
|
|
212
|
+
else process.env.HOME = previousHome;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const screen = rendered.join("\n");
|
|
216
|
+
assert.match(screen, /rapido/, "a saved profile is on the opening screen");
|
|
217
|
+
assert.match(screen, /lento/, "and so is the other one");
|
|
218
|
+
assert.doesNotMatch(screen, /modelos sin perfil/, "with profiles saved, the menu is not the no-profile one");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("the picker input carries the profiles and the active one", () => {
|
|
222
|
+
const input = register.pickerInput(
|
|
223
|
+
{
|
|
224
|
+
models: { implement: "cliproxy/personal/claude-opus-5" },
|
|
225
|
+
thinking: { implement: "high" },
|
|
226
|
+
profiles: { rapido: { models: { implement: "cliproxy/ds/deepseek-flash" } } },
|
|
227
|
+
activeProfile: "rapido",
|
|
228
|
+
},
|
|
229
|
+
new Map([["cliproxy", ["personal/claude-opus-5"]]]),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
assert.deepEqual(Object.keys(input.profiles), ["rapido"], "saved profiles reach the picker");
|
|
233
|
+
assert.equal(input.activeProfile, "rapido", "and so does the active one");
|
|
234
|
+
assert.deepEqual(input.thinking, { implement: "high" }, "and the levels, so editing does not wipe them");
|
|
235
|
+
});
|
|
236
|
+
|
|
167
237
|
test("the command is registered with pi under its own name", () => {
|
|
168
238
|
const commands = new Map<string, unknown>();
|
|
169
239
|
register({ registerCommand: (name: string, options: unknown) => commands.set(name, options) } as never);
|
|
@@ -334,8 +334,12 @@ function register(pi?: PiApi): void {
|
|
|
334
334
|
// deterministic text path, which is the whole command in a headless run.
|
|
335
335
|
if (args.trim() === "" && typeof ctx?.ui?.custom === "function") {
|
|
336
336
|
const groups = groupsFrom(ctx.modelRegistry);
|
|
337
|
+
// Everything the picker needs, including the saved profiles and the
|
|
338
|
+
// active one. Passing only the models opened the picker as though
|
|
339
|
+
// nothing had ever been saved.
|
|
340
|
+
const input = pickerInput(io.readConfig(), groups);
|
|
337
341
|
const result = await ctx.ui.custom<EnterResult>((tui, _theme, _keys, done) =>
|
|
338
|
-
createComponent(
|
|
342
|
+
createComponent(input, done, () => tui.requestRender()),
|
|
339
343
|
);
|
|
340
344
|
runPicker(result, io);
|
|
341
345
|
notify?.(result.type === "save" ? "nodd · modelos guardados" : "nodd · sin cambios", "info");
|
|
@@ -352,5 +356,6 @@ function register(pi?: PiApi): void {
|
|
|
352
356
|
|
|
353
357
|
register.runPicker = runPicker;
|
|
354
358
|
register.createComponent = createComponent;
|
|
359
|
+
register.pickerInput = pickerInput;
|
|
355
360
|
|
|
356
361
|
export default register;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonrocca/nodd",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Non-negotiable Organic Driven Development — the ODD protocol as runtime mechanism for pi: blocking gates, observed evidence, and promotion to /forge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|