Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/cli/tui/actions/headless-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down
57 changes: 57 additions & 0 deletions tests/unit/cli/tui/actions/headless-set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> = {};
const fakeSave = vi.fn(async (opts: {
store: { getPending: () => Record<string, unknown>; commit: () => void };
}): Promise<SaveResult> => {
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<string, unknown> = {};
const fakeSave = vi.fn(async (opts: {
store: { getPending: () => Record<string, unknown>; commit: () => void };
}): Promise<SaveResult> => {
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<string, unknown> = {};
Expand Down