sideby 0.16.0 → 0.17.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.
- package/README.md +5 -3
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +16 -0
- package/dist/src/cli/commands.js +460 -5
- package/dist/src/cli/json-schemas.js +115 -0
- package/dist/src/cli/main.js +93 -3
- package/dist/src/core/ask.d.ts +150 -0
- package/dist/src/core/ask.js +365 -0
- package/dist/src/core/attachments.d.ts +50 -0
- package/dist/src/core/attachments.js +442 -0
- package/dist/src/core/autostart.d.ts +88 -0
- package/dist/src/core/autostart.js +256 -0
- package/dist/src/core/config.d.ts +97 -0
- package/dist/src/core/config.js +278 -10
- package/dist/src/core/launch.d.ts +15 -4
- package/dist/src/core/launch.js +27 -23
- package/dist/src/core/local-build.d.ts +17 -0
- package/dist/src/core/local-build.js +39 -3
- package/dist/src/core/probe.d.ts +78 -0
- package/dist/src/core/probe.js +127 -0
- package/dist/src/core/same-login.d.ts +9 -4
- package/dist/src/core/same-login.js +9 -4
- package/dist/src/core/secret-file.d.ts +26 -0
- package/dist/src/core/secret-file.js +126 -7
- package/dist/src/core/terminal.d.ts +93 -0
- package/dist/src/core/terminal.js +371 -0
- package/dist/src/core/update.d.ts +17 -0
- package/dist/src/core/update.js +63 -1
- package/dist/src/families/claude/index.js +3 -0
- package/dist/src/families/claude/quota-cache.d.ts +24 -7
- package/dist/src/families/claude/quota-cache.js +72 -21
- package/dist/src/families/codex/index.js +7 -0
- package/dist/src/families/codex/login-key.d.ts +8 -0
- package/dist/src/families/codex/login-key.js +33 -0
- package/dist/src/families/grok/index.js +3 -0
- package/dist/src/families/login-keys.d.ts +6 -0
- package/dist/src/families/login-keys.js +6 -0
- package/dist/src/families/pi/index.js +4 -0
- package/dist/src/panel/background.js +3 -2
- package/dist/src/panel/handler.d.ts +29 -2
- package/dist/src/panel/handler.js +396 -9
- package/dist/src/panel/i18n.d.ts +175 -2
- package/dist/src/panel/i18n.js +381 -35
- package/dist/src/panel/page-logic.d.ts +214 -0
- package/dist/src/panel/page-logic.js +394 -0
- package/dist/src/panel/page-script.js +984 -134
- package/dist/src/panel/page-styles.js +69 -19
- package/dist/src/panel/page.d.ts +5 -0
- package/dist/src/panel/page.js +2 -0
- package/dist/src/panel/server.d.ts +16 -2
- package/dist/src/panel/server.js +41 -13
- package/dist/src/plugins/check.d.ts +47 -0
- package/dist/src/plugins/check.js +544 -0
- package/dist/src/plugins/scaffold.d.ts +21 -0
- package/dist/src/plugins/scaffold.js +123 -0
- package/dist/src/quota/statusline-tap.js +1 -1
- package/dist/src/runtime.d.ts +10 -0
- package/dist/src/runtime.js +47 -16
- package/dist/src/types.d.ts +19 -0
- package/package.json +2 -2
- package/schemas/ask.json +180 -0
- package/schemas/autostart.json +165 -0
- package/schemas/config-get.json +132 -0
- package/schemas/config-set.json +107 -0
- package/schemas/config.json +73 -6
- package/schemas/key.json +93 -0
- package/schemas/local.json +4 -0
- package/schemas/plugins-check.json +113 -0
- package/schemas/plugins-new.json +87 -0
- package/schemas/terminal.json +152 -0
- package/schemas/try.json +109 -0
- package/schemas/update.json +4 -0
- package/skills/sideby/SKILL.md +12 -6
- package/skills/sideby/references/accounts.md +1 -1
- package/skills/sideby/references/ai-tasks.md +116 -0
- package/skills/sideby/references/panel.md +14 -0
- package/skills/sideby/references/update.md +1 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Start the Panel at login (spec §3.29, ADR-0029): a LaunchAgent on macOS that runs this Node and this sideby's entry
|
|
2
|
+
// with `ui --background --no-open`. sideby writes, replaces or removes only a file that carries its marker; any other
|
|
3
|
+
// file at that path is reported and left alone. Every program runs through `AutostartContext.exec`, so tests never
|
|
4
|
+
// load a real agent.
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
7
|
+
import { tmpdir, userInfo } from 'node:os';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { UserError } from './errors.js';
|
|
10
|
+
import { fileHash, realpathOrNull, sha256, statOrNull, writeIfUnchanged } from './fs-safe.js';
|
|
11
|
+
import { askLauncher, launcherProblem } from './terminal.js';
|
|
12
|
+
export const AUTOSTART_LABEL = 'dev.sideby.panel';
|
|
13
|
+
/** Present in every agent sideby writes; a file without it is someone else's. */
|
|
14
|
+
export const AUTOSTART_MARKER = 'Written by `sideby autostart`';
|
|
15
|
+
const defaultExec = (file, args) => new Promise((resolve) => {
|
|
16
|
+
execFile(file, [...args], { timeout: 15_000 }, (err, stdout, stderr) => {
|
|
17
|
+
const code = err ? (typeof err.code === 'number' ? err.code : 1) : 0;
|
|
18
|
+
resolve({ code, output: `${stdout}${stderr}` });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
export const autostartPath = (home) => join(home, 'Library', 'LaunchAgents', `${AUTOSTART_LABEL}.plist`);
|
|
22
|
+
const xml = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
23
|
+
const unxml = (s) => s.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');
|
|
24
|
+
export const programArgumentsFor = (l) => [l.node, l.entry, 'ui', '--background', '--no-open'];
|
|
25
|
+
/** The agent's file: starts the Panel once at login, never again by itself, and lets it outlive the job. */
|
|
26
|
+
export function plistContent(args) {
|
|
27
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
28
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
29
|
+
<!-- ${AUTOSTART_MARKER}: starts the sideby panel in the background at login. Turn it off with \`sideby autostart off\`. -->
|
|
30
|
+
<plist version="1.0">
|
|
31
|
+
<dict>
|
|
32
|
+
<key>Label</key>
|
|
33
|
+
<string>${AUTOSTART_LABEL}</string>
|
|
34
|
+
<key>ProgramArguments</key>
|
|
35
|
+
<array>
|
|
36
|
+
${args.map((a) => ` <string>${xml(a)}</string>`).join('\n')}
|
|
37
|
+
</array>
|
|
38
|
+
<key>RunAtLoad</key>
|
|
39
|
+
<true/>
|
|
40
|
+
<key>KeepAlive</key>
|
|
41
|
+
<false/>
|
|
42
|
+
<key>AbandonProcessGroup</key>
|
|
43
|
+
<true/>
|
|
44
|
+
</dict>
|
|
45
|
+
</plist>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
/** The ProgramArguments of a file sideby wrote; null when it holds none. */
|
|
49
|
+
export function parseProgramArguments(content) {
|
|
50
|
+
const m = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(content);
|
|
51
|
+
if (!m)
|
|
52
|
+
return null;
|
|
53
|
+
return [...m[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((s) => unxml(s[1]));
|
|
54
|
+
}
|
|
55
|
+
const platformOf = (ctx) => ctx.platform ?? process.platform;
|
|
56
|
+
const UNSUPPORTED = 'sideby can start at login on macOS only for now; Linux and Windows come later';
|
|
57
|
+
async function readAgent(path) {
|
|
58
|
+
try {
|
|
59
|
+
return await readFile(path, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err.code === 'ENOENT')
|
|
63
|
+
return null;
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Whether a sideby agent (with the marker) is installed; reads only that one file. */
|
|
68
|
+
export async function autostartInstalled(home) {
|
|
69
|
+
const content = await readAgent(autostartPath(home)).catch(() => null);
|
|
70
|
+
return content?.includes(AUTOSTART_MARKER) ?? false;
|
|
71
|
+
}
|
|
72
|
+
export async function autostartStatus(ctx) {
|
|
73
|
+
const path = autostartPath(ctx.home);
|
|
74
|
+
const supported = platformOf(ctx) === 'darwin';
|
|
75
|
+
const base = { supported, ...(supported ? {} : { reason: UNSUPPORTED }), path, current: false };
|
|
76
|
+
if (!supported)
|
|
77
|
+
return { ...base, state: 'off' };
|
|
78
|
+
const content = await readAgent(path);
|
|
79
|
+
if (content === null)
|
|
80
|
+
return { ...base, state: 'off' };
|
|
81
|
+
const args = content.includes(AUTOSTART_MARKER) ? parseProgramArguments(content) : null;
|
|
82
|
+
if (!args)
|
|
83
|
+
return { ...base, state: 'foreign' };
|
|
84
|
+
const missing = [];
|
|
85
|
+
for (const p of args.slice(0, 2))
|
|
86
|
+
if (!(await statOrNull(p)))
|
|
87
|
+
missing.push(p);
|
|
88
|
+
const launcher = ctx.launcher ?? askLauncher();
|
|
89
|
+
const want = programArgumentsFor(launcher);
|
|
90
|
+
const current = args.length === want.length && args.every((a, i) => a === want[i]);
|
|
91
|
+
return {
|
|
92
|
+
...base,
|
|
93
|
+
state: missing.length ? 'stale' : 'on',
|
|
94
|
+
programArguments: args,
|
|
95
|
+
current,
|
|
96
|
+
...(missing.length ? { missing } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Under the system's temporary folders, which are emptied without asking. */
|
|
100
|
+
async function inTempDir(path) {
|
|
101
|
+
const roots = ['/tmp/', '/private/tmp/', '/var/folders/', '/private/var/folders/'];
|
|
102
|
+
const t = await realpathOrNull(tmpdir());
|
|
103
|
+
if (t)
|
|
104
|
+
roots.push(`${t.replace(/\/+$/, '')}/`);
|
|
105
|
+
return roots.some((r) => path.startsWith(r));
|
|
106
|
+
}
|
|
107
|
+
/** Why the agent cannot start this launcher, with what to do instead; null when it can. */
|
|
108
|
+
export async function autostartLauncherProblem(l) {
|
|
109
|
+
const p = launcherProblem(l);
|
|
110
|
+
if (p?.reason === 'npx-cache')
|
|
111
|
+
return {
|
|
112
|
+
code: 'npx-cache',
|
|
113
|
+
message: 'sideby runs from the npx cache, which npm may clear, so it cannot start at login from there; install it with `npm i -g sideby`, then run `sideby autostart on` again',
|
|
114
|
+
};
|
|
115
|
+
if (p)
|
|
116
|
+
return {
|
|
117
|
+
code: 'unsafe-path',
|
|
118
|
+
message: 'the path of sideby or Node has characters a login item cannot run safely; install sideby with `npm i -g sideby` in an ordinary folder, then run `sideby autostart on` again',
|
|
119
|
+
};
|
|
120
|
+
if ((await inTempDir(l.entry)) || (await inTempDir(l.node)))
|
|
121
|
+
return {
|
|
122
|
+
code: 'temp-dir',
|
|
123
|
+
message: 'sideby or Node runs from a temporary folder that the system empties, so it cannot start at login from there; install it with `npm i -g sideby`, then run `sideby autostart on` again',
|
|
124
|
+
};
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const uidOf = (ctx) => ctx.uid ?? process.getuid?.() ?? 0;
|
|
128
|
+
const bootstrap = (ctx, path) => [
|
|
129
|
+
'launchctl',
|
|
130
|
+
'bootstrap',
|
|
131
|
+
`gui/${uidOf(ctx)}`,
|
|
132
|
+
path,
|
|
133
|
+
];
|
|
134
|
+
const bootout = (ctx) => ['launchctl', 'bootout', `gui/${uidOf(ctx)}/${AUTOSTART_LABEL}`];
|
|
135
|
+
/** What `on` or `off` would change; writes nothing. */
|
|
136
|
+
export async function planAutostart(ctx, action) {
|
|
137
|
+
const status = await autostartStatus(ctx);
|
|
138
|
+
const path = status.path;
|
|
139
|
+
const hash = status.supported ? await fileHash(path) : null;
|
|
140
|
+
const plan = (extra) => ({
|
|
141
|
+
action,
|
|
142
|
+
status,
|
|
143
|
+
changed: false,
|
|
144
|
+
path,
|
|
145
|
+
commands: [],
|
|
146
|
+
hash,
|
|
147
|
+
...extra,
|
|
148
|
+
});
|
|
149
|
+
if (!status.supported)
|
|
150
|
+
return plan({ blocked: { code: 'unsupported', message: UNSUPPORTED } });
|
|
151
|
+
if (status.state === 'foreign')
|
|
152
|
+
return plan({
|
|
153
|
+
blocked: {
|
|
154
|
+
code: 'foreign',
|
|
155
|
+
message: `${path} was not written by sideby, so sideby leaves it alone; look at it, and remove or rename it yourself before \`sideby autostart ${action}\` if it is not needed`,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
if (action === 'off')
|
|
159
|
+
return status.state === 'off' ? plan({}) : plan({ changed: true, commands: [bootout(ctx)] });
|
|
160
|
+
const launcher = ctx.launcher ?? askLauncher();
|
|
161
|
+
const blocked = await autostartLauncherProblem(launcher);
|
|
162
|
+
if (blocked)
|
|
163
|
+
return plan({ blocked });
|
|
164
|
+
const programArguments = programArgumentsFor(launcher);
|
|
165
|
+
const content = plistContent(programArguments);
|
|
166
|
+
const same = hash !== null && hash === sha256(content);
|
|
167
|
+
return plan({
|
|
168
|
+
changed: !same,
|
|
169
|
+
content,
|
|
170
|
+
programArguments,
|
|
171
|
+
commands: same ? [] : [...(status.state === 'off' ? [] : [bootout(ctx)]), bootstrap(ctx, path)],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Whether launchctl may run: only for the login user's own LaunchAgents, never for a demo or test HOME. */
|
|
175
|
+
function launchctlNote(ctx) {
|
|
176
|
+
let login = ctx.loginHome;
|
|
177
|
+
if (login === undefined)
|
|
178
|
+
try {
|
|
179
|
+
login = userInfo().homedir;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
login = '';
|
|
183
|
+
}
|
|
184
|
+
return login === ctx.home
|
|
185
|
+
? null
|
|
186
|
+
: `HOME (${ctx.home}) is not your login home, so launchd was not told; it reads this file only from your login home`;
|
|
187
|
+
}
|
|
188
|
+
/** Carries out a plan the user confirmed. Refuses, changing nothing, when the file changed since it was planned. */
|
|
189
|
+
export async function applyAutostart(ctx, plan) {
|
|
190
|
+
if (plan.blocked)
|
|
191
|
+
throw new UserError(plan.blocked.message, { code: plan.blocked.code });
|
|
192
|
+
const path = plan.path;
|
|
193
|
+
if (!plan.changed)
|
|
194
|
+
return {
|
|
195
|
+
changed: false,
|
|
196
|
+
state: plan.status.state,
|
|
197
|
+
path,
|
|
198
|
+
...(plan.programArguments ? { programArguments: plan.programArguments } : {}),
|
|
199
|
+
loaded: false,
|
|
200
|
+
};
|
|
201
|
+
if ((await fileHash(path)) !== plan.hash)
|
|
202
|
+
throw new UserError(`${path} changed since sideby looked at it; nothing was changed. Run the command again`, {
|
|
203
|
+
code: 'changed',
|
|
204
|
+
});
|
|
205
|
+
const exec = ctx.exec ?? defaultExec;
|
|
206
|
+
const note = launchctlNote(ctx);
|
|
207
|
+
const run = async (cmd) => (note ? null : exec(cmd[0], cmd.slice(1)));
|
|
208
|
+
if (plan.action === 'off') {
|
|
209
|
+
// Not loaded (already out, or never in) is fine: the file is what starts it at the next login.
|
|
210
|
+
await run(bootout(ctx));
|
|
211
|
+
await rm(path, { force: true });
|
|
212
|
+
return { changed: true, state: 'off', path, loaded: note === null, ...(note ? { note } : {}) };
|
|
213
|
+
}
|
|
214
|
+
if (plan.status.state !== 'off')
|
|
215
|
+
await run(bootout(ctx));
|
|
216
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o755 });
|
|
217
|
+
// launchd skips an agent that others can write; 644 is what it expects.
|
|
218
|
+
if (!(await writeIfUnchanged(path, plan.hash, plan.content, 0o644)))
|
|
219
|
+
throw new UserError(`${path} changed while sideby was writing it; nothing was changed. Run the command again`, {
|
|
220
|
+
code: 'changed',
|
|
221
|
+
});
|
|
222
|
+
const r = await run(bootstrap(ctx, path));
|
|
223
|
+
const result = {
|
|
224
|
+
changed: true,
|
|
225
|
+
state: 'on',
|
|
226
|
+
path,
|
|
227
|
+
programArguments: plan.programArguments,
|
|
228
|
+
loaded: r !== null && r.code === 0,
|
|
229
|
+
};
|
|
230
|
+
if (note)
|
|
231
|
+
result.note = note;
|
|
232
|
+
else if (r && r.code !== 0)
|
|
233
|
+
result.note = `launchctl bootstrap failed (exit ${r.code}); the panel starts at your next login, or now with \`sideby ui --background\``;
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* After `sideby update` or `pnpm local`: rewrites an existing sideby agent so it starts `launcher`. Never creates one,
|
|
238
|
+
* never touches a foreign file, and never throws; `rewritten` says whether the file changed.
|
|
239
|
+
*/
|
|
240
|
+
export async function refreshAutostart(ctx) {
|
|
241
|
+
let state = 'off';
|
|
242
|
+
try {
|
|
243
|
+
const status = await autostartStatus(ctx);
|
|
244
|
+
state = status.state;
|
|
245
|
+
if (status.state !== 'on' && status.state !== 'stale')
|
|
246
|
+
return { rewritten: false, state: status.state };
|
|
247
|
+
const plan = await planAutostart(ctx, 'on');
|
|
248
|
+
if (plan.blocked)
|
|
249
|
+
return { rewritten: false, state: status.state, note: plan.blocked.message };
|
|
250
|
+
const r = await applyAutostart(ctx, plan);
|
|
251
|
+
return { rewritten: r.changed, state: r.state, ...(r.note ? { note: r.note } : {}) };
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
return { rewritten: false, state, note: err.message };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -31,6 +31,17 @@ export interface HandoffSettings {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const ConfigSchema: Type.TObject<{
|
|
33
33
|
$schema: Type.TOptional<Type.TString>;
|
|
34
|
+
panel: Type.TOptional<Type.TObject<{
|
|
35
|
+
theme: Type.TOptional<Type.TString>;
|
|
36
|
+
language: Type.TOptional<Type.TString>;
|
|
37
|
+
}>>;
|
|
38
|
+
terminal: Type.TOptional<Type.TObject<{
|
|
39
|
+
preferred: Type.TOptional<Type.TString>;
|
|
40
|
+
custom: Type.TOptional<Type.TString>;
|
|
41
|
+
}>>;
|
|
42
|
+
ask: Type.TOptional<Type.TObject<{
|
|
43
|
+
account: Type.TOptional<Type.TString>;
|
|
44
|
+
}>>;
|
|
34
45
|
accounts: Type.TOptional<Type.TRecord<"^.*$", Type.TObject<{
|
|
35
46
|
args: Type.TOptional<Type.TArray<Type.TString>>;
|
|
36
47
|
}>>>;
|
|
@@ -172,4 +183,90 @@ export declare function removeConfigAlias(file: string, alias: string): Promise<
|
|
|
172
183
|
status: 'removed' | 'absent';
|
|
173
184
|
aliases: Record<string, AliasValue>;
|
|
174
185
|
}>;
|
|
186
|
+
export declare function editConfigTerminal(file: string, patch: {
|
|
187
|
+
preferred?: string;
|
|
188
|
+
custom?: string;
|
|
189
|
+
}): Promise<{
|
|
190
|
+
changed: boolean;
|
|
191
|
+
before: {
|
|
192
|
+
preferred?: string | undefined;
|
|
193
|
+
custom?: string | undefined;
|
|
194
|
+
} | undefined;
|
|
195
|
+
after: {
|
|
196
|
+
preferred?: string | undefined;
|
|
197
|
+
custom?: string | undefined;
|
|
198
|
+
} | undefined;
|
|
199
|
+
}>;
|
|
200
|
+
export declare function editConfigAsk(file: string, account?: string): Promise<{
|
|
201
|
+
changed: boolean;
|
|
202
|
+
before: {
|
|
203
|
+
account?: string | undefined;
|
|
204
|
+
} | undefined;
|
|
205
|
+
after: {
|
|
206
|
+
account?: string | undefined;
|
|
207
|
+
} | undefined;
|
|
208
|
+
}>;
|
|
209
|
+
export type PanelConfig = NonNullable<Config['panel']>;
|
|
210
|
+
/**
|
|
211
|
+
* The Panel's theme and language (spec §3.29): a value sets the key, null removes it (back to the default), and an
|
|
212
|
+
* empty `panel` goes away with it (see `editConfigKey` for how the file is written).
|
|
213
|
+
*/
|
|
214
|
+
export declare function editConfigPanel(file: string, patch: {
|
|
215
|
+
theme?: PanelConfig['theme'] | null;
|
|
216
|
+
language?: PanelConfig['language'] | null;
|
|
217
|
+
}, opts?: {
|
|
218
|
+
dryRun?: boolean;
|
|
219
|
+
}): Promise<{
|
|
220
|
+
changed: boolean;
|
|
221
|
+
before: PanelConfig | undefined;
|
|
222
|
+
after: PanelConfig | undefined;
|
|
223
|
+
}>;
|
|
224
|
+
export interface SettableKey {
|
|
225
|
+
/** Dot path; `<…>` marks a part the user fills in (`accounts.<family>:<name>.args`). */
|
|
226
|
+
key: string;
|
|
227
|
+
/** One line on what it does. */
|
|
228
|
+
meaning: string;
|
|
229
|
+
/** The values it takes, in words. */
|
|
230
|
+
takes: string;
|
|
231
|
+
/** The current value of a key without `<…>`; absent when not set. */
|
|
232
|
+
value?: unknown;
|
|
233
|
+
/** For a key with `<…>`: the keys set now and their values. */
|
|
234
|
+
entries?: {
|
|
235
|
+
key: string;
|
|
236
|
+
value: unknown;
|
|
237
|
+
}[];
|
|
238
|
+
}
|
|
239
|
+
/** Every key `sideby config set` takes, with its meaning and current value, in schema order. */
|
|
240
|
+
export declare function settableKeys(config: Config): SettableKey[];
|
|
241
|
+
/** Splits and checks a dot path; refuses what `config` may not read or change. */
|
|
242
|
+
export declare function configKeyPath(key: string): string[];
|
|
243
|
+
/** The path of `key`, what it does and the values it takes; refuses what `config` may not read or change. */
|
|
244
|
+
export declare function describeConfigKey(key: string): {
|
|
245
|
+
path: string[];
|
|
246
|
+
meaning: string;
|
|
247
|
+
takes: string;
|
|
248
|
+
};
|
|
249
|
+
/** A value from the command line: JSON when it parses (90, true, ["a"]), else the text itself. */
|
|
250
|
+
export declare function parseConfigValue(text: string): unknown;
|
|
251
|
+
/** `sideby config get [key]`: the value at `key` (undefined when not set). */
|
|
252
|
+
export declare function configGet(file: string, key: string): Promise<{
|
|
253
|
+
key: string;
|
|
254
|
+
value: unknown;
|
|
255
|
+
}>;
|
|
256
|
+
export interface ConfigChange {
|
|
257
|
+
key: string;
|
|
258
|
+
before: unknown;
|
|
259
|
+
after: unknown;
|
|
260
|
+
changed: boolean;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* `sideby config set|unset` (spec §3.29): sets `key` to `value`, or removes it when `value` is undefined (and parents
|
|
264
|
+
* left empty with it). The whole result must match the schema; `accounts.<ref>.args` takes only what `argsProblem`
|
|
265
|
+
* accepts (the model and effort arguments, as `sideby model`), and without `argsProblem` it is refused. Written as
|
|
266
|
+
* every other config change (see `editConfigKey`); `dryRun` shows the change without writing.
|
|
267
|
+
*/
|
|
268
|
+
export declare function editConfigPath(file: string, key: string, value: unknown, opts?: {
|
|
269
|
+
dryRun?: boolean;
|
|
270
|
+
argsProblem?: (ref: string, args: string[]) => string | null;
|
|
271
|
+
}): Promise<ConfigChange>;
|
|
175
272
|
export {};
|