diff --git a/src/cli/tui/actions/headless-set.ts b/src/cli/tui/actions/headless-set.ts index 6ee6cebab..2ffbd3bf3 100644 --- a/src/cli/tui/actions/headless-set.ts +++ b/src/cli/tui/actions/headless-set.ts @@ -4,7 +4,9 @@ * TUI uses. * * Design: - * - Find the FieldDef in CATALOG by `key` (env-var style, e.g. WIGOLO_SEARCH). + * - Find the FieldDef in CATALOG by `key` (env-var style, e.g. WIGOLO_SEARCH, + * the names text `--plain` prints) or by `settingsPath` (config.json name, + * e.g. searchBackend — the docs' `--set` examples and `--plain --json`). * - Refuse `kind === 'masked'` / `secret: true` fields with a clear error — * secrets leak via shell history; the dedicated secret-store path stays in * charge of those. @@ -78,6 +80,11 @@ function findField( if (field.key === key) return field; } } + for (const category of catalog) { + for (const field of category.fields) { + if (field.settingsPath === key) return field; + } + } return null; } diff --git a/tests/unit/cli/tui/actions/headless-set.test.ts b/tests/unit/cli/tui/actions/headless-set.test.ts index fd27eeeff..4b85458a2 100644 --- a/tests/unit/cli/tui/actions/headless-set.test.ts +++ b/tests/unit/cli/tui/actions/headless-set.test.ts @@ -191,6 +191,63 @@ describe('applyHeadlessSet — validation', () => { }); }); +describe('applyHeadlessSet — settingsPath keys (issue #256)', () => { + it("accepts the settingsPath name 'searchBackend' and saves hybrid", async () => { + let capturedDirty: Record = {}; + const fakeSave = vi.fn(async (opts: { + store: { getPending: () => Record; commit: () => void }; + }): Promise => { + capturedDirty = opts.store.getPending(); + opts.store.commit(); + return { saved: ['searchBackend'], propagated: ['claude-code'], failed: [] }; + }); + + const result = await applyHeadlessSet({ + key: 'searchBackend', + value: 'hybrid', + configPath: '/tmp/config.json', + catalog: CATALOG, + agents: [stubAgent], + secretStore: stubSecretStore, + storeFactory: createSettingsStore, + readSettings: () => ({}), + save: fakeSave, + }); + + expect(result.status).toBe('ok'); + expect(result.saved).toEqual(['searchBackend']); + expect(capturedDirty).toEqual({ searchBackend: 'hybrid' }); + expect(fakeSave).toHaveBeenCalledOnce(); + }); + + it("still accepts the env-style key 'WIGOLO_SEARCH'", async () => { + let capturedDirty: Record = {}; + const fakeSave = vi.fn(async (opts: { + store: { getPending: () => Record; commit: () => void }; + }): Promise => { + capturedDirty = opts.store.getPending(); + opts.store.commit(); + return { saved: ['searchBackend'], propagated: ['claude-code'], failed: [] }; + }); + + const result = await applyHeadlessSet({ + key: 'WIGOLO_SEARCH', + value: 'hybrid', + configPath: '/tmp/config.json', + catalog: CATALOG, + agents: [stubAgent], + secretStore: stubSecretStore, + storeFactory: createSettingsStore, + readSettings: () => ({}), + save: fakeSave, + }); + + expect(result.status).toBe('ok'); + expect(result.saved).toEqual(['searchBackend']); + expect(capturedDirty).toEqual({ searchBackend: 'hybrid' }); + }); +}); + describe('applyHeadlessSet — happy path', () => { it('coerces a number-kind field and calls save with the staged value', async () => { let capturedDirty: Record = {};