@pugi/cli 0.1.0-alpha.10
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/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/run.js +2 -0
- package/dist/commands/jobs.js +245 -0
- package/dist/core/agents/loader.js +104 -0
- package/dist/core/agents/registry.js +69 -0
- package/dist/core/auto-open-browser.js +128 -0
- package/dist/core/bash-classifier.js +1001 -0
- package/dist/core/clipboard.js +70 -0
- package/dist/core/context/builder.js +114 -0
- package/dist/core/context/compaction-events.js +99 -0
- package/dist/core/context/compaction.js +602 -0
- package/dist/core/context/invariants.js +250 -0
- package/dist/core/context/markdown-loader.js +270 -0
- package/dist/core/credentials.js +355 -0
- package/dist/core/engine/adapter-runner.js +8 -0
- package/dist/core/engine/anvil-client.js +156 -0
- package/dist/core/engine/compaction-hook.js +154 -0
- package/dist/core/engine/index.js +12 -0
- package/dist/core/engine/native-pugi.js +369 -0
- package/dist/core/engine/noop.js +27 -0
- package/dist/core/engine/prompts.js +118 -0
- package/dist/core/engine/tool-bridge.js +313 -0
- package/dist/core/file-cache.js +29 -0
- package/dist/core/hooks.js +415 -0
- package/dist/core/index-store.js +260 -0
- package/dist/core/jobs/registry.js +462 -0
- package/dist/core/mcp/client.js +316 -0
- package/dist/core/mcp/registry.js +171 -0
- package/dist/core/mcp/trust.js +91 -0
- package/dist/core/path-security.js +63 -0
- package/dist/core/permission.js +309 -0
- package/dist/core/repl/cap-warning.js +91 -0
- package/dist/core/repl/clipboard-read.js +174 -0
- package/dist/core/repl/history-search.js +175 -0
- package/dist/core/repl/history.js +172 -0
- package/dist/core/repl/kill-ring.js +138 -0
- package/dist/core/repl/session.js +618 -0
- package/dist/core/repl/slash-commands.js +227 -0
- package/dist/core/repl/workspace-context.js +113 -0
- package/dist/core/session.js +258 -0
- package/dist/core/settings.js +59 -0
- package/dist/core/skills/loader.js +454 -0
- package/dist/core/skills/sources.js +480 -0
- package/dist/core/skills/trust.js +172 -0
- package/dist/core/subagents/dispatcher.js +258 -0
- package/dist/core/subagents/index.js +26 -0
- package/dist/core/subagents/spawn.js +86 -0
- package/dist/core/trust.js +109 -0
- package/dist/index.js +8 -0
- package/dist/runtime/cli.js +3405 -0
- package/dist/runtime/commands/agents.js +385 -0
- package/dist/runtime/commands/budget.js +192 -0
- package/dist/runtime/commands/config.js +231 -0
- package/dist/runtime/commands/privacy.js +107 -0
- package/dist/runtime/commands/skills.js +401 -0
- package/dist/runtime/commands/undo.js +329 -0
- package/dist/runtime/update-check.js +294 -0
- package/dist/tools/bash.js +660 -0
- package/dist/tools/file-tools.js +346 -0
- package/dist/tools/registry.js +25 -0
- package/dist/tools/web-fetch.js +535 -0
- package/dist/tui/agent-tree.js +66 -0
- package/dist/tui/conversation-pane.js +45 -0
- package/dist/tui/device-flow.js +142 -0
- package/dist/tui/input-box.js +474 -0
- package/dist/tui/login-picker.js +69 -0
- package/dist/tui/render.js +125 -0
- package/dist/tui/repl-render.js +240 -0
- package/dist/tui/repl-splash-art.js +64 -0
- package/dist/tui/repl-splash.js +111 -0
- package/dist/tui/repl.js +214 -0
- package/dist/tui/slash-palette.js +106 -0
- package/dist/tui/splash-data.js +61 -0
- package/dist/tui/splash.js +31 -0
- package/dist/tui/status-bar.js +71 -0
- package/dist/tui/update-banner.js +8 -0
- package/dist/tui/workspace-context.js +105 -0
- package/package.json +71 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { loadMcpRegistry } from '../../core/mcp/registry.js';
|
|
6
|
+
import { listMcpTrust, setMcpTrust, } from '../../core/mcp/trust.js';
|
|
7
|
+
import { trustWorkspace } from '../../core/trust.js';
|
|
8
|
+
/**
|
|
9
|
+
* `pugi config` — operator-level configuration surface.
|
|
10
|
+
*
|
|
11
|
+
* Subcommands:
|
|
12
|
+
* - `pugi config get <key>` read a value from `~/.pugi/config.json`
|
|
13
|
+
* - `pugi config set <key> <value>` write a value
|
|
14
|
+
* - `pugi config list` dump all values
|
|
15
|
+
* - `pugi config trust .` trust the current workspace (delegates to core/trust.ts)
|
|
16
|
+
* - `pugi config mcp trust <name>` flip MCP server to trusted
|
|
17
|
+
* - `pugi config mcp deny <name>` flip MCP server to denied
|
|
18
|
+
* - `pugi config mcp list` show declared servers + their trust state
|
|
19
|
+
*
|
|
20
|
+
* Schema (pugi-config-v1):
|
|
21
|
+
* {
|
|
22
|
+
* "permissionMode": "ask" | "acceptEdits" | "auto" | "plan" | "dontAsk" | "bypassPermissions",
|
|
23
|
+
* "privacy": "local-only" | "metadata" | "full",
|
|
24
|
+
* "model": "<id>" | null,
|
|
25
|
+
* "preferredEndpoint": "https://api.pugi.io"
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* The config file lives at `~/.pugi/config.json` (PUGI_HOME-aware) and uses
|
|
29
|
+
* mode 0o600. Unknown keys are rejected by `set` so a typo never silently
|
|
30
|
+
* persists.
|
|
31
|
+
*/
|
|
32
|
+
const configSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
permissionMode: z
|
|
35
|
+
.enum(['plan', 'ask', 'acceptEdits', 'auto', 'dontAsk', 'bypassPermissions'])
|
|
36
|
+
.optional(),
|
|
37
|
+
privacy: z.enum(['local-only', 'metadata', 'full']).optional(),
|
|
38
|
+
model: z.string().nullable().optional(),
|
|
39
|
+
preferredEndpoint: z.string().url().optional(),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
const CONFIG_KEYS = ['permissionMode', 'privacy', 'model', 'preferredEndpoint'];
|
|
43
|
+
export async function runConfigCommand(args, ctx) {
|
|
44
|
+
const sub = args[0];
|
|
45
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
46
|
+
ctx.writeOutput({
|
|
47
|
+
command: 'config',
|
|
48
|
+
usage: [
|
|
49
|
+
'pugi config get <key>',
|
|
50
|
+
'pugi config set <key> <value>',
|
|
51
|
+
'pugi config list',
|
|
52
|
+
'pugi config trust .',
|
|
53
|
+
'pugi config mcp trust <name>',
|
|
54
|
+
'pugi config mcp deny <name>',
|
|
55
|
+
'pugi config mcp list',
|
|
56
|
+
],
|
|
57
|
+
}, [
|
|
58
|
+
'Usage:',
|
|
59
|
+
' pugi config get <key> Read a config value.',
|
|
60
|
+
' pugi config set <key> <value> Write a config value.',
|
|
61
|
+
' pugi config list Show all config values.',
|
|
62
|
+
' pugi config trust . Trust the current workspace for hooks + MCP.',
|
|
63
|
+
' pugi config mcp trust <name> Mark an MCP server as trusted.',
|
|
64
|
+
' pugi config mcp deny <name> Block an MCP server.',
|
|
65
|
+
' pugi config mcp list Show declared MCP servers + trust state.',
|
|
66
|
+
].join('\n'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
switch (sub) {
|
|
70
|
+
case 'get':
|
|
71
|
+
return runConfigGet(args.slice(1), ctx);
|
|
72
|
+
case 'set':
|
|
73
|
+
return runConfigSet(args.slice(1), ctx);
|
|
74
|
+
case 'list':
|
|
75
|
+
return runConfigList(ctx);
|
|
76
|
+
case 'trust':
|
|
77
|
+
return runConfigTrust(args.slice(1), ctx);
|
|
78
|
+
case 'mcp':
|
|
79
|
+
return runConfigMcp(args.slice(1), ctx);
|
|
80
|
+
default:
|
|
81
|
+
throw new Error(`Unknown sub-command "pugi config ${sub}". Expected get, set, list, trust, or mcp.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function configPath() {
|
|
85
|
+
const home = process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
|
|
86
|
+
return resolve(home, 'config.json');
|
|
87
|
+
}
|
|
88
|
+
export function readConfig() {
|
|
89
|
+
const path = configPath();
|
|
90
|
+
if (!existsSync(path))
|
|
91
|
+
return {};
|
|
92
|
+
const raw = readFileSync(path, 'utf8');
|
|
93
|
+
if (raw.trim() === '')
|
|
94
|
+
return {};
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
return configSchema.parse(parsed);
|
|
97
|
+
}
|
|
98
|
+
function writeConfig(config) {
|
|
99
|
+
const path = configPath();
|
|
100
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
+
// 0o600 — `preferredEndpoint` could be a private self-hosted URL; the
|
|
102
|
+
// config does not contain secrets, but file mode parity with the trust
|
|
103
|
+
// ledger keeps the audit surface uniform.
|
|
104
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
mode: 0o600,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function isConfigKey(value) {
|
|
110
|
+
return CONFIG_KEYS.includes(value);
|
|
111
|
+
}
|
|
112
|
+
function runConfigGet(args, ctx) {
|
|
113
|
+
const key = args[0];
|
|
114
|
+
if (!key)
|
|
115
|
+
throw new Error('pugi config get requires a key.');
|
|
116
|
+
if (!isConfigKey(key)) {
|
|
117
|
+
throw new Error(`Unknown config key "${key}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
|
|
118
|
+
}
|
|
119
|
+
const config = readConfig();
|
|
120
|
+
const value = config[key] ?? null;
|
|
121
|
+
ctx.writeOutput({ command: 'config.get', key, value }, value === null || value === undefined ? `${key} = (unset)` : `${key} = ${String(value)}`);
|
|
122
|
+
}
|
|
123
|
+
function runConfigSet(args, ctx) {
|
|
124
|
+
const key = args[0];
|
|
125
|
+
const value = args.slice(1).join(' ');
|
|
126
|
+
if (!key)
|
|
127
|
+
throw new Error('pugi config set requires a key.');
|
|
128
|
+
if (value.length === 0)
|
|
129
|
+
throw new Error('pugi config set requires a value.');
|
|
130
|
+
if (!isConfigKey(key)) {
|
|
131
|
+
throw new Error(`Unknown config key "${key}". Allowed: ${CONFIG_KEYS.join(', ')}.`);
|
|
132
|
+
}
|
|
133
|
+
const current = readConfig();
|
|
134
|
+
// Build the candidate and validate via the schema so an invalid value
|
|
135
|
+
// (e.g. `permissionMode = nonsense`) is rejected before persistence.
|
|
136
|
+
const candidate = { ...current };
|
|
137
|
+
candidate[key] = coerceValue(key, value);
|
|
138
|
+
const validated = configSchema.parse(candidate);
|
|
139
|
+
writeConfig(validated);
|
|
140
|
+
ctx.writeOutput({
|
|
141
|
+
command: 'config.set',
|
|
142
|
+
key,
|
|
143
|
+
value: validated[key] ?? null,
|
|
144
|
+
}, `${key} = ${String(validated[key] ?? '')}`);
|
|
145
|
+
}
|
|
146
|
+
function coerceValue(key, raw) {
|
|
147
|
+
if (key === 'model') {
|
|
148
|
+
return raw === 'null' || raw === '' ? null : raw;
|
|
149
|
+
}
|
|
150
|
+
return raw;
|
|
151
|
+
}
|
|
152
|
+
function runConfigList(ctx) {
|
|
153
|
+
const config = readConfig();
|
|
154
|
+
const entries = CONFIG_KEYS.map((key) => ({
|
|
155
|
+
key,
|
|
156
|
+
value: config[key] ?? null,
|
|
157
|
+
}));
|
|
158
|
+
ctx.writeOutput({ command: 'config.list', config, entries }, entries
|
|
159
|
+
.map((entry) => entry.value === null || entry.value === undefined
|
|
160
|
+
? `${entry.key} = (unset)`
|
|
161
|
+
: `${entry.key} = ${String(entry.value)}`)
|
|
162
|
+
.join('\n'));
|
|
163
|
+
}
|
|
164
|
+
async function runConfigTrust(args, ctx) {
|
|
165
|
+
const target = args[0];
|
|
166
|
+
if (!target)
|
|
167
|
+
throw new Error('pugi config trust requires a path (use "." for cwd).');
|
|
168
|
+
const root = target === '.' ? ctx.workspaceRoot : resolve(ctx.workspaceRoot, target);
|
|
169
|
+
// Identity for the audit entry: prefer the explicit env override
|
|
170
|
+
// (`PUGI_TRUSTED_BY`), then `USER` from the shell, then literal
|
|
171
|
+
// 'cli'. The trust ledger requires a non-empty string and we want
|
|
172
|
+
// it to mean something for a future audit replay.
|
|
173
|
+
const by = process.env.PUGI_TRUSTED_BY?.trim() ||
|
|
174
|
+
process.env.USER?.trim() ||
|
|
175
|
+
process.env.USERNAME?.trim() ||
|
|
176
|
+
'cli';
|
|
177
|
+
await trustWorkspace(root, by);
|
|
178
|
+
ctx.writeOutput({ command: 'config.trust', workspaceRoot: root, trustedBy: by }, `Trusted workspace: ${root}`);
|
|
179
|
+
}
|
|
180
|
+
async function runConfigMcp(args, ctx) {
|
|
181
|
+
const sub = args[0];
|
|
182
|
+
if (!sub) {
|
|
183
|
+
throw new Error('pugi config mcp requires a sub-command: trust, deny, or list.');
|
|
184
|
+
}
|
|
185
|
+
switch (sub) {
|
|
186
|
+
case 'list':
|
|
187
|
+
return runConfigMcpList(ctx);
|
|
188
|
+
case 'trust':
|
|
189
|
+
return runConfigMcpFlip(args.slice(1), ctx, 'trusted');
|
|
190
|
+
case 'deny':
|
|
191
|
+
return runConfigMcpFlip(args.slice(1), ctx, 'denied');
|
|
192
|
+
default:
|
|
193
|
+
throw new Error(`Unknown sub-command "pugi config mcp ${sub}". Expected trust, deny, or list.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function runConfigMcpList(ctx) {
|
|
197
|
+
const registry = await loadMcpRegistry(ctx.workspaceRoot, { connect: false });
|
|
198
|
+
const declared = Array.from(registry.servers.values()).map((state) => ({
|
|
199
|
+
name: state.name,
|
|
200
|
+
command: state.config.command,
|
|
201
|
+
args: state.config.args,
|
|
202
|
+
trust: state.trust,
|
|
203
|
+
surfacedTools: state.surfacedTools.length,
|
|
204
|
+
lastError: state.lastError ?? null,
|
|
205
|
+
}));
|
|
206
|
+
const ledger = await listMcpTrust();
|
|
207
|
+
await registry.shutdown();
|
|
208
|
+
if (declared.length === 0) {
|
|
209
|
+
ctx.writeOutput({ command: 'config.mcp.list', servers: [], ledger }, 'No MCP servers declared. Add one to .pugi/mcp.json or ~/.pugi/mcp.json.');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
ctx.writeOutput({ command: 'config.mcp.list', servers: declared, ledger }, [
|
|
213
|
+
'MCP servers:',
|
|
214
|
+
...declared.map((server) => ` ${server.name.padEnd(20)} ${server.trust.padEnd(8)} ${server.command} ${server.args.join(' ')}`),
|
|
215
|
+
].join('\n'));
|
|
216
|
+
}
|
|
217
|
+
async function runConfigMcpFlip(args, ctx, state) {
|
|
218
|
+
const name = args[0];
|
|
219
|
+
if (!name) {
|
|
220
|
+
throw new Error(`pugi config mcp ${state === 'trusted' ? 'trust' : 'deny'} requires a server name.`);
|
|
221
|
+
}
|
|
222
|
+
const by = process.env.PUGI_TRUSTED_BY?.trim() ||
|
|
223
|
+
process.env.USER?.trim() ||
|
|
224
|
+
process.env.USERNAME?.trim() ||
|
|
225
|
+
'cli';
|
|
226
|
+
await setMcpTrust(name, state, by);
|
|
227
|
+
ctx.writeOutput({ command: `config.mcp.${state === 'trusted' ? 'trust' : 'deny'}`, name, state, decidedBy: by }, state === 'trusted'
|
|
228
|
+
? `MCP server "${name}" is now trusted.`
|
|
229
|
+
: `MCP server "${name}" is now denied.`);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readConfig } from './config.js';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* `pugi privacy` — read or update the operator's privacy mode.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* - `pugi privacy show` — print current mode + source
|
|
11
|
+
* - `pugi privacy set <mode>` — write `local-only | metadata | full`
|
|
12
|
+
*
|
|
13
|
+
* Persistence:
|
|
14
|
+
* - Stored in `~/.pugi/privacy.json` (PUGI_HOME-aware).
|
|
15
|
+
* - The `privacy` key in `~/.pugi/config.json` is also consulted at
|
|
16
|
+
* read time (config takes precedence when both exist) so a user who
|
|
17
|
+
* already set `pugi config set privacy ...` sees the same value
|
|
18
|
+
* here.
|
|
19
|
+
*
|
|
20
|
+
* Modes:
|
|
21
|
+
* - `local-only` — nothing leaves the workstation. Engine commands
|
|
22
|
+
* refuse to ship transcripts; sync is a no-op.
|
|
23
|
+
* - `metadata` — only artifact metadata + session timeline ships
|
|
24
|
+
* (no raw file contents).
|
|
25
|
+
* - `full` — full sync allowed (operator-acknowledged).
|
|
26
|
+
*/
|
|
27
|
+
export const privacyModeSchema = z.enum(['local-only', 'metadata', 'full']);
|
|
28
|
+
const privacyFileSchema = z.object({
|
|
29
|
+
schema: z.number().int().positive().default(1),
|
|
30
|
+
mode: privacyModeSchema,
|
|
31
|
+
});
|
|
32
|
+
function privacyPath() {
|
|
33
|
+
const home = process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
|
|
34
|
+
return resolve(home, 'privacy.json');
|
|
35
|
+
}
|
|
36
|
+
export function readPrivacyFile() {
|
|
37
|
+
const path = privacyPath();
|
|
38
|
+
if (!existsSync(path))
|
|
39
|
+
return null;
|
|
40
|
+
const raw = readFileSync(path, 'utf8');
|
|
41
|
+
if (raw.trim() === '')
|
|
42
|
+
return null;
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
const result = privacyFileSchema.safeParse(parsed);
|
|
45
|
+
if (!result.success)
|
|
46
|
+
return null;
|
|
47
|
+
return result.data.mode;
|
|
48
|
+
}
|
|
49
|
+
function writePrivacyFile(mode) {
|
|
50
|
+
const path = privacyPath();
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
writeFileSync(path, `${JSON.stringify({ schema: 1, mode }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Effective privacy mode resolution order:
|
|
56
|
+
* 1. `~/.pugi/config.json` privacy key (when set via `pugi config set`)
|
|
57
|
+
* 2. `~/.pugi/privacy.json` (when set via `pugi privacy set`)
|
|
58
|
+
* 3. `metadata` (default — matches the M1 default-ship posture)
|
|
59
|
+
*/
|
|
60
|
+
export function resolvePrivacyMode() {
|
|
61
|
+
const config = readConfig();
|
|
62
|
+
if (config.privacy)
|
|
63
|
+
return { mode: config.privacy, source: 'config' };
|
|
64
|
+
const fromFile = readPrivacyFile();
|
|
65
|
+
if (fromFile)
|
|
66
|
+
return { mode: fromFile, source: 'privacy' };
|
|
67
|
+
return { mode: 'metadata', source: 'default' };
|
|
68
|
+
}
|
|
69
|
+
export async function runPrivacyCommand(args, ctx) {
|
|
70
|
+
const sub = args[0];
|
|
71
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
72
|
+
ctx.writeOutput({
|
|
73
|
+
command: 'privacy',
|
|
74
|
+
usage: ['pugi privacy show', 'pugi privacy set <mode>'],
|
|
75
|
+
modes: ['local-only', 'metadata', 'full'],
|
|
76
|
+
}, [
|
|
77
|
+
'Usage:',
|
|
78
|
+
' pugi privacy show Show current privacy mode.',
|
|
79
|
+
' pugi privacy set <mode> Set mode to local-only, metadata, or full.',
|
|
80
|
+
].join('\n'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
switch (sub) {
|
|
84
|
+
case 'show':
|
|
85
|
+
return runPrivacyShow(ctx);
|
|
86
|
+
case 'set':
|
|
87
|
+
return runPrivacySet(args.slice(1), ctx);
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unknown sub-command "pugi privacy ${sub}". Expected show or set.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function runPrivacyShow(ctx) {
|
|
93
|
+
const resolved = resolvePrivacyMode();
|
|
94
|
+
ctx.writeOutput({ command: 'privacy.show', ...resolved }, `Privacy mode: ${resolved.mode} (source: ${resolved.source})`);
|
|
95
|
+
}
|
|
96
|
+
function runPrivacySet(args, ctx) {
|
|
97
|
+
const raw = args[0];
|
|
98
|
+
if (!raw)
|
|
99
|
+
throw new Error('pugi privacy set requires a mode: local-only, metadata, or full.');
|
|
100
|
+
const result = privacyModeSchema.safeParse(raw);
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
throw new Error(`Invalid privacy mode "${raw}". Allowed: local-only, metadata, full.`);
|
|
103
|
+
}
|
|
104
|
+
writePrivacyFile(result.data);
|
|
105
|
+
ctx.writeOutput({ command: 'privacy.set', mode: result.data }, `Privacy mode set to ${result.data}.`);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=privacy.js.map
|