glimmervoid 0.35.0 → 0.36.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 +2 -0
- package/dist/bin/glimmervoid.js +17 -12
- package/dist/chunks/{adapters-B37wqNti.js → adapters-BVozg-Lg.js} +1 -1
- package/dist/chunks/{adapters-CADejSpq.js → adapters-BdnLyJjf.js} +10 -49
- package/dist/chunks/{agent-api-cli-CRj_FYbz.js → agent-api-cli-DIqRLvu3.js} +1 -1
- package/dist/chunks/{agent-api-cli-vGwstJ1j.js → agent-api-cli-d-Bgsshx.js} +3 -3
- package/dist/chunks/{agent-api-core-D1233u1U.js → agent-api-core-BUC9WND7.js} +2 -2
- package/dist/chunks/{agent-setup-cli-BTAFcckd.js → agent-setup-cli-CH7EHsAX.js} +1 -1
- package/dist/chunks/{config-rPbbL4nW.js → config-CuZ16F9W.js} +47 -771
- package/dist/chunks/config-store-LqQ7MuyV.js +4181 -0
- package/dist/chunks/config-store-tVmt_nfm.js +1 -0
- package/dist/chunks/{contracts-DjIgIuDk.js → contracts-BLZAOP57.js} +55 -6
- package/dist/chunks/{grok-KKgdSe-7.js → grok-Ch4-35_r.js} +3 -3
- package/dist/chunks/{hook-command-core-DclEoE9e.js → hook-command-core-qTNK-xMF.js} +4 -1
- package/dist/chunks/{hook-tools-UX2fmt81.js → hook-tools-BkxllUHE.js} +9 -16
- package/dist/chunks/{knowledge-graph-cli-Rj5nj87L.js → knowledge-graph-cli-DD7QvcFd.js} +1 -1
- package/dist/chunks/{main-BTI0J5QE.js → main-Rmo83Ow-.js} +3087 -834
- package/dist/chunks/{pair-cli-Dcyzv3In.js → pair-cli-DzFeAmq5.js} +2 -2
- package/dist/chunks/{remote-config-Dd1T-VJA.js → remote-config-CPdSF2Xi.js} +1 -1
- package/dist/chunks/{rtk-hook-core-BmEz2aq9.js → rtk-hook-core-VrUafGAp.js} +15 -5
- package/dist/chunks/rtk-resolver-B8BPgN9L.js +1 -0
- package/dist/chunks/{rtk-resolver-B9pzjLsM.js → rtk-resolver-C_micGrL.js} +1 -1
- package/dist/chunks/session-B8RhqQJe.js +986 -0
- package/dist/chunks/{session-routes-BYy3am9m.js → session-routes-DB5aDKsx.js} +1 -1
- package/dist/chunks/settings-cli-C2TY6hl-.js +484 -0
- package/dist/chunks/{ssh-sign-cli-DPHpT22B.js → ssh-sign-cli-C2nUwJC-.js} +2 -2
- package/dist/client/assets/index-BIaSyXwH.css +1 -0
- package/dist/client/assets/index-Ddu7M4Ad.js +259 -0
- package/dist/client/index.html +5 -3
- package/dist/server/index.js +1 -1
- package/dist/session/hook-tool-relay.js +3 -3
- package/package.json +3 -3
- package/dist/chunks/config-store-BeIxAZNn.js +0 -1
- package/dist/chunks/config-store-BtsYbNC3.js +0 -1713
- package/dist/chunks/rtk-resolver-SWU5VVM7.js +0 -1
- package/dist/chunks/session-D6xXN0UU.js +0 -232
- package/dist/client/assets/index-DE2GUmqs.css +0 -1
- package/dist/client/assets/index-Ds4ngkHp.js +0 -254
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as SESSION_COMMANDS } from "./session-B8RhqQJe.js";
|
|
2
2
|
//#region session/core/session-routes.ts
|
|
3
3
|
var SUPERVISION_LINE = "Glimmervoid supervises this session. Run glimmervoid --help for the commands it gives you and this session's limits.";
|
|
4
4
|
var GUARDRAILS_LINE = "Its guardrails are OS-enforced, so a read, write or connection they refuse will not succeed on retry.";
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { r as errorMessage, t as errorCode } from "./text-DoirglWV.js";
|
|
2
|
+
import "./session-B8RhqQJe.js";
|
|
3
|
+
import { t as renderTable } from "./ascii-figure-core-pDAwtlKO.js";
|
|
4
|
+
import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
|
|
5
|
+
import { n as isRecord } from "./coerce-NSst8oRP.js";
|
|
6
|
+
import { r as BrowserConfig } from "./config-CuZ16F9W.js";
|
|
7
|
+
import { $ as withEnvSecrets, A as settingsOf, C as deleteValueAtPath, D as scoreSettingsSearch, E as parsePosthogProjectSelection, M as FILE_ONLY_GROUPS, N as SETTINGS_REGISTRY, O as setValueAtPath, S as cloneValue, T as hasScaledUnit, X as ENV_SECRET_BINDINGS, Z as readEnvSecrets, j as valueAtPath, k as settingsGroupOf, l as saveConfigFileOnce, s as loadConfigFile, u as validateConfig, w as displayText, x as SETTINGS_INDEX } from "./config-store-LqQ7MuyV.js";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
11
|
+
//#region server/settings-cli.ts
|
|
12
|
+
var usage = `glimmervoid settings: read and change config.json from the command line
|
|
13
|
+
|
|
14
|
+
glimmervoid settings list every config.json setting with its value and where the value comes from
|
|
15
|
+
glimmervoid settings get <path> one setting; a secret shows as configured or not configured, never its value
|
|
16
|
+
glimmervoid settings set <path> <value> validate the whole file and write it; a running Glimmervoid reloads it
|
|
17
|
+
glimmervoid settings unset <path> remove the key from config.json so the default applies
|
|
18
|
+
glimmervoid settings search <text> find settings by title, keyword, key path or section
|
|
19
|
+
glimmervoid settings schema every setting: id, path, type, default, unit, effect and section (needs no config file)
|
|
20
|
+
|
|
21
|
+
<path> is a config.json key path such as posthog.trafficSpikeEnabled, or a setting id such as posthog-traffic-enabled.
|
|
22
|
+
<value> is true or false for a toggle, a number, text, a comma-separated or JSON list, or JSON for a structured value.
|
|
23
|
+
A number is in the unit config.json stores, as schema's Stored unit column names: set visions.dispatch.quietMs 45000 means 45000 ms.
|
|
24
|
+
Where the dashboard shows another unit, get, list and set add it in parentheses, such as 30000 (30 seconds).
|
|
25
|
+
A secret (a bot token or API key) is never set or unset here, since a value on the command line lands in shell history;
|
|
26
|
+
change it in the dashboard settings or through its environment variable.
|
|
27
|
+
--json machine-readable output`;
|
|
28
|
+
var VALUE_COLUMN_WIDTH = 40;
|
|
29
|
+
var EFFECT_NOTES = Object.freeze({
|
|
30
|
+
now: "A running Glimmervoid applies it without a restart.",
|
|
31
|
+
"new-sessions": "Sessions started after this change use it.",
|
|
32
|
+
restart: "Restart Glimmervoid to apply it."
|
|
33
|
+
});
|
|
34
|
+
var HAND_EDITED_SETTING_REASONS = /* @__PURE__ */ new Map([["port", "a listener port that clashes with remote.port stops Glimmervoid from starting"]]);
|
|
35
|
+
var SECTION_BY_SETTING_ID = new Map([...SETTINGS_REGISTRY.sections.flatMap((section) => section.settings.map((setting) => [setting.id, {
|
|
36
|
+
id: section.id,
|
|
37
|
+
title: section.title
|
|
38
|
+
}])), ...SETTINGS_REGISTRY.fileOnly.map((setting) => {
|
|
39
|
+
const groupId = setting.group ?? "";
|
|
40
|
+
return [setting.id, {
|
|
41
|
+
id: groupId,
|
|
42
|
+
title: settingsGroupOf(groupId)?.title ?? groupId
|
|
43
|
+
}];
|
|
44
|
+
})]);
|
|
45
|
+
var ALL_SETTINGS = [...settingsOf(SETTINGS_REGISTRY.sections), ...SETTINGS_REGISTRY.fileOnly];
|
|
46
|
+
var SECTION_ORDER = [...SETTINGS_REGISTRY.sections.map((section) => ({
|
|
47
|
+
id: section.id,
|
|
48
|
+
title: section.title
|
|
49
|
+
})), ...Object.entries(FILE_ONLY_GROUPS).map(([id, group]) => ({
|
|
50
|
+
id,
|
|
51
|
+
title: group.title
|
|
52
|
+
}))];
|
|
53
|
+
function fail(message) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
message
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function succeed(value) {
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
value
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function isConfigFileSetting(setting) {
|
|
66
|
+
return setting.scope === "machine" && setting.control !== "readonly";
|
|
67
|
+
}
|
|
68
|
+
function sectionOf(setting) {
|
|
69
|
+
return SECTION_BY_SETTING_ID.get(setting.id) ?? {
|
|
70
|
+
id: "",
|
|
71
|
+
title: ""
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function describeSetting(setting) {
|
|
75
|
+
const descriptor = {
|
|
76
|
+
id: setting.id,
|
|
77
|
+
path: setting.path,
|
|
78
|
+
title: setting.title,
|
|
79
|
+
description: setting.description,
|
|
80
|
+
type: setting.control,
|
|
81
|
+
scope: setting.scope,
|
|
82
|
+
access: setting.access,
|
|
83
|
+
effect: setting.takesEffect,
|
|
84
|
+
default: cloneValue(setting.defaultValue) ?? null,
|
|
85
|
+
unit: setting.unit?.storedLabel ?? setting.unit?.label ?? null,
|
|
86
|
+
displayUnit: setting.unit?.label ?? null,
|
|
87
|
+
storedPerDisplay: setting.unit?.storedPerDisplay ?? 1,
|
|
88
|
+
section: sectionOf(setting)
|
|
89
|
+
};
|
|
90
|
+
if (setting.options) descriptor.options = setting.options.map((option) => option.value);
|
|
91
|
+
if (setting.range) descriptor.range = setting.range;
|
|
92
|
+
if (setting.fileOnlyReason) descriptor.fileOnlyReason = setting.fileOnlyReason;
|
|
93
|
+
return descriptor;
|
|
94
|
+
}
|
|
95
|
+
function isStoredSecret(value) {
|
|
96
|
+
return typeof value === "string" && value.length > 0;
|
|
97
|
+
}
|
|
98
|
+
function readSetting(setting, view) {
|
|
99
|
+
const stored = valueAtPath(view.config, setting.path);
|
|
100
|
+
const source = view.secretSources[setting.path] ?? (stored !== void 0 ? "config.json" : "default");
|
|
101
|
+
const readout = {
|
|
102
|
+
...describeSetting(setting),
|
|
103
|
+
secret: setting.control === "password",
|
|
104
|
+
source
|
|
105
|
+
};
|
|
106
|
+
if (readout.secret) {
|
|
107
|
+
readout.configured = isStoredSecret(stored);
|
|
108
|
+
return readout;
|
|
109
|
+
}
|
|
110
|
+
readout.value = stored !== void 0 ? cloneValue(stored) : readout.default;
|
|
111
|
+
return readout;
|
|
112
|
+
}
|
|
113
|
+
function fitColumn(text) {
|
|
114
|
+
if (text.length <= VALUE_COLUMN_WIDTH) return text;
|
|
115
|
+
return `${text.slice(0, 37)}...`;
|
|
116
|
+
}
|
|
117
|
+
function storedValueText(setting, value) {
|
|
118
|
+
if (setting.valueKind) return displayText(setting, value);
|
|
119
|
+
const stored = JSON.stringify(value ?? null);
|
|
120
|
+
if (typeof value !== "number" || !hasScaledUnit(setting)) return stored;
|
|
121
|
+
return `${stored} (${displayText(setting, value)})`;
|
|
122
|
+
}
|
|
123
|
+
function valueText(readout, setting) {
|
|
124
|
+
if (readout.secret) return readout.configured ? "configured" : "not configured";
|
|
125
|
+
return storedValueText(setting, readout.value);
|
|
126
|
+
}
|
|
127
|
+
function defaultText(setting) {
|
|
128
|
+
return storedValueText(setting, setting.defaultValue);
|
|
129
|
+
}
|
|
130
|
+
function settingsBySection(settings) {
|
|
131
|
+
return SECTION_ORDER.map((section) => ({
|
|
132
|
+
section,
|
|
133
|
+
settings: settings.filter((setting) => sectionOf(setting).id === section.id)
|
|
134
|
+
})).filter((group) => group.settings.length > 0);
|
|
135
|
+
}
|
|
136
|
+
function renderSectionTables(settings, headers, rowOf) {
|
|
137
|
+
return settingsBySection(settings).map(({ section, settings: grouped }) => renderTable({
|
|
138
|
+
title: section.title,
|
|
139
|
+
headers,
|
|
140
|
+
rows: grouped.map(rowOf),
|
|
141
|
+
align: headers.map(() => "left")
|
|
142
|
+
})).join("\n\n");
|
|
143
|
+
}
|
|
144
|
+
function resolveSetting(argument) {
|
|
145
|
+
return SETTINGS_INDEX.byPath.get(argument) ?? SETTINGS_INDEX.byId.get(argument) ?? null;
|
|
146
|
+
}
|
|
147
|
+
function requireConfigFileSetting(argument, configPath) {
|
|
148
|
+
const setting = resolveSetting(argument);
|
|
149
|
+
if (!setting) return fail(`${argument} is not a setting in the registry, so this command cannot read or change it; edit config.json at ${configPath} by hand`);
|
|
150
|
+
if (setting.scope === "browser") return fail(`${setting.id} is a browser preference saved in each browser, not in config.json; change it in the dashboard under Settings > ${sectionOf(setting).title}`);
|
|
151
|
+
if (setting.control === "readonly") return fail(`${setting.id} is a read-only status row under Settings > ${sectionOf(setting).title}; it has no config.json key`);
|
|
152
|
+
return succeed(setting);
|
|
153
|
+
}
|
|
154
|
+
function requirePositional(positionals, index, name) {
|
|
155
|
+
const value = positionals[index];
|
|
156
|
+
if (value === void 0) throw new Error(`missing <${name}>\n\n${usage}`);
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
function parseListValue(path, raw) {
|
|
160
|
+
const listError = `${path} must be a comma-separated list or a JSON array of strings`;
|
|
161
|
+
if (!raw.trim().startsWith("[")) return succeed(raw.split(",").map((entry) => entry.trim()).filter((entry) => entry !== ""));
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(raw);
|
|
164
|
+
if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")) return succeed(parsed);
|
|
165
|
+
} catch {
|
|
166
|
+
return fail(listError);
|
|
167
|
+
}
|
|
168
|
+
return fail(listError);
|
|
169
|
+
}
|
|
170
|
+
function parseNumberValue(setting, raw) {
|
|
171
|
+
if (setting.nullable && raw.trim() === "") return succeed(null);
|
|
172
|
+
const number = Number(raw);
|
|
173
|
+
if (raw.trim() === "" || !Number.isFinite(number)) return fail(`${setting.path} must be a number`);
|
|
174
|
+
if (setting.integer !== false && !Number.isInteger(number)) return fail(`${setting.path} must be a whole number`);
|
|
175
|
+
if (setting.zeroIsNull && number <= 0) return succeed(null);
|
|
176
|
+
return succeed(number);
|
|
177
|
+
}
|
|
178
|
+
function parseSettingValue(setting, raw) {
|
|
179
|
+
if (setting.valueKind === "posthog-projects") {
|
|
180
|
+
const selection = parsePosthogProjectSelection(raw);
|
|
181
|
+
if (selection === null) return fail(`${setting.path} must be all, none or a comma-separated list of positive integer project ids`);
|
|
182
|
+
return succeed(selection);
|
|
183
|
+
}
|
|
184
|
+
if (setting.control === "toggle") {
|
|
185
|
+
if (/^(true|on|yes|1)$/i.test(raw.trim())) return succeed(true);
|
|
186
|
+
if (/^(false|off|no|0)$/i.test(raw.trim())) return succeed(false);
|
|
187
|
+
return fail(`${setting.path} must be a boolean: true or false`);
|
|
188
|
+
}
|
|
189
|
+
if (setting.control === "number") return parseNumberValue(setting, raw);
|
|
190
|
+
if (setting.control === "list" || setting.control === "projects") return parseListValue(setting.path, raw);
|
|
191
|
+
if (setting.control === "json") try {
|
|
192
|
+
return succeed(JSON.parse(raw));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return fail(`${setting.path} must be JSON: ${errorMessage(error)}`);
|
|
195
|
+
}
|
|
196
|
+
const text = raw.trim();
|
|
197
|
+
if (setting.nullable && text === "") return succeed(null);
|
|
198
|
+
return succeed(text);
|
|
199
|
+
}
|
|
200
|
+
function rangeLabel(range) {
|
|
201
|
+
if (range.label) return range.label;
|
|
202
|
+
if (range.max !== void 0) return `between ${range.min} and ${range.max}`;
|
|
203
|
+
return range.exclusiveMin ? `greater than ${range.min}` : `at least ${range.min}`;
|
|
204
|
+
}
|
|
205
|
+
function registryValueError(setting, value) {
|
|
206
|
+
if (value === null) return null;
|
|
207
|
+
if (setting.control === "number" && typeof value === "number" && setting.range) {
|
|
208
|
+
const belowMin = setting.range.exclusiveMin ? value <= setting.range.min : value < setting.range.min;
|
|
209
|
+
const aboveMax = setting.range.max !== void 0 && value > setting.range.max;
|
|
210
|
+
if (belowMin || aboveMax) return `${setting.path} must be ${rangeLabel(setting.range)}`;
|
|
211
|
+
}
|
|
212
|
+
if (setting.options && typeof value === "string" && !setting.options.some((option) => option.value === value)) return `${setting.path} must be one of ${setting.options.map((option) => option.value).join(", ")}`;
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
function isBrowserConfigKey(key) {
|
|
216
|
+
return Object.hasOwn(BrowserConfig.shape, key);
|
|
217
|
+
}
|
|
218
|
+
function dashboardSchemaCheck(setting) {
|
|
219
|
+
const [topLevelKey, ...nestedParts] = setting.path.split(".");
|
|
220
|
+
return (candidate) => {
|
|
221
|
+
if (!isBrowserConfigKey(topLevelKey)) return null;
|
|
222
|
+
const parsed = BrowserConfig.shape[topLevelKey].safeParse(candidate[topLevelKey]);
|
|
223
|
+
if (parsed.success) return null;
|
|
224
|
+
const issue = parsed.error.issues.find((candidateIssue) => nestedParts.every((part, index) => String(candidateIssue.path[index]) === part)) ?? parsed.error.issues.at(0);
|
|
225
|
+
if (!issue) return null;
|
|
226
|
+
const issueKeyPath = [topLevelKey, ...issue.path.map(String)].join(".");
|
|
227
|
+
return issue.message.includes(issueKeyPath) ? issue.message : `${issueKeyPath}: ${issue.message}`;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function validationErrorOf(view, mutate, settingPath, checks) {
|
|
231
|
+
const candidate = cloneValue(view.config);
|
|
232
|
+
if (!isRecord(candidate)) return "config must be a plain object";
|
|
233
|
+
mutate(candidate);
|
|
234
|
+
const validation = validateConfig(withEnvSecrets(candidate, readEnvSecrets(process.env)));
|
|
235
|
+
if (!validation.ok) {
|
|
236
|
+
const schemaMessage = validation.errors.join("; ");
|
|
237
|
+
return schemaMessage.includes(settingPath) ? schemaMessage : `${settingPath}: ${schemaMessage}`;
|
|
238
|
+
}
|
|
239
|
+
for (const check of checks) {
|
|
240
|
+
const error = check(candidate);
|
|
241
|
+
if (error) return error;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function applyValidatedMutation(view, setting, mutate, checks = []) {
|
|
246
|
+
const validationError = validationErrorOf(view, mutate, setting.path, checks);
|
|
247
|
+
if (validationError) throw new Error(validationError);
|
|
248
|
+
const saved = saveConfigFileOnce(view.configPath, mutate);
|
|
249
|
+
if (!saved.ok) throw new Error(saved.message);
|
|
250
|
+
return saved.config;
|
|
251
|
+
}
|
|
252
|
+
function unreadableHomeMessage(homeDir, error) {
|
|
253
|
+
return `cannot read the Glimmervoid home at ${homeDir} (${errorCode(error) ?? errorMessage(error)}). A session with guardrails cannot read it; run this command from a shell outside the session`;
|
|
254
|
+
}
|
|
255
|
+
function homeReadError(homeDir) {
|
|
256
|
+
try {
|
|
257
|
+
fs.accessSync(homeDir, fs.constants.R_OK);
|
|
258
|
+
return null;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (errorCode(error) === "ENOENT" && !process.env["GLIMMERVOID_AGENT_URL"]) return null;
|
|
261
|
+
return unreadableHomeMessage(homeDir, error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function readConfigFile(configPath, homeDir) {
|
|
265
|
+
try {
|
|
266
|
+
const loaded = loadConfigFile(configPath, { exitOnError: false });
|
|
267
|
+
if (!loaded.config) return fail(loaded.message);
|
|
268
|
+
return succeed(loaded.config);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (errorCode(error) === "EACCES" || errorCode(error) === "EPERM") return fail(unreadableHomeMessage(homeDir, error));
|
|
271
|
+
return fail(`could not read ${configPath}: ${errorMessage(error)}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function openConfigFile() {
|
|
275
|
+
const env = process.env;
|
|
276
|
+
const homeDir = glimmervoidHomeDir(os.homedir(), env);
|
|
277
|
+
const homeError = homeReadError(homeDir);
|
|
278
|
+
if (homeError) return fail(homeError);
|
|
279
|
+
const decided = decideConfigPath({
|
|
280
|
+
env,
|
|
281
|
+
homeDir
|
|
282
|
+
}, (candidate) => fs.existsSync(candidate));
|
|
283
|
+
if (!decided.path && decided.source === "env") return fail(`config file not found: ${decided.envPath}`);
|
|
284
|
+
if (!decided.path) return fail(`no config.json at ${decided.homePath}. Start Glimmervoid once to create it; glimmervoid settings schema needs no config file`);
|
|
285
|
+
const loaded = readConfigFile(decided.path, homeDir);
|
|
286
|
+
if (!loaded.ok) return fail(loaded.message);
|
|
287
|
+
const secretSources = Object.fromEntries(readEnvSecrets(env).map((secret) => [`${secret.blockName}.${secret.secretKey}`, secret.environmentVariable]));
|
|
288
|
+
return succeed({
|
|
289
|
+
configPath: decided.path,
|
|
290
|
+
config: loaded.value,
|
|
291
|
+
secretSources
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function print(context, jsonValue, text) {
|
|
295
|
+
context.writeOutput(`${context.isJson ? JSON.stringify(jsonValue, null, 2) : text}\n`);
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
function requireView() {
|
|
299
|
+
const opened = openConfigFile();
|
|
300
|
+
if (!opened.ok) throw new Error(opened.message);
|
|
301
|
+
return opened.value;
|
|
302
|
+
}
|
|
303
|
+
function requireSetting(context, view) {
|
|
304
|
+
const resolved = requireConfigFileSetting(requirePositional(context.positionals, 1, "path"), view.configPath);
|
|
305
|
+
if (!resolved.ok) throw new Error(resolved.message);
|
|
306
|
+
return resolved.value;
|
|
307
|
+
}
|
|
308
|
+
function secretRefusal(setting) {
|
|
309
|
+
const binding = ENV_SECRET_BINDINGS.find((candidate) => `${candidate.blockName}.${candidate.secretKey}` === setting.path);
|
|
310
|
+
const environmentWay = binding ? `, or set ${binding.environmentVariable} in the environment Glimmervoid starts with` : "";
|
|
311
|
+
return `${setting.path} is a secret, so this command never takes or removes it, since a value on the command line lands in shell history; change it in the dashboard under Settings > ${sectionOf(setting).title}${environmentWay}`;
|
|
312
|
+
}
|
|
313
|
+
function writeRefusal(setting, configPath) {
|
|
314
|
+
if (setting.control === "password") return secretRefusal(setting);
|
|
315
|
+
const handEditedReason = HAND_EDITED_SETTING_REASONS.get(setting.path);
|
|
316
|
+
if (handEditedReason) return `${setting.path} cannot be changed from this command, because ${handEditedReason}; edit config.json at ${configPath} by hand`;
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function requireWritableSetting(context, view) {
|
|
320
|
+
const setting = requireSetting(context, view);
|
|
321
|
+
const refusal = writeRefusal(setting, view.configPath);
|
|
322
|
+
if (refusal) throw new Error(refusal);
|
|
323
|
+
return setting;
|
|
324
|
+
}
|
|
325
|
+
var commands = {
|
|
326
|
+
list: (context) => {
|
|
327
|
+
const view = requireView();
|
|
328
|
+
const settings = ALL_SETTINGS.filter(isConfigFileSetting);
|
|
329
|
+
const readouts = settings.map((setting) => readSetting(setting, view));
|
|
330
|
+
const readoutById = new Map(readouts.map((readout) => [readout.id, readout]));
|
|
331
|
+
const rowOf = (setting) => {
|
|
332
|
+
const readout = readoutById.get(setting.id);
|
|
333
|
+
if (!readout) return [
|
|
334
|
+
setting.path,
|
|
335
|
+
"",
|
|
336
|
+
""
|
|
337
|
+
];
|
|
338
|
+
return [
|
|
339
|
+
setting.path,
|
|
340
|
+
fitColumn(valueText(readout, setting)),
|
|
341
|
+
readout.source
|
|
342
|
+
];
|
|
343
|
+
};
|
|
344
|
+
return print(context, readouts, renderSectionTables(settings, [
|
|
345
|
+
"Setting",
|
|
346
|
+
"Value",
|
|
347
|
+
"Source"
|
|
348
|
+
], rowOf));
|
|
349
|
+
},
|
|
350
|
+
get: (context) => {
|
|
351
|
+
const view = requireView();
|
|
352
|
+
const setting = requireSetting(context, view);
|
|
353
|
+
const readout = readSetting(setting, view);
|
|
354
|
+
const sourceNote = readout.secret && !readout.configured ? "" : ` (${readout.source})`;
|
|
355
|
+
return print(context, readout, `${setting.path} = ${valueText(readout, setting)}${sourceNote}`);
|
|
356
|
+
},
|
|
357
|
+
set: (context) => {
|
|
358
|
+
const view = requireView();
|
|
359
|
+
const setting = requireWritableSetting(context, view);
|
|
360
|
+
const parsed = parseSettingValue(setting, requirePositional(context.positionals, 2, "value"));
|
|
361
|
+
if (!parsed.ok) throw new Error(parsed.message);
|
|
362
|
+
const previous = cloneValue(valueAtPath(view.config, setting.path)) ?? null;
|
|
363
|
+
const written = applyValidatedMutation(view, setting, (config) => setValueAtPath(config, setting.path, parsed.value), [() => registryValueError(setting, parsed.value), dashboardSchemaCheck(setting)]);
|
|
364
|
+
const readout = readSetting(setting, {
|
|
365
|
+
...view,
|
|
366
|
+
config: written
|
|
367
|
+
});
|
|
368
|
+
return print(context, {
|
|
369
|
+
...readout,
|
|
370
|
+
previous,
|
|
371
|
+
configPath: view.configPath
|
|
372
|
+
}, `${setting.path} = ${valueText(readout, setting)}\n${EFFECT_NOTES[setting.takesEffect]}`);
|
|
373
|
+
},
|
|
374
|
+
unset: (context) => {
|
|
375
|
+
const view = requireView();
|
|
376
|
+
const setting = requireWritableSetting(context, view);
|
|
377
|
+
if (valueAtPath(view.config, setting.path) === void 0) return print(context, {
|
|
378
|
+
...readSetting(setting, view),
|
|
379
|
+
configPath: view.configPath,
|
|
380
|
+
changed: false
|
|
381
|
+
}, `${setting.path} is not set in config.json; the default ${defaultText(setting)} already applies`);
|
|
382
|
+
const written = applyValidatedMutation(view, setting, (config) => deleteValueAtPath(config, setting.path), [dashboardSchemaCheck(setting)]);
|
|
383
|
+
return print(context, {
|
|
384
|
+
...readSetting(setting, {
|
|
385
|
+
...view,
|
|
386
|
+
config: written
|
|
387
|
+
}),
|
|
388
|
+
configPath: view.configPath,
|
|
389
|
+
changed: true
|
|
390
|
+
}, `${setting.path} unset; the default ${defaultText(setting)} applies\n${EFFECT_NOTES[setting.takesEffect]}`);
|
|
391
|
+
},
|
|
392
|
+
search: (context) => {
|
|
393
|
+
const query = context.positionals.slice(1).join(" ");
|
|
394
|
+
if (query.trim() === "") throw new Error(`missing <text>\n\n${usage}`);
|
|
395
|
+
const results = scoreSettingsSearch(SETTINGS_REGISTRY.sections, query, SETTINGS_REGISTRY.fileOnly);
|
|
396
|
+
const rows = results.map((result) => [
|
|
397
|
+
result.setting.scope === "browser" ? `${result.setting.id} (browser preference)` : result.setting.path,
|
|
398
|
+
result.setting.title,
|
|
399
|
+
sectionOf(result.setting).title
|
|
400
|
+
]);
|
|
401
|
+
const text = results.length === 0 ? "(no matches)" : renderTable({
|
|
402
|
+
title: `Settings matching ${query}`,
|
|
403
|
+
headers: [
|
|
404
|
+
"Setting",
|
|
405
|
+
"Title",
|
|
406
|
+
"Section"
|
|
407
|
+
],
|
|
408
|
+
rows,
|
|
409
|
+
align: [
|
|
410
|
+
"left",
|
|
411
|
+
"left",
|
|
412
|
+
"left"
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
return print(context, results.map((result) => ({
|
|
416
|
+
...describeSetting(result.setting),
|
|
417
|
+
score: result.score
|
|
418
|
+
})), text);
|
|
419
|
+
},
|
|
420
|
+
schema: (context) => {
|
|
421
|
+
const rowOf = (setting) => [
|
|
422
|
+
setting.id,
|
|
423
|
+
setting.scope === "browser" ? `${setting.path} (browser)` : setting.path,
|
|
424
|
+
setting.control,
|
|
425
|
+
fitColumn(JSON.stringify(setting.defaultValue ?? null)),
|
|
426
|
+
setting.unit?.storedLabel ?? setting.unit?.label ?? "",
|
|
427
|
+
setting.unit?.label ?? "",
|
|
428
|
+
setting.takesEffect
|
|
429
|
+
];
|
|
430
|
+
return print(context, ALL_SETTINGS.map(describeSetting), renderSectionTables(ALL_SETTINGS, [
|
|
431
|
+
"Id",
|
|
432
|
+
"Path",
|
|
433
|
+
"Type",
|
|
434
|
+
"Default",
|
|
435
|
+
"Stored unit",
|
|
436
|
+
"Display unit",
|
|
437
|
+
"Effect"
|
|
438
|
+
], rowOf));
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
function runCommandLine(commandArguments, io) {
|
|
442
|
+
const { values, positionals } = parseArgs({
|
|
443
|
+
args: commandArguments,
|
|
444
|
+
allowPositionals: true,
|
|
445
|
+
options: {
|
|
446
|
+
json: {
|
|
447
|
+
type: "boolean",
|
|
448
|
+
default: false
|
|
449
|
+
},
|
|
450
|
+
help: {
|
|
451
|
+
type: "boolean",
|
|
452
|
+
short: "h",
|
|
453
|
+
default: false
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
const commandName = positionals[0];
|
|
458
|
+
const command = commandName === void 0 ? void 0 : commands[commandName];
|
|
459
|
+
if (values.help || commandName === void 0 || command === void 0) {
|
|
460
|
+
io.writeOutput(`${usage}\n`);
|
|
461
|
+
return values.help ? 0 : 1;
|
|
462
|
+
}
|
|
463
|
+
return command({
|
|
464
|
+
positionals,
|
|
465
|
+
isJson: values.json,
|
|
466
|
+
writeOutput: io.writeOutput
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function processIo() {
|
|
470
|
+
return {
|
|
471
|
+
writeOutput: (text) => process.stdout.write(text),
|
|
472
|
+
writeError: (text) => process.stderr.write(text)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function runSettingsCommand(commandArguments, io = processIo()) {
|
|
476
|
+
try {
|
|
477
|
+
return runCommandLine(commandArguments, io);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
io.writeError(`settings: ${errorMessage(error)}\n`);
|
|
480
|
+
return 1;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
//#endregion
|
|
484
|
+
export { runSettingsCommand };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { r as errorMessage } from "./text-DoirglWV.js";
|
|
2
|
-
import {
|
|
2
|
+
import { C as SshSignReply, v as SIGN_URL_ENV } from "./session-B8RhqQJe.js";
|
|
3
3
|
import { n as execFileAsync, o as spawn } from "./child-process-safe-CbQnyh7D.js";
|
|
4
4
|
import { a as isLoopbackHostname } from "./hook-relay-core-DA_bz2KA.js";
|
|
5
|
-
import { t as postToAgentIngress } from "./agent-api-cli-
|
|
5
|
+
import { t as postToAgentIngress } from "./agent-api-cli-d-Bgsshx.js";
|
|
6
6
|
import { l as parseSshPublicKey } from "./ssh-signing-core-CRhVVWNW.js";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fsp from "node:fs/promises";
|